Handling authentication in GraphQL Part 1: Introduction

handling-authentication-in-graphql-header.png

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.

Introduction

You can find part 2

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.

What is authentication?

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.

Authentication in REST

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.

Authentication 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.

Conclusion

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.

You can find part 2