Disclaimer: This repository is not actively maintained!
Previously called Prisma Auth
A very opinionated user authentication package for GraphQL. It uses old-school email/password authentication.
This package provides a GraphQL schema and GraphQL resolvers for everything you need related to authentication. It does not access your data layer (e.g. an ORM); for that you need to write an adapter (which is not hard to do).
If you use Prisma, there is already an adapter for you, graphql-authentication-prisma. You can also checkout the examples/
folder with examples on how to write an adapter for Sequelize and TypeORM!
Features:
- Signup with good ol' email/password and confirmation email
- Login
- Invite another user (sends email)
- Password reset
- Change password of current user
- Update current user info
- Support for graphql-shield to deal with permissions
Adding user authentication seems simple; there are lots of examples on how to write a "login" and a "signup" resolver. You implement it in your own project and continue working. After a while you'll have users forgetting their password so you need to build something for that. Then you want to be able to invite users, ... you get the idea. In the end you have a lot of boilerplate code related to user authentication.
The intention with this package is to let you write as less user-related code as possible, while being flexible enough to support different use cases like open sign up, invitation-only signup, extra fields on the User model etc.
If this package is too opinionated for you, you could still copy/paste parts of it in your application!
Node v8+ should be used. Install with Yarn or npm:
yarn add graphql-authentication email-templates
npm i graphql-authentication email-templates
In your own GraphQL schema you can import all the types this package provides:
# import Query.*, Mutation.* from "node_modules/graphql-authentication/schema.graphql"
This only works if you use graphql-import. If you are using graphql-yoga this will work out of the box!
Alternatively you can only import the types you want to expose, for example:
# import Query.currentUser, Mutation.signupByInvite, Mutation.inviteUser, Mutation.login from "node_modules/graphql-authentication/schema.graphql"
We need to add some configuration to get this package to work. The following example uses graphql-yoga, but it should also work with Apollo Server.
import { graphqlAuthenticationConfig } from 'graphql-authentication';
import * as Email from 'email-templates';
const server = new GraphQLServer({
typeDefs: './schema.graphql',
resolvers,
context: req => ({
...req,
graphqlAuthentication: graphqlAuthenticationConfig({
// Required, see for more info the "Writing an adapter" section on this page
adapter: new GraphqlAuthenticationSequelizeAdapter(),
// Required, used for signing JWT tokens
secret: 'wheredidthesodago',
// Optional, for sending emails with email-templates (https://www.npmjs.com/package/email-templates)
mailer: new Email(),
// Optional, the URL to your frontend which is used in emails
mailAppUrl: 'http://example.com'
})
})
});
You need to expose the resolvers this package provides for you to your own GraphQL server. For example:
import { authQueries, authMutations } from 'graphql-authentication';
const resolvers = {
Query: {
...authQueries
},
Mutation: {
...authMutations
}
};
Lastly, this project can optionally send emails for you (e.g. the password reset link). email-templates
is used for this. Be sure to configure it in the options:
import * as Email from 'email-templates';
graphqlAuthentication: graphqlAuthenticationConfig({
mailer: new Email()
});
However, this package does not provide the email templates itself for you, since these differ too much. You can copy the email templates from our example to get started.
Mutations:
signUpByInvite
signup
confirmEmail
inviteUser
login
changePassword
updateCurrentUser
trigerPasswordReset
passwordReset
Queries:
currentUser
For more details take a look at schema.graphql.
On some of your endpoints you might want to require that the user is logged in, or only allow the user to see the data if they have a specific role. A very powerful package exists for this, graphql-shield:
import { shield, rule } from 'graphql-shield';
import { isAuthResolver } from 'graphql-authentication';
const isAuth = rule()(isAuthResolver);
const permissions = shield({
Mutation: {
publish: isAuth
}
});
const server = new GraphQLServer({
typeDefs: './schema.graphql',
resolvers,
middlewares: [permissions]
});
Take a look at the graphql-shield README to find out more.
Get the current user in a resolver (performs a request to your data layer):
import { getUser } from 'graphql-authentication';
const Mutation = {
async publish(parent, data, ctx) {
const user = await getUser(ctx);
console.log('User', user.email);
}
};
Get only the current user ID in a resolver (without request to your data layer):
import { getUserId } from 'graphql-authentication';
const Mutation = {
async publish(parent, data, ctx) {
const userId = await getUserId(ctx);
console.log('User', userId);
}
};
JWT tokens are used to handle sessions. In the frontend you can perform a login like this:
mutation login($email: String!, $password: String!) {
login(email: $email, password: $password) {
token
user {
# optional
name
}
}
}
And then save the token to localStorage
. Now you need to send the token with every request. If you are using Apollo, the documentation has a great example on how to do this.
If you wish to expose some fields on the User type that are not exposed in our schema.graphql, you can provide your own User. In your own schema.graphql
, do something like the following:
# import Mutation.* from "node_modules/graphql-authentication/schema.graphql"
type Query {
currentUser: User
}
type User {
id: ID!
email: String!
name: String!
inviteAccepted: Boolean!
emailConfirmed: Boolean!
deletedAt: DateTime
lastLogin: DateTime
joinedAt: DateTime!
isSuper: Boolean!
# And finally, our custom field:
isWillingToDance: Boolean!
}
If for example you do not want the joinedAt
field to be exposed, you can simply remove it from your schema.
extend type User
would save some copy/pasta here, but unfortunately that doesn't work yet ingraphql-js
. More info.
By default everyone can signup for your project. But what if you want to only allow invite by signup? In this case you need to leave out the Mutation.signup
import. Example:
# import Mutation.signupByInvite, Mutation.inviteUser, Mutation.login, Mutation.changePassword, Mutation.updateCurrentUser, Mutation.triggerPasswordReset, Mutation.passwordReset, from "node_modules/graphql-authentication/schema.graphql"
After a user signups via the signup
endpoint, they will get an email with a link in it to confirm their email. Meanwhile they can still login in the app. This is done to not disturb the users flow too much (e.g. services like Twitter do this too). It is left open to the project to block the user after a while. With the fields emailConfirmed
and joinedAt
on the User you can perhaps display a warning in your frontend or disallow certain features.
However, you might want to block the user from logging in at all when their email is not yet confirmed. In this case you need to pass this option:
graphqlAuthentication: graphqlAuthenticationConfig({
requiredConfirmedEmailForLogin: true
});
The users password is validated with password.length >= 8
by default. Maybe you want stricter or less stricter validation on this. You will need to pass this option to change it:
graphqlAuthentication: graphqlAuthenticationConfig({
validatePassword: value => value.length >= 10
});
An adapter sits between GraphQL Authentication and your own ORM/database thingy. If you are using Prisma, there is already graphql-authentication-prisma for you.
However, if you don't use Prisma, that's totally fine! Writing an adapter shouldn't take very long.
In the tests there is a good example of an adapter. Also, the examples/
folder contains more some custom adapters, like one for Sequelize.
You can keep the adapter class directly in your own project, make a separate npm package for it or write a PR to add it here (please do)!
TODO: this section needs to be improved