Authentication
In this section, you're going to implement signup and login functionality that allows your users to authenticate against your GraphQL server.
Adding a User Model
The first thing you need is a way to represent user data in the database. To do so, you can add a User type to your Prisma data model.
You'll also want to add a relation between the User and the existing Link type to express that Links are posted by Users.
Open prisma/schema.prisma and add the following code, making sure to also update your existing Link model accordingly:
model Link {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
description String
url String
postedBy User? @relation(fields: [postedById], references: [id])
postedById Int?
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
password String
links Link[]
}Now you see even more how Prisma helps you to reason about your data in a way that is more aligned with how it is represented in the underlying database.
Understanding relation fields
Notice how you're adding a new relation field called postedBy to the Link model that points to a User instance. The User model then has a links field that's a list of
Links.
To do this, you need to also define the relation by annotating the postedBy field with
the @relation attribute (opens in a new tab). This is required for every relation field in
your Prisma schema, and all you're doing is defining what the foreign key of the related table will be. So in this case, we're adding an extra field to store the id of the User who posts a Link, and then telling Prisma that postedById will be equal to the id field in the User table (if you are familiar with SQL, this kind of relation is being represented as one-to-many).
If this is quite new to you, don't worry! We're going to be adding a few of these relational fields and you'll get the hang of it as you go! For a deeper dive on relations with Prisma, check out these docs (opens in a new tab).
Updating Prisma Client
This is a great time to refresh your memory on the workflow we described for your project at the end of chapter 4!
After every change you make to the data model, you need to migrate your database and then re-generate Prisma Client.
In the root directory of the project, run the following command:
npx prisma migrate dev --name "add-user-model"This command has now generated your second migration inside of prisma/migrations, and you can start to see how this becomes a historical record of how your database evolves over
time. This script also run the Prisma migration, so your new models and types are ready-to-use.
That might feel like a lot of steps, but the workflow will become automatic by the end of this tutorial!
Your database is ready and Prisma Client is now updated to expose all the CRUD queries for the newly added User model – woohoo! 🎉
Extending the GraphQL schema
Remember back when we were setting up your GraphQL server and discussed the process of schema-driven development? It all starts with extending your schema definition with the new
operations that you want to add to the API - in this case a signup and login mutation.
Open the application schema in src/schema.ts and update schema types as follows:
type Query {
hello: String!
feed: [Link!]!
}
type Mutation {
post(url: String!, description: String!): Link!
signup(email: String!, password: String!, name: String!): AuthPayload
login(email: String!, password: String!): AuthPayload
}
type Link {
id: ID!
description: String!
url: String!
}
type AuthPayload {
token: String
user: User
}
type User {
id: ID!
name: String!
email: String!
links: [Link!]!
}The signup and login mutations behave very similarly: both return information about the User who's signing up (or logging in) as well as a token which can be used to
authenticate subsequent requests against your GraphQL API. This information is bundled in the AuthPayload type.
Finally, you need to reflect that the relation between User and Link should be bi-directional by adding the postedBy field to the existing Link model definition in
schema.ts:
type Link {
id: ID!
description: String!
url: String!
postedBy: User
}Implementing the resolver functions
After extending the schema definition with the new operations, you need to implement resolver functions for them.
Setup for authentication
In this tutorial, you will implement simple, naive implementation of a JWT (Json Web Token) implementation. This is a simple solution for creating token-based authentication.
You'll also use bcryptjs for simple encryption for the user's password.
Start by installing jsonwebtoken library from NPM:
yarn add --save-exact jsonwebtoken bcryptjsAnd to get better integration with TypeScript, you need to install the typing libraries:
yarn add -D --save-exact @types/jsonwebtoken @types/bcryptjsCreate a new file called src/auth.ts, and for now just app a simple variable to hold our signing secret (you'll later use that as the base for our encryption):
export const APP_SECRET = 'this is my secret'Implementing signup resolvers
Open src/schema.ts and add the new signup resolver, under Mutation:
// ... other imports ...
import { APP_SECRET } from './auth'
import { hash } from 'bcryptjs'
import { sign } from 'jsonwebtoken'
const resolvers = {
// ... other resolver maps ...
Mutation: {
// ... other Mutation field resolvers ...
async signup(
parent: unknown,
args: { email: string; password: string; name: string },
context: GraphQLContext
) {
// 1
const password = await hash(args.password, 10)
// 2
const user = await context.prisma.user.create({
data: { ...args, password }
})
// 3
const token = sign({ userId: user.id }, APP_SECRET)
// 4
return { token, user }
}
}
}Let's use the good ol' numbered comments again to understand what's going on here – starting with signup.
- In the
signupmutation, the first thing to do is encrypt theUser's password using thebcryptjslibrary which you'll install soon. - The next step is to use your
PrismaClientinstance (viaprismaas we covered in the steps aboutcontext) to store the newUserrecord in the database. - You're then generating a JSON Web Token which is signed with an
APP_SECRET. You still need to create thisAPP_SECRETand also install thejwtlibrary that's used here. - Finally, you return the
tokenand theuserin an object that adheres to the shape of anAuthPayloadobject from your GraphQL schema.
You can now open GraphiQL and play around with your new schema and resolvers, try to run the following mutations:
mutation {
signup(email: "test@mail.com", name: "Dotan Simha", password: "123456") {
token
user {
id
name
email
}
}
}
Implementing login resolvers
Now, the login mutation, add it under the signup resolvers.
Add the following right under the signup mutation:
// ... other imports ...
import { hash, compare } from 'bcryptjs'
const resolvers = {
// ... other resolver maps ...
Mutation: {
// ... other Mutation field resolvers ...
async login(
parent: unknown,
args: { email: string; password: string },
context: GraphQLContext
) {
// 1
const user = await context.prisma.user.findUnique({
where: { email: args.email }
})
if (!user) {
throw new Error('No such user found')
}
// 2
const valid = await compare(args.password, user.password)
if (!valid) {
throw new Error('Invalid password')
}
const token = sign({ userId: user.id }, APP_SECRET)
// 3
return { token, user }
}
}
}And if you'll open Playground, you should be able to login with the user you previously created:
mutation {
login(email: "test@mail.com", password: "123456") {
token
user {
id
name
email
}
}
}
You should be able to get the information of the user.
Please save the authentication token you get, we'll need that on the next step!
Now on the login mutation!
- Instead of creating a new
Userobject, you're now using yourPrismaClientinstance to retrieve an existingUserrecord by theemailaddress that was sent along as an argument in theloginmutation. If noUserwith that email address was found, you're returning a corresponding error. - The next step is to compare the provided password with the one that is stored in the database. If the two don't match, you're returning an error as well.
- In the end, you're returning
tokenanduseragain.
Detecting the current user
Now, you have our users' database ready to use, and our next step is to be able to detect who's the current user that queries the server.
To do that, you'll need to add the option to pass the authentication token along with our GraphQL operation.
You are not going to use the GraphQL schema in this case, since you don't want to mix the authentication flow with the GraphQL contract that you have. So you'll use HTTP headers.
The authentication token will be passed as a HTTP header, in the following form:
Authorization: "Bearer MY_TOKEN_HERE"To add support for this kind of authentication in our server, you'll need to be able to access the raw incoming HTTP request, then verify the token and identify the current user.
You also want to be able to tell who's the current authenticated user within our resolvers, so you'll inject the current user into the GraphQL context.
So let's do that:
You'll now modify the context building phase of your GraphQL server, by detecting the current authenticated user. Use the following code in src/auth.ts and add a function for that:
import { PrismaClient, User } from '@prisma/client'
import { FastifyRequest } from 'fastify'
import { JwtPayload, verify } from 'jsonwebtoken'
export const APP_SECRET = 'this is my secret'
export async function authenticateUser(
prisma: PrismaClient,
request: FastifyRequest
): Promise<User | null> {
const header = request.headers.get('authorization')
if (header !== null) {
// 1
const token = header.split(' ')[1]
// 2
const tokenPayload = verify(token, APP_SECRET) as JwtPayload
// 3
const userId = tokenPayload.userId
// 4
return await prisma.user.findUnique({ where: { id: userId } })
}
return null
}So what happened here?
- Take the
Authorizationfor the incoming HTTP request headers. - Use
verifyofjsonwebtokento check that the token is valid, and extract theuserIdfrom the token payload. - Use Prisma API to fetch the user from the database.
- Return the current user, or
nullin case of missing/invalid token.
Now, modify your createContext function in src/context.ts function to call this function:
import { PrismaClient, User } from '@prisma/client'
import { YogaInitialContext } from '@graphql-yoga/node'
import { authenticateUser } from './auth'
const prisma = new PrismaClient()
export type GraphQLContext = {
prisma: PrismaClient
currentUser: null | User
}
export async function createContext(
initialContext: YogaInitialContext
): Promise<GraphQLContext> {
return {
prisma,
currentUser: await authenticateUser(prisma, initialContext.request)
}
}Now, every incoming GraphQL request that has a valid token and a user, will also have the context.currentUser available with the authenticated user details. If an incoming request doesn't have that, the context.currentUser will be set to null.
So that's really cool, and to test that, you can add a new GraphQL field under type Query called me that just exposes the current user information.
Start by adding the me field to the GraphQL schema under Query:
type Query {
hello: String!
feed: [Link!]!
me: User!
}And then implement the resolver for this new field:
const resolvers = {
// ... other resolver maps ...
Query: {
// ... other Query Object Type field resolver functions ...
me(parent: unknown, args: {}, context: GraphQLContext) {
if (context.currentUser === null) {
throw new Error('Unauthenticated!')
}
return context.currentUser
}
}
}You can now try it in GraphiQL with the following query:
query {
me {
id
name
}
}And under the HEADERS section of GraphiQL, add your authentication token in the following structure:
{
"Authorization": "Bearer YOUR_TOKEN_HERE"
}And if you'll run it, you'll see that the GraphQL server now being able to authenticate you based on the token!

Connecting other resolvers
If you remember, you added more new fields to the GraphQL schema (such as Link.postedBy), so let's implement the missing resolvers!
To make sure our server knows how to identify the creator of each Link, let's modify the resolver of Mutation.post to ensure that only authenticated users can use it, and also add the current authenticated user id to the object created on our database.
Protecting resolvers
Let's go and finish up the implementation, and connect everything together with the rest of the resolvers.
Modify src/schema.ts and change the resolver of post field to the following:
const resolvers = {
Mutation: {
async post(
parent: unknown,
args: { url: string; description: string },
context: GraphQLContext
) {
if (context.currentUser === null) {
throw new Error('Unauthenticated!')
}
const newLink = await context.prisma.link.create({
data: {
url: args.url,
description: args.description,
postedBy: { connect: { id: context.currentUser.id } }
}
})
return newLink
}
}
}You can now try it from GraphiQL:
mutation {
post(
url: "www.graphqlconf.org"
description: "An awesome GraphQL conference"
) {
id
}
}
Resolving relations
There's one more thing you need to do before you can launch the GraphQL server again and test the new functionality: ensuring the relation between User and Link gets properly
resolved.
To resolve the postedBy relation, open src/schema.ts and add the following code to your resolvers:
const resolvers = {
Link: {
id: (parent: Link) => parent.id,
description: (parent: Link) => parent.description,
url: (parent: Link) => parent.url,
postedBy(parent: Link, args: {}, context: GraphQLContext) {
if (!parent.postedById) {
return null
}
return context.prisma.link
.findUnique({ where: { id: parent.id } })
.postedBy()
}
}
}In the postedBy resolver, you're first fetching the Link from the database using the prisma instance and then invoke postedBy on it. Notice that the resolver needs to be
called postedBy because it resolves the postedBy field from the Link type in our type-definitions.
You can resolve the links relation in a similar way.
In src/schema.ts, add a field resolvers for User.links to your resolvers variable:
// ... other imports ...
import { Link, User } from '@prisma/client'
const resolvers = {
// ... other resolver maps ...
User: {
// ... other User object type field resolver functions ...
links: (parent: User, args: {}, context: GraphQLContext) =>
context.prisma.user.findUnique({ where: { id: parent.id } }).links()
}
}That's all! Now you have resolvers for all fields, and you can sign up, login, identify the user as part of our GraphQL server!
You should be able to run complex GraphQL queries, for example:
query {
feed {
id
description
url
postedBy {
id
name
}
}
}