Part one of a three part series on authentication in GraphQL. In this part, get an overview of authentication and how it is handled in GraphQL.
Any application that stores user data requires some form of authentication. In this series, I’ll be showing how to handle authentication in GraphQL. We’ll begin by looking at an overview of authentication and how it is handled in GraphQL. Then we’ll move on to practically adding authentication to a GraphQL server in two ways: manually adding authentication with JWT and using Auth0.
This tutorial assumes you have some basic understanding of GraphQL. If not, you might want to check out Getting up and running with GraphQL.
In this first part of the series, we’ll be looking at an overview of authentication, how it is done in GraphQL and how is it different from REST.
To get started, let’s look at what authentication is. Authentication is the process of determining claimed user identity by checking user-provided credentials. For instance, to log in to an application, a user must provide some kind of credential with which to identify themselves. These credentials can be a username/password pair or a kind of token. If the credentials matches what’s in the application database, the user is logged in.
Authentication is the process of recognizing a user’s identity. It is different from authorization. Authentication and authorization are commonly used together as they work hand-in-hand but they are completely different. Authorization occurs after a successful authentication, it check the access levels or privileges of the user which will determine what the user can see or do with the application.
We’ll be focusing our attention on authentication in GraphQL in this tutorial.
If you have ever done authentication in REST in the past, you will know that there are different ways it can be done, include using Basic Auth, Hawk, Bearer Token, OAuth etc. Let’s take a look at how authentication can be done in a typical REST API built with Express. We’ll use JWT for the purpose of this tutorial:
1const express = require('express') 2 const bodyParser = require('body-parser') 3 const jwt = require('express-jwt') 4 5 const app = express() 6 7 app.use(bodyParser.json()) 8 app.use(bodyParser.urlencoded({ extended: false })) 9 10 // authentication middleware 11 const auth = jwt({secret: 'somesuperdupersecret'}) 12 13 app.get('/comments', (req, res) => { 14 res.json('All comments') 15 }) 16 17 // secured endpoint 18 app.post('/comments/create', auth, (req, res) => { 19 // return error if user is not authenticated 20 if (!req.user){ 21 return res.status(401).send('You are not authorized') 22 } 23 24 // create comment 25 const comment = {...} 26 27 res.json({ 28 message: 'Comment created!', 29 data: comment 30 }) 31 }) 32 33 app.listen(3000, () => { 34 console.log('Server is up on 3000') 35 })
As we can see, in a REST API, we can easily restrict an endpoint to only authenticated users by adding an auth middleware to it. This way, all other endpoints can be accessed freely. In the code above, the /comments
endpoint can be accessed without authentication, but the /comments/create
endpoint requires users to be authenticated.
Then users can be authenticated through a /login
endpoint like below:
1const bcrypt = require('bcrypt') 2 const jsonwebtoken = require('jsonwebtoken') 3 4 app.post('/login', (req, res) => { 5 const user = await User.findOne({ where: { req.body.email } }) 6 7 if (!user) { 8 throw new Error('No user with that email') 9 } 10 11 const valid = await bcrypt.compare(req.body.password, user.password) 12 13 if (!valid) { 14 throw new Error('Incorrect password') 15 } 16 17 // signin user and generate a jwt 18 const token = jsonwebtoken.sign({ 19 id: user.id, 20 email: user.email 21 }, 'somesuperdupersecret', { expiresIn: '1y' }) 22 23 // return json web token 24 res.json({ 25 message: 'Authentication successful!', 26 data: token 27 }) 28 })
That’s basically how authentication is done in REST. Now, let’s look at how authentication is handled in GraphQL.
GraphQL doesn’t really make a prescription about how you handle authentication. Authentication in GraphQL can be handled using the methods we are familiar with from REST. The only difference is how they are implemented. In a REST API, we can define an auth middleware and apply it to a number of endpoints we might want secured. This same approach will work in GraphQL also. We might have a GraphQL server as below:
1const express = require('express') 2 const bodyParser = require('body-parser') 3 const { graphqlExpress } = require('apollo-server-express') 4 const schema = require('./schema') 5 const jwt = require('express-jwt') 6 7 const app = express() 8 9 // bodyparser 10 app.use(bodyParser.json()) 11 12 // authentication middleware 13 const authMiddleware = jwt({ 14 secret: 'somesuperdupersecret' 15 }) 16 17 app.use(authMiddleware) 18 19 app.use('/api', graphqlExpress(req => ({ 20 schema, 21 context: { 22 user: req.user 23 } 24 }))) 25 26 app.listen(3000, () => { 27 console.log('Server is up on 3000') 28 })
Since GraphQL has only one endpoint, which all requests are made through, we simply apply the auth middleware to that endpoint. Just as with REST, the jwt
will check if an Authorization
header with a valid token is available on every request made to the endpoint. If present, it will decode it then add a user
object to the request. Otherwise, user
will be null
. So, we can get the details of the authenticated user with req.user
. To make user
available in GraphQL, we add it to the context
object which is passed as option to GraphQL. Thereafter, we can use the user
object however we like.
To authenticate users, we can add a login
resolver function like below:
1const bcrypt = require('bcrypt') 2 const jsonwebtoken = require('jsonwebtoken') 3 4 login (_, { email, password }) { 5 const user = await User.findOne({ where: { email } }) 6 7 if (!user) { 8 throw new Error('No user with that email') 9 } 10 11 const valid = await bcrypt.compare(password, user.password) 12 13 if (!valid) { 14 throw new Error('Incorrect password') 15 } 16 17 // return json web token 18 return jsonwebtoken.sign({ 19 id: user.id, 20 email: user.email 21 }, 'somesuperdupersecret', { expiresIn: '1y' }) 22 }
This will verify the details provided by a user against what’s in the database. Once the details are verified, the user is authenticated and a JWT is generated and returned as response.
If we are using a third-party authentication service like Auth0 to handle authentication, then the approach will be slightly different. We might have something like below:
1const express = require('express') 2 const bodyParser = require('body-parser') 3 const { graphqlExpress } = require('apollo-server-express') 4 const schema = require('./schema') 5 const jwt = require('express-jwt') 6 const jwksRsa = require('jwks-rsa') 7 8 const app = express() 9 10 // bodyparser 11 app.use(bodyParser.json()) 12 13 // authentication middleware 14 const authMiddleware = jwt({ 15 // dynamically provide a signing key based on the kid in the header and 16 // the signing keys provided by the JWKS endpoint. 17 secret: jwksRsa.expressJwtSecret({ 18 cache: true, 19 rateLimit: true, 20 jwksRequestsPerMinute: 5, 21 jwksUri: `https://YOUR_AUTH0_DOMAIN/.well-known/jwks.json` 22 }), 23 24 // validate the audience and the issuer. 25 audience: '{YOUR_API_IDENTIFIER}', 26 issuer: `https://YOUR_AUTH0_DOMAIN/`, 27 algorithms: ['RS256'] 28 }) 29 30 app.use(authMiddleware) 31 32 app.use('/api', graphqlExpress(req => ({ 33 schema, 34 context: { 35 user: req.user 36 } 37 }))) 38 39 app.listen(3000, () => { 40 console.log('Server is up on 3000') 41 })
The only difference here is in the authentication middleware which makes use of Auth0 specific details to check and verify the JWT obtained from the incoming requests.
From our GraphQL server implementations above, you can see that applying the auth middleware to the GraphQL endpoint automatically makes all our queries, mutations etc. secured. In many cases, this might not be what we want. If we are using an auth middleware package like express-jwt
(which is highly recommended when using JWT) as in the example above, we can prevent that by setting credentialsRequired
to false
.
1// auth middleware 2 const authMiddleware = jwt({ 3 credentialsRequired: false 4 })
In our GraphQL logic, we can then check if the user
object is available to determine whether a user is authenticated or not. This can be done in a resolver function. Resolver functions are the simplest and commonest place to handle this kind of logic. So, in our resolver function, we can do something like:
1addComment (_, args, context) { 2 // make sure user is authenticated 3 if (!context.user) { 4 throw new Error('You are not authorized!') 5 } 6 7 // user is authenticated, continue with adding comment 8 }
If the user isn’t authenticated we throw an error, otherwise we allow the user to continue with the rest of the app.
We have seen what authentication is, how it is done in REST and how it can also be done in GraphQL. This is just an overview of handling authentication in GraphQL. The interesting thing about authentication in GraphQL is that, in addition to writing our own authentication middleware, we can also make use of packages like Passport, express-jwt etc. which we are already used to using from REST.
In the next part of this series, we’ll be looking at a more practical example on how to add authentication with JWT to a GraphQL server by building an authentication system.