How to use NextAuth.js as your JWT provider with Grafbase

How to use NextAuth.js as your JWT provider with Grafbase

NextAuth.js is a popular authentication library for Next.js that comes with built-in support for dozens of providers (including GitHub, Twitter, Google, and more).

In this guide we will look at adding NextAuth.js and Grafbase to an existing Next.js application. We will use the JWT generated by NextAuth.js to pass to Grafbase to authorize requests.

If you don't already have an application with Next.js, you can follow the getting started guide.

Inside of your project root, run the following command to initialize a new GraphQL backend with the Grafbase CLI:

npx grafbase init

Then inside of the file that was created at grafbase/schema.graphql up it to contain the following schema:

type Message @model { author: String! body: String! }

Grafbase provides an agnostic authentication layer that lets you bring your own auth provider. Since NextAuth.js generated JWTs for users when they sign-in, we'll use the JWT provider.

Create the file grafbase/.env and add the following:

NEXTAUTH_SECRET=YourSuperSecret ISSUER_URL=https://grafbase.com

You will want to generate a random secret for NEXTAUTH_SECRET. You can run the following command to generate one:

openssl rand -base64 32

The ISSUER_URL can be any valid URL you like.

Now inside grafbase/schema.graphql we can configure the JWT provider using the @auth directive. We will also set the rule { allow: private } that requires users to be signed-in to make requests.

schema @auth( providers: [ { type: jwt issuer: "{{ env.ISSUER_URL }}" secret: "{{ env.NEXTAUTH_SECRET }}" } ] rules: [{ allow: private }] ) { query: Query }

You'll notice we use the special {{ env. }} syntax so we can use those environment variables created above.

Make sure to add these environment variables to your Grafbase project when you deploy to production.

Now all that's left to do is run your backend locally:

npx grafbase dev

At this point you can visit the playground at localhost:4000 to execute a GraphQL mutation to create a new Message. It will look something like this:

mutation { messageCreate( input: { author: "Grafbase Admin", body: "Grafbase is awesome!" } ) { message { id } } }

Now let's move to installing NextAuth.js and the jsonwebtoken package:

npm install next-auth jsonwebtoken

If you're using TypeScript you will want to install the @types/jsonwebtoken package as a dev dependency:

npm install @types/jsonwebtoken

Now create the file pages/api/auth/[...nextauth].ts inside of your project. If you're using the new Next.js 13 app directory, you will still want to create this file (for now):

import NextAuth, { NextAuthOptions } from 'next-auth' export const authOptions: NextAuthOptions = {} export default NextAuth(authOptions)

Next let's update the root pages/_app.tsx file to include the SessionProvider by NextAuth.js:

import { SessionProvider } from 'next-auth/react' import type { AppProps } from 'next/app' export default function App({ Component, pageProps: { session, ...pageProps }, }: AppProps) { return ( <SessionProvider session={session}> <Component {...pageProps} /> </SessionProvider> ) }

Now open or create the file .env in the root of your project and add the following:

NEXT_PUBLIC_GRAFBASE_API_URL=http://localhost:4000/graphql NEXTAUTH_SECRET= ISSUER_URL=https://grafbase.com

The NEXT_PUBLIC_GRAFBASE_API_URL is prefixed with NEXT_PUBLIC_ so it can be used client-side.

The NEXTAUTH_SECRET and ISSUER_URL should match that set inside grafbase/.env.

Let's move onto configuring the Login with GitHub functionality. You should create a new app inside of your GitHub developer settings.

The callback URL should be http://localhost:3000/api/auth/callback/github.

Then update the .env file to contain the Client ID and Client Secret provided by GitHub:

GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET=

Next we can use these environment variables by importing next-auth/providers/github and adding it to the providers array in our authOptions object:

import NextAuth, { NextAuthOptions } from 'next-auth' import GitHubProvider from 'next-auth/providers/github' export const authOptions: NextAuthOptions = { providers: [ GitHubProvider({ clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, }), ], } export default NextAuth(authOptions)

Make sure to add a button in your application to login and you will be taken to the NextAuth.js login screen that will show the button to Login with GitHub:

// pages/index.tsx import { signIn } from 'next-auth/react' export default function Home() { return ( <> <p>Not signed in<p> <button onClick={() => signIn()}>Sign in</button> </> ) }

Right now when users sign in they will be generated a generic JWT that is encoded by NextAuth.js but we would like to customize the token so it works with the Grafbase JWT provider.

To do this we will provide our own encode and decode methods for the jwt property of authOptions.

We will use the library jsonwebtoken that we installed previously to sign and verify tokens. When we sign a new token we will pass the iss and exp values.

import jsonwebtoken from 'jsonwebtoken' import NextAuth, { NextAuthOptions } from 'next-auth' import { JWT } from 'next-auth/jwt' import GitHubProvider from 'next-auth/providers/github' export const authOptions: NextAuthOptions = { providers: [ GitHubProvider({ clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, }), ], jwt: { encode: ({ secret, token }) => { const encodedToken = jsonwebtoken.sign( { ...token, iss: process.env.ISSUER_URL, exp: Math.floor(Date.now() / 1000) + 60 * 60, }, secret, ) return encodedToken }, decode: async ({ secret, token }) => { const decodedToken = jsonwebtoken.verify(token!, secret) return decodedToken as JWT }, }, } export default NextAuth(authOptions)

Now NextAuth.js knows how to encode and decode JWTs in the same format Grafbase is expecting we can now use that token to send with each GraphQL request.

One of the benefits of using NextAuth.js is how it handles storing JWTs. The raw JWT isn't something that's saved in your browser that can be used over and over as NextAuth.js securely hides and handles those for us.

Instead we will create a Next.js API route that returns the token we can then pass on. We must hit this endpoint before every request to get a new valid JWT using the getToken method from NextAuth.js.

Create the file pages/api/auth/token.ts and add the following:

import { NextApiRequest, NextApiResponse } from 'next' import { getToken } from 'next-auth/jwt' const secret = process.env.NEXTAUTH_SECRET export default async function handler( req: NextApiRequest, res: NextApiResponse, ) { const token = await getToken({ req, secret, raw: true }) res.json({ token }) }

Now all that's left to do is hook up your GraphQL client to invoke the new /api/auth/token endpoint before every operation.

We'll use fetch in this example and create the helper fetchToken for the purposes of this guide:

const fetchToken = async () => await fetch('/api/auth/token').then(res => res.json())

Then wherever you want to query you can invoke fetchToken and pass the value of that to your fetch. We will use the query automatically generated by Grafbase to fetch all Message's:

const fetchMessages = async () => { const { token } = await fetchToken() return fetch(process.env.NEXT_PUBLIC_GRAFBASE_API_URL!, { method: 'POST', headers: { Authorization: `Bearer ${token}`, }, body: JSON.stringify({ query: /* GraphQL */ ` { messageCollection(first: 100) { edges { node { id } } } } `, }), }) .then(res => res.json()) .catch(err => console.log(err)) }

That's it! All requests will now be authenticated using the JWT generated by NextAuth.js after you successfully login.

Unauthenticated requests will not be permitted because of the auth rule we configured with Grafbase.

Get Started

Use NextAuth.js with Grafbase as your JWT provider.