Tutorial

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

Updated on October 13, 2020
Default avatar

By Joshua Hall

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

This tutorial is out of date and no longer maintained.

Warning: This tutorial is out of date and no longer maintained. It is based on Prisma 1 which is not actively developed any more. To learn everything about the latest version of Prisma, visit the official Prisma documentation.

If you want to learn how to build server-side applications with Prisma, you can follow these new tutorials:

Introduction

In part 1, we set up 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

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 user’s password, although they would get something, it wouldn’t be usable for breaking into anyone’s account.

It takes the string we would like to hash and the number of salts. A salt is a cycle that runs the password through its hashing algorithm; the higher the number of salts, 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.

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 by 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 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
  }
}

Conclusion

In this tutorial, you explored how to log in users and generate JWTs. In part 3 of this series, we will cover validation for queries, mutations, and subscriptions.

Warning: This tutorial is out of date and no longer maintained. It is based on Prisma 1 which is not actively developed any more. To learn everything about the latest version of Prisma, visit the official Prisma documentation.

If you want to learn how to build server-side applications with Prisma, you can follow these new tutorials:

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Joshua Hall

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel