GraphQL HTTP-only JWT Authentication with Next.js
Lately, I've been working on building one of the app challenges on devchallenges.io.
I decided to use Next.js with GraphQL as my stack. I was a little worried about how I would implement secure HTTP-only authentication but it turned out to be super simple! Let me show you how.
Starting off we'll use a basic graphql API route adapted from the next.js example
1import { ApolloServer, gql } from 'apollo-server-micro'23const typeDefs = gql`4 type Query {5 me: User6 }78 type Mutation {9 signup(username: String!, password: String!): User10 }1112 type User {13 username: String!14 }15`1617const resolvers = {18 Query: {19 me(_parent, _args, context) {20 // what do we do here?21 },22 },2324 Mutation: {25 signup(_parent, {username, password}, context) {26 // ??27 },28 }2930}3132const apolloServer = new ApolloServer({ typeDefs, resolvers })3334export const config = {35 api: {36 bodyParser: false,37 },38}3940export default apolloServer.createHandler({ path: '/api/graphql' })
Here's where the fun begins.
We'll import
and1jsonwebtoken
(make sure you add them to your dependencies!):1cookies
1import jwt from "jsonwebtoken";2import Cookies from "cookies";
Then we'll add a context within the apollo server where we'll create a cookie jar to set and get cookies within our resolves and parse our JWT token (if we have it).
1const verifyToken = (token) => {2 if (!token) return null;3 try {4 return jwt.verify(token, process.env.SECRET!);5 } catch {6 return null;7 }8};91011const apolloServer = new ApolloServer({12 typeDefs,13 resolvers,14 context: ({ req, res }) => {15 const cookies = new Cookies(req, res);16 const token = cookies.get("auth-token");17 const user = verifyToken(token);18 return {19 cookies,20 user,21 };22 },23});
Now in our resolvers, we can set the cookie when a user signs up (and signs in, but I'll let you figure that out):
1const resolvers = {2 // ...3 Mutation: {4 async signup(_parent, {username, password}, context) {5 let hash = await bcrypt.hash(password, 10);6 // bring your own db logic7 let user = await db.createUser({username, password: hash})8910 let token = jwt.sign({ id: user.id }, process.env.SECRET!);11 context.cookies.set("auth-token", token, {12 httpOnly: true,13 sameSite: "lax",14 // here we put 6 hours, but you can put whatever you want (the shorter the safer, but also more annoying)15 maxAge: 6 * 60 * 60,16 secure: process.env.NODE_ENV === "production",17 });18 return user;19 },20 }21}
Now, whenever a request is made to check our auth status, it's easy!
1const resolvers = {2 Query: {3 me(_parent, _args, context) {4 // bring your own db logic5 context.user?.id ? db.findUser(context.user.id) : null6 },7 },8}
That should be enough to get you started 😄