Next.js Email Authentication using NextAuth

By

Learn how to add email-based authentication to Next.js with NextAuth.js, from configuring an SMTP email provider and JWT sessions to the useSession hook.

~~~

Managing authentication in Next.js can be done in many different ways.

In my site I chose to implement email-based authentication with JWT tokens via NextAuth.js and here’s how I did it.

This guide uses NextAuth.js v4 (next-auth@4.24.15) with the Pages Router (pages/api/auth/[...nextauth].js). Auth.js / NextAuth v5 is still a different API surface, so stay on v4 for this setup.

An external database is needed. You can use a local database, or a cloud one. I chose PostgreSQL but you can use anything you want.

I suppose you already have a Next.js website up.

Run npm install next-auth@4 nodemailer pg to install NextAuth v4, Nodemailer (used by the Email provider), and the PostgreSQL library.

Then add to your .env file:

DATABASE_URL=<enter URL of the postgresql:// database>
EMAIL_SERVER=smtp://user:pass@smtp.mailtrap.io:465
EMAIL_FROM=Your name <you@email.com>
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=

Make sure you add a NEXTAUTH_SECRET value. You can use https://generate-secret.vercel.app/32 to generate it. In v4 this replaces the old plain SECRET env name for signing/encryption.

I use https://mailtrap.io to test the emails, it’s quite handy while you are setting things up.

Create a pages/api/auth/[...nextauth].js file with this content:

import NextAuth from 'next-auth'
import EmailProvider from 'next-auth/providers/email'

export const authOptions = {
  providers: [
    EmailProvider({
      server: process.env.EMAIL_SERVER,
      from: process.env.EMAIL_FROM,
    }),
  ],

  secret: process.env.NEXTAUTH_SECRET,

  session: {
    strategy: 'jwt',
    maxAge: 30 * 24 * 60 * 60, // 30 days
  },

  debug: true,
}

export default NextAuth(authOptions)

The Email provider needs a database adapter to store verification tokens. The old v3 database: process.env.DATABASE_URL shortcut is gone in v4.

Now it depends a a lot on your data access layer. If you use the Prisma ORM, also install @next-auth/prisma-adapter with

npm install @next-auth/prisma-adapter @prisma/client
npm install -D prisma

and include it in [...nextauth].js:

import NextAuth from 'next-auth'
import EmailProvider from 'next-auth/providers/email'
import { PrismaAdapter } from '@next-auth/prisma-adapter'
import prisma from 'lib/prisma'

export const authOptions = {
  adapter: PrismaAdapter(prisma),
  providers: [
    EmailProvider({
      server: process.env.EMAIL_SERVER,
      from: process.env.EMAIL_FROM,
    }),
  ],

  secret: process.env.NEXTAUTH_SECRET,

  session: {
    strategy: 'jwt',
    maxAge: 30 * 24 * 60 * 60, // 30 days
  },

  debug: true,
}

export default NextAuth(authOptions)

You need to add these models to your schema.prisma (v4 naming — note VerificationToken, not the old VerificationRequest):

model Account {
  id                String  @id @default(cuid())
  userId            String
  type              String
  provider          String
  providerAccountId String
  refresh_token     String? @db.Text
  access_token      String? @db.Text
  expires_at        Int?
  token_type        String?
  scope             String?
  id_token          String? @db.Text
  session_state     String?

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  accounts      Account[]
  sessions      Session[]
}

model VerificationToken {
  identifier String
  token      String   @unique
  expires    DateTime

  @@unique([identifier, token])
}

Remember to run npx prisma migrate dev any time you modify the schema to apply the changes to the database.

Now open pages/_app.js and add

import { SessionProvider } from 'next-auth/react'

And wrap your <Component /> call:

return <Component {...pageProps} />

with it:

return (
<SessionProvider session={pageProps.session}>
  <Component {...pageProps} />
</SessionProvider>
)

Now add into your app a link that points to /api/auth/signin. This will be the login form.

Finally, in pages you want to require a logged in session active, you first import the useSession hook:

import { useSession, signOut } from 'next-auth/react'

Then you use that to gather information on the state. status === 'loading' means the session info is still loading.

const { data: session, status } = useSession()

We can use this session object to print information on screen when the user is logged in:

{session && (
  <p>
    {session.user.email}{' '}
    <button
      className="underline"
      onClick={() => {
        signOut()
        router.push('/')
      }}
    >
      logout
    </button>
  </p>
)}

We can also use that information to not return anything unless we finished loading, and unless the session is established:

if (typeof window !== 'undefined' && status === 'loading') return null

if (typeof window !== 'undefined' && !session) {
  router.push('/api/auth/signin')
}

if (!session) { //for server-side rendering
  return null
}

I send the browser to /api/auth/signin if the user is not logged in. You can also create a custom form if you want, but those are the basics.

Server-side, you use

import { getServerSession } from 'next-auth/next'
import { authOptions } from './api/auth/[...nextauth]'

Export your config as authOptions from [...nextauth].js if you want to reuse it, then:

const session = await getServerSession(req, res, authOptions)

to get the session data, either in an API route or inside getServerSideProps({ req, res }).

That’s how I use NextAuth for a very basic authentication setup.

The NextAuth package is very complete and provides tons of options and customizations, check them out on https://next-auth.js.org.

Tagged: Next.js · All topics

Want me to talk about your product? You can sponsor this site.

~~~

Related posts about next: