Authentication with GraphQL & Prisma

avatar
Marshal Murphy
May 26, 2020 - 6 MIN READ

In a previous article, I guided us through setting up a modern backend app using the following tech:

  • GraphQL: database query language.
  • Prisma: an auto-generated type-safe query builder.
  • PostgreSQL database on Heroku
  • Docker: to connect our local app to the Heroku database.
  • NodeJS: serve our backend app.

Today we take the plunge into practically learning GraphQL queries and mutations as we build out a Signup authentication flow for this app.

This will involve creating an asynchronous resolver mutation for signing up a new user and logging them in, encrypting their password, and generating a token stored in context for the session.

To get started, pull down the repo we left off from right here or skip to the end for final repo.

Feature

Previously on…

Let’s start where we left off.

The last thing we did was run a query for users.

This query returned an empty array because we have yet to write mutations for creating a new User.

However, returning an empty array confirmed that our setup succeeded. Otherwise, we would have received an error.

Change into your project root directory (from the repo linked to above) and start the app.

> npm start

Server is running on http://localhost:4000

Make sure that your Docker Desktop is running. If it doesn’t start when you turn your computer and log in, then simply opening docker from applications will do the trick.

At localhost:4000, test that our setup still works by running that users query once more.

graphiql

Perfect! That’s what we want to see!


User Model

Last time we were here, we created a User model so that we could represent user data in our database.

This model is declared by type, and must contain an id and name.

Let's take a brief moment to examine the anatomy of these type declaration lines:

ids

When creating a model in datamodel.prisma, we need to declare the fields that belong to it in the database so that we can retrieve the data.

In the above line from the User model, we are saying that:

  • User will have an id field.
  • The id is of the type ID.
  • It must be present (!)
  • Assign the @id directive to create a unique ID whenever creating a new User.

Similarly, the name field must return a String.

Since we plan on authenticating this user, let’s add two new fields to the User model: email & password.

Here we are introducing a new directive on email, @unique.

When a request to create a new user is submitted, if there are any other users with the same email, the request will be denied.

Now, to apply these changes to our underlying database schema, shut down the server (control + c) and redeploy prisma.

> prisma deploy

ids


Signup Mutation: Schema

With our User model and the underlying schema ready to accept email and password parameters, it's time we write our first mutation!

In src/schema.graphql, create a mutation type with our soon-to-follow signup mutation:

Again notice that we declare the fields, their types, and if they are required (!).

Additionally, let’s add that AuthPayload type, and update the User type to accept an email and password.

Bringing it all together:


Signup Mutation: Resolver

With the schema in place, we can create a resolver for our signup function.

But first we will need to install a couple more dependencies for encrypting passwords and generating tokens:

> yarn add jsonwebtoken bcryptjs

Next, create a new file in the resolvers directory alongside Query.js called Mutation.js.

In it, add the following:

This function is similar to the users Query, in that it accepts values for parent, args, context, info; except, you know, that it's not a Query, but a Mutation!

Let’s walk through what happens when the UI triggers this signup mutation.

  1. We use bcrypt to create a hash of the password, which comes in as part of the args object.
  2. We use the method createUser (auto-generated by prisma and located in prisma-schema.js), passing in all args and the hashed password. We await successful creation and store this in user.
  3. We create a token using jsonwebtoken to later store in our app's context (from index.js), which we can reference to detect if the user is currently signed in.

Lastly, we need to add our Signup mutation to our resolvers in index.js:


Running the Signup Mutation

With all of the above in place, start your server and head over to localhost:4000.

In the docs tab to the right you will now see our Signup mutation, as well as our updated schema.

Time to sign up a new user!

In the editor, send the following mutation (with your own values 😉🐶):

mutation {
  signup(
    name: "Chase"
    email: "ismydogsname@gmail.com"
    password: "woof"
  ) {
    token
    user {
      id
    }
  }
}

And the response from our server…

graphiql

SUCCESS!

Our mutation created a new user, returning the logged in token and confirmed that the new user exists by also returning the id.

How about we take a look at the user we just created.

Open a new tab in the editor and send off a query for users.

graphiql

How about that nice encrypted password, eh?

Lookin’ good!


Homework

Now, do you think you can you write a resolver function to log in a user that is already signed up?

I’ll help you out with a couple hints.

  1. The function will be quite similar to signing up a user.
  2. Remember that emails are unique? You will want to check if the email exists on User first: context.prisma.user({ email: args.email }).
  3. If it does, use bcrypt to compare the password in args with that of user: bcrypt.compare(args.password, password).
  4. Finally, generate and return a token the exact same way we did before.

Conclusion

Great work following along! I hope using practical examples has helped you get a better grasp on how GraphQL and Prisma work.

Here is a brief recap of what we did today:

  • Added fields for email and password to our User model in datamodel.prisma and updated the underlying Prisma database schema by running > prisma deploy.
  • Extended our GraphQL schema to include the new User fields, AuthPayload, and the Signup mutation.
  • Wrote our signup resolver function, implementing jsonwebtoken for generating the signed-in token, and bcryptjs for encrypting the user password.
  • Tested our signup mutation in the Prisma playground.

Great job!

Here is the Final Project Repo if you experienced any issues.

Until next time 😉

Marshal Murphy 2020