Authentication in Prisma - Part 2: JSON Web Tokens & Login

Joshua Hall

Over in Part 1 we setup our project to require authentication to interact with the GraphQL API. Now, we’re going to look at logging in users and generating JSON Web Tokens (JWT) for our users to lock them out from data we don’t want them to access.

Installation

We’re going to be using bcrypt to encrypt our passwords, since storing passwords as just normal strings is very bad for security, and jsonwebtoken to generate tokens we can use to verify if a user should have access to something.

$ npm install bcryptjs jsonwebtoken
Recommended React and GraphQL course

Encryption

Let’s take a moment to go over the two libraries we’ll be using. If you’re already familiar with them then you can skip this as well as the next section.

bcrypt allows us to encrypt our passwords and compare that hashed version with another string to see if they’re the same. The point of this is that you can compare the hashed and un-hashed versions but you can never decode the original from it. This way, even if someone were to query for a users password, although they would get something, it wouldn’t be usable to break into anyone’s account.

It’s pretty simple, it just takes the string we would like to hash, and the number of salts. A salt is just a cycle that it runs the password through its hashing algorithm, the higher the number the more secure it’ll be but the longer it’ll take to generate.

import bcrypt from 'bcryptjs';

const password = 'pleaseDontHackMe3248';
console.log('Raw Password: ', password);

bcrypt.hash(password, 8)
  .then(hashed => {
    console.log('Secure Password: ', hashed);

    // Must take the string version then the hashed version
    const doesMatch = bcrypt.compare(password, hashed);

    return doesMatch;
  }
  ).then(doesMatch => console.log('Password Matches: ', doesMatch));

Generating Tokens

jsonwebtoken is an extremely popular library for generating unique access tokens that we can use to verify for authenticity and even have expire over some period of time.

jwt.sign takes in something to link the token to a particular user and a secret that we’ll use to verify if the token is valid, since anyone could generate their own outside of our app. We can set the expiration with the expiresIn property and a string describing the time span, such as 5000 (milliseconds), '8d', or '9 months'.

const token = jwt.sign({ name: 'Someone' }, 'anotherSecret', { expiresIn: '1 day' });
console.log(token);

Now we can use jwt.verify to use our secret to decrypt our user’s data that was stored. You’d normally be storing this secret as an environment variable of course.

const decrypted = jwt.verify(token, 'anotherSecret');
console.log(decrypted);

Creating a User

Creating a login system is a very simple process. All we need to do is give every user a token when they run the login or createUser mutations, which would normally be saved and sent back from the client side, but we’ll stick with GraphQL Playground’s header for now.

Let’s start with adding some mutations and generating a token for our user. Since a user will only sometimes need a token, we can leave it nullable.

schema.graphql

type Mutation {
  createUser(data: CreateUserInput): User!
  loginUser(email: String!, password: String!): User!
}

type User {
  id: ID!
  name: String!
  email: String!
  password: String! 
  token: String
}

input CreateUserInput {
  name: String! 
  email: String!
  password: String!
}

With our schema in place, let’s try to create a new user with a valid token.

resolvers.

const expiresIn = '1 day'; // We'll be using this value repeatedly.

const Mutation = {
  async createUser(parent, { data }, { prisma }, info) {
    const password = await bcrypt.hash(data.password, 10);
    const user = await prisma.mutation.createUser({
      data: {
        ...data,
        password
      }
    });
    // Since id is added by prisma it is unavailable when creating a user.
    const userWithToken = {
      ...user,
      token: jwt.sign({ userId: user.id }, process.env.TOKEN_SECRET, { expiresIn })
    };

    return userWithToken;
  }
}

Finally, just add our secret to our environment variables, which by now, should look like this.

.env

API_SECRET=SuperSecretSecret
TOKEN_SECRET=EvenSecreterSecret

Logging in a User

The last step for now is to use the user’s email to get their account and compare their encoded password with the one they entered. If it’s correct, then we’ll generate and return a new token for them.

resolvers.js

const expiresIn = '1 day';

const Mutation = {
  async createUser(parent, { data }, { prisma }, info) {...},
  async loginUser(parent, { email, password }, { prisma }, info) {
    const user = await prisma.query.user({ where: { email } });
    if (!user) throw new Error('No User Found');

    const isValid = await bcrypt.compare(password, user.password);
    if (!isValid) throw new Error('Wrong Password');

    const userWithToken = {
      ...user,
      token: jwt.sign({ userId: user.id }, process.env.TOKEN_SECRET, { expiresIn })
    }

    return userWithToken
  }
}

Stay tuned for part 3, where I’ll cover validation for queries, mutations and subscriptions!

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#