NodeJS GraphQL: Adding JWT Authentication to GraphQL API in NodeJS #6

In this post, we will set up the JWT token authentication for our GraphQL API to authenticate the users



In this post, we are going to see how to protect particular GraphQL API using JSON web tokens that allow the users to authenticate with JWT tokens. we will be;
  • We will add a mutation that allows obtaining the token with username/password
  • We will add mutation to validate the user token
  • We will authenticate the users using the token in out resolvers

One of the most popular ways to enforce some kind of authorization in an application is through the use of JSON web tokens (JWT).

Users authenticate with a service and the service responds with a JWT to be used in every future request so that way the password is kept safe. The service can then validate the JWT to make sure it is correct and not expired.

So lets get started!
. . .


Generating and Validating JSON Web Tokens

Before we start adding code to the project, let’s create the project and download any project dependencies that will be used. With the project created, execute the following from the command line:
yarn add bcrypt
bcrypt package will allow us to sign and validate JSON web tokens.
Now that we have our project created and the appropriate dependencies in hand, let’s start adding some code. We’re going to start by adding our boilerplate code and the logic for creating and validating JSON web tokens. Let's create the JWT token:

const jwt = require('jsonwebtoken');

const {
AuthenticationError,
} = require('apollo-server-express');

const createToken = (email, password) => {
try {
// here you will add actual database call to validate username/password
const token = jwt.sign({ email: email, password: password }, "supersecret");
return { token, email }
} catch (e) {
throw new AuthenticationError(
'Authentication token is invalid, please log in',
)
}
}

What we’re doing is taking a username and password and creating a JWT token from it. Then we sign this token using our secret and return the signed token back as a response.

If you really wanted to be production-ready, you would not include the password in the token and you’d probably add an expiration time that you check when you validate. We’re not going to worry about any of this for our example. Also, you would like to check if the user exists in your database before creating a token, but for simplicity, we are not using the database

Validating the JWT tokens:

const jwt = require('jsonwebtoken');
const {
AuthenticationError,
} = require('apollo-server-express');


const verifyToken = (token) => {
try {
const { email } = jwt.verify(token, "supersecret");
return { email, token };
} catch (e) {
throw new AuthenticationError(
'Authentication token is invalid, please log in',
);
}
}

Our ValidateToken function will validate the given token string. If no token is present, we’re going to respond with an error, otherwise, we’re going to parse the token using our secret.

If the token is valid we’re going to decode it and return the decoded value, otherwise, we’re going to return an error.

We will be extending out GraphQL schema, where users can obtain a token by providing the valid username and password, and validating token:

const typeDefs = gql`
type AuthPayLoad {
token: String!
email: String!
}

type Mutation {
// #1
createToken(email: String!, password: String!) : AuthPayLoad!
// #2
verifyToken(token: String!): AuthPayLoad!
}
`;

Lets walk through the comments to understand the code
  • Added createToken mutation to create a token with the given username and password
  • Added verifyToken mutation to validate a token with the given token

We also need to implement the resolver function to our newly added mutations, so lets add resolvers functions for same

const { createToken, verifyToken } = require('./auth')

const resolvers = {
Mutation: {
// #1
createToken: async (root, args, context) => {
const { email, password } = args;
return createToken(email, password);
},
// #2
verifyToken: async (root, args, context) => {
const { token } = args;
return verifyToken(token);
},
}
};

Let’s use numbered comments again to understand what’s going on here
  1. Added the resolver function for createToken, it takes email and password from args and returns a new token.
  2. Added the resolver function for verifyToken, it takes the token and validates it.


Restart your server and run the following query to create the JWT token
mutation {
createToken (
email: "mike@gmail.com",
password: "213456"
) {
token
email
}
}
It will return the JWT token and email in the result shown below
{
"data": {
"createToken": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1pa2VAZ21haWwuY29tIiwicGFzc3dvcmQiOiIyMTM0NTYiLCJpYXQiOjE1ODk2NDk4MDJ9.4mFgDfk2jHFeZex6p3SuH96cX2pceyPxoH_E0M1yeZE",
"email": "mike@gmail.com"
}
}
}

Now we have successfully added the mutation that allows the users to create a token with given email and password, which can be used for authentication purpose


To verify the token you can run the below query to see if the JWT token is valid or not, it will throw authentication error if the token is invalid
mutation {
verifyToken (
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1pa2VAZ21haWwuY29tIiwicGFzc3dvcmQiOiIyMTM0NTYiLCJpYXQiOjE1ODk2NDk4MDJ9.4mFgDfk2jHFeZex6p3SuH96cX2pceyPxoH_E0M1yeZE"
) {
token
email
}
}
. . .


Passing JWT token in HTTP Headers

Now we have the JWT token that can be used to authenticate the users. But how? How we are going pass this token to graphql resolvers to authenticate. We are going to make the following changes in our code
  • We need to make changes in our HTTP server to accept the token from the headers
  • Then pass these headers to the GraphQL server context
  • We can access the context in any resolvers and can easily validate the user
So let's edit our HTTP server code to accept the headers as follows

const server = new ApolloServer({
typeDefs,
resolvers,
// #1
context: ({ req }) => {
// Note! This example uses the `req` object to access headers,
// but the arguments received by `context` vary by integration.
// This means they will vary for Express, Koa, Lambda, etc.!
//
// To find out the correct arguments for a specific integration,
// see the `context` option in the API reference for `apollo-server`:
// https://www.apollographql.com/docs/apollo-server/api/apollo-server/
// Get the user token from the headers.
// #2
const token = req.headers.authorization || '';
// #3
// add the user to the context
return { token };
},
});

Let's walk through the comments to understand it better:
  1. We are passing the context to the apollo-server which contains the JWT token
  2. We can get the JWT token from the request headers
  3. Then the token is passed to the graphql server context
Now token can be accessed into any resolvers as follows:
verifyToken: async (root, args, context) => { const { token } = context; return verifyToken(token); }

You can pass the JWT token into the headers from the GraphQL playground as follows

You can create the token and then set it into the headers, which will sent to the graphql server with every request you fire!

The next step is to authenticate the users with the JWT token coming from the request headers.
. . .

Protecting GraphQL Query/Mutation with an Authorization token

We’ve got our JWT logic and we’ve got our resolvers in place. Now we need to authenticate the incoming request to the GraphQL server. We need to make the following changes in our Human resolver to authenticate the incoming request as follows

const { createToken, verifyToken } = require('./auth')
const resolvers = {
Query: {
// #1
async person(parent, args, context) {
const id = args.id;
// #2
const { token } = context;
// #3
const _ = verifyToken(token)

person = Persons.find(person => person.id === id);
if (person === null || this.person === undefined){
throw new UserInputError('could not find any user with given id', {
data: args
});
}
return this.person
},

},
};

Let’s use numbered comments again to understand what’s going on here
  1. We have our person resolver in place
  2. We are obtaining the JWT token from the context of the resolver function
  3. We are validating the Jwt token, it will throw authentication error is token is invalid

Let's run the below query to see the authentication in action, but don't forget to pass the valid token into the header
{
person(id: 1) {
id
name
gender
height
mass
homeworld
}
}

You will see the following response from the GraphQL server
{
"data": {
"person": {
"id": 1,
"name": "Luke Skywalker",
"gender": "male",
"height": 172,
"mass": 77,
"homeworld": "Tatooine"
}
}
}


What happens if the token is invalid? let's see

If you pass the invalid authorization token into the headers, it will fail to authenticate and will give the authentication error! So now we have successfully implemented the JWT authorization in our GraphQL API
. . .
In the next tutorial, we will add the realtime subscription functionality to the GraphQL API.


Never miss a post from Gufran Mirza, when you sign up for Ednsquare.