Graphql server authentication with JWT
Tags: graphql, tutorial, webdev, beginners
Published: 2020-02-19 14:23:45



Welcome, we will continue working on our [Graphql to-do API](https://dev.to/ahmdtalat/build-a-todo-graphql-api-with-node-js-1iaa), by setting up authorization for our app using JWT.

## Getting Started

This tutorial will be focusing on:

- What is JWT and how to use it
- Hashing the password with bcryptjs
- How to control who can interact with the data

## Step 1: Install dependencies

You'll need to install `jsonwebtoken` and `bcryptjs` by running the following in the terminal:

```zsh
yarn add jsonwebtoken bcryptjs
```

or using npm

```bash
npm i jsonwebtoken bcryptjs
```

**So, what are JSON Web Tokens and bcryptjs, and Why?**

1. JWT

   > JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. [_more info_](https://tools.ietf.org/html/rfc7519). passing jwt tokens will take this form![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/y1gccnudv1wnykb52k16.png)

2. bcryptjs

   > Is a way to hash the user info before hitting the DB. and why hashing? just a safety precaution.

## Step 2: Re-organize project structure

Our app is growing and we need a proper structure to avoid confusion.

So, we'll put resolvers and typeDefs both in separate folders and create an `index.js` in each one. then you can invoke them as Node modules. `const resolvers = require('./resolvers')`.

**_index.js will look something like this_**

- `typeDefs/index.js`

```js
const typesDefs = require('./typeDefs');
module.exports = typesDefs;
```

- `resolvers/index.js`

```js
const todosResolvers = require('./todos');
const userResolvers = require('./user'); // Will create this file later.
module.exports = {
  Query: {
    ...todosResolvers.Query
  },
  Mutation: {
    ...todosResolvers.Mutation,
    ...userResolvers.Mutation
  }
};
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/xhmiaf32pz5byp9n7xil.png)

## Step 3: Create a User model

We need a new MongoDB collection for the users.

```js
const { Schema, model } = require('mongoose');

const userSchema = new Schema({
  username: String,
  password: String,
  email: String,
  created: String
});

module.exports = model('User', userSchema);
```

And we need to update our Todo model as well.

```js
const todoSchema = new Schema({
  text: String,
  created: String,
  username: String, // who created it
  user: {
    // referencing the User docutment
    type: Schema.Types.ObjectId,
    ref: 'users'
  }
});
```

## Step 4: Create a Schema and resolvers for User

1. **we will update our typeDefs**

```js
const { gql } = require('apollo-server');

module.exports = gql`
  type Todo {
    id: ID!
    username: String! // new! add a username
    text: String!
    created: String!
  }
  // new! types of user data
  type User {
    id: ID!
    username: String!
    email: String!
    created: String!
    token: String!
  }
  // new! we use input instead of type
  // to pass the whole user info as an object
  input RegisterUserInput {
    username: String!
    password: String!
    confirmPassword: String!
    email: String!
  }
  type Query {
    getTodos: [Todo]
  }
  type Mutation {
    createTodo(text: String!): Todo!
    deleteTodo(todoId: ID!): String!
    // new! A user needs to be logged in before changing data.
    LoginUser(username: String!, password: String!): User!
    RegisterUser(user: RegisterUserInput!): User!
  }
`;
```

2. **update the ApolloServer parameters**

**[Per the documentation](https://www.apollographql.com/docs/apollo-server/security/authentication/#putting-user-info-on-the-context)**: there are a number of ways to handle authentication of users. And we'll be using a token in an HTTP _authorization_ header.

```js
const server = new ApolloServer({
  typeDefs,
  resolvers,
  // a () => {} to build our context object.
  context: ({ req }) => {
    // get the authorization from the request headers
    // return a context obj with our token. if any!
    const auth = req.headers.authorization || '';
    return {
      auth
    };
  }
});
```

> _The **context** object is one that gets passed to every single resolver at every level, so we can access it anywhere in our schema code_.

3. **create the user resolvers**

In our case, We'll be creating two resolvers. Login & Register.

_Register takes a userinput (username, pass,confirmPass, email) and returns the user info with a token._

But, First, We need import jsonwebtoken and bcrypt, So if the userinput is valid ( non-empty fields - some basic validation ), then `hash` the password, generate a token for that user.

```js
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');

const {
  UserInputError, // Throw an error if empty fields.
  AuthenticationError
} = require('apollo-server');

const User = require('../models/User');
// some string as a secret to generate toekns.
const { SECRET } = require('../config');

const getToken = ({ id, username, email }) =>
  jwt.sign(
    {
      id,
      username,
      email
    },
    SECRET,
    { expiresIn: '1d' }
  );

module.exports = {
  Mutation: {
    async LoginUser(_, { username, password }) {
      // validateLogin is a simple func that checks for empty fields
      // and return valid = false if any.
      const { errors, valid } = validateLogin(username, password);
      if (!valid) throw new UserInputError('Error', { errors });

      // check if that user already exists.
      const user = await User.findOne({ username });
      if (!user) throw new AuthenticationError('this user is not found!');

      const match = await bcrypt.compare(password, user.password);
      if (!match) throw new AuthenticationError('wrong password!');

      const token = getToken(user); // generate a token if no erros.
      return {
        id: user._id, // set an id
        ...user._doc, // spread the user info (email, created at, etc)
        token
      };
    },

    async RegisterUser(
      _,
      { user: { username, password, confirmPassword, email } }
    ) {
      const { errors, valid } = validateRegister(
        username,
        password,
        confirmPassword,
        email
      );
      if (!valid) throw new UserInputError('Error', { errors });

      const user = await User.findOne({ username });
      if (user) throw new ValidationError('This username is not valid!');

      password = await bcrypt.hash(password, 10); // hashing the password
      const newUser = new User({
        username,
        password,
        email,
        created: new Date().toISOString()
      });
      const res = await newUser.save();
      const token = getToken(res);

      return {
        id: res._id,
        ...res._doc,
        token
      };
    }
  }
};
```

## _Finally_

Now, We'll update the todo resolvers (createTodo - deleteTodo), So We can control who can create and delete todos.

```js
// Simply take an auth header and returns the user.
const getUser = async auth => {
  if (!auth) throw new AuthenticationError('you must be logged in!');

  const token = auth.split('Bearer ')[1];
  if (!token) throw new AuthenticationError('you should provide a token!');

  const user = await jwt.verify(token, SECRET, (err, decoded) => {
    if (err) throw new AuthenticationError('invalid token!');
    return decoded;
  });
  return user;
};


// The mutation takes 3 args (parent,args,context)
// in our case, we don't need parent
// destructure the todo text from args, and auth from context.
async createTodo(_, { text }, { auth }) {
      const user = await getUser(auth);
      if (user) {
        try {
          const newTodo = new Todo({
            user: user.id,
            username: user.username,
            text,
            created: new Date().toISOString()
          });
          const todo = await newTodo.save();
          return todo;
        } catch (err) {
          throw new Error(err);
        }
      }
    },
    async deleteTodo(_, { todoId }, { auth }) {
      const user = await getUser(auth);
      try {
        const todo = await Todo.findById(todoId);
        if (todo) {
          if (todo.username === user.username) await todo.delete();
          else
            throw new AuthenticationError(
              `you're not allowed to delete this todo!!`
            );
        } else throw new Error('todo is not found');
        return 'Todo deleted!';
      } catch (err) {
        throw new Error(err);
      }
    }
  }
```

That's it, I hope you find it easy and simple. If you have any questions, please let me know.

_The cover image is from [undraw.co](https://undraw.co/illustrations)._