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.
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 match 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 checks 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:
const express = require('express')
const bodyParser = require('body-parser')
const jwt = require('express-jwt')
const app = express()
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
// authentication middleware
const auth = jwt({secret: 'somesuperdupersecret'})
app.get('/comments', (req, res) => {
res.json('All comments')
})
// secured endpoint
app.post('/comments/create', auth, (req, res) => {
// return error if user is not authenticated
if (!req.user){
return res.status(401).send('You are not authorized')
}
// create comment
const comment = {...}
res.json({
message: 'Comment created!',
data: comment
})
})
app.listen(3000, () => {
console.log('Server is up on 3000')
})
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:
const bcrypt = require('bcrypt')
const jsonwebtoken = require('jsonwebtoken')
app.post('/login', (req, res) => {
const user = await User.findOne({ where: { req.body.email } })
if (!user) {
throw new Error('No user with that email')
}
const valid = await bcrypt.compare(req.body.password, user.password)
if (!valid) {
throw new Error('Incorrect password')
}
// signin user and generate a jwt
const token = jsonwebtoken.sign({
id: user.id,
email: user.email
}, 'somesuperdupersecret', { expiresIn: '1y' })
// return json web token
res.json({
message: 'Authentication successful!',
data: token
})
})
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 to be secured. This same approach will work in GraphQL also. We might have a GraphQL server as below:
const express = require('express')
const bodyParser = require('body-parser')
const { graphqlExpress } = require('apollo-server-express')
const schema = require('./schema')
const jwt = require('express-jwt')
const app = express()
// bodyparser
app.use(bodyParser.json())
// authentication middleware
const authMiddleware = jwt({
secret: 'somesuperdupersecret'
})
app.use(authMiddleware)
app.use('/api', graphqlExpress(req => ({
schema,
context: {
user: req.user
}
})))
app.listen(3000, () => {
console.log('Server is up on 3000')
})
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:
const bcrypt = require('bcrypt')
const jsonwebtoken = require('jsonwebtoken')
login (_, { email, password }) {
const user = await User.findOne({ where: { email } })
if (!user) {
throw new Error('No user with that email')
}
const valid = await bcrypt.compare(password, user.password)
if (!valid) {
throw new Error('Incorrect password')
}
// return json web token
return jsonwebtoken.sign({
id: user.id,
email: user.email
}, 'somesuperdupersecret', { expiresIn: '1y' })
}
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 a 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:
const express = require('express')
const bodyParser = require('body-parser')
const { graphqlExpress } = require('apollo-server-express')
const schema = require('./schema')
const jwt = require('express-jwt')
const jwksRsa = require('jwks-rsa')
const app = express()
// bodyparser
app.use(bodyParser.json())
// authentication middleware
const authMiddleware = jwt({
// dynamically provide a signing key based on the kid in the header and
// the signing keys provided by the JWKS endpoint.
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://YOUR_AUTH0_DOMAIN/.well-known/jwks.json`
}),
// validate the audience and the issuer.
audience: '{YOUR_API_IDENTIFIER}',
issuer: `https://YOUR_AUTH0_DOMAIN/`,
algorithms: ['RS256']
})
app.use(authMiddleware)
app.use('/api', graphqlExpress(req => ({
schema,
context: {
user: req.user
}
})))
app.listen(3000, () => {
console.log('Server is up on 3000')
})
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
.
// auth middleware
const authMiddleware = jwt({
credentialsRequired: false
})
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:
addComment (_, args, context) {
// make sure user is authenticated
if (!context.user) {
throw new Error('You are not authorized!')
}
// user is authenticated, continue with adding comment
}
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.