Next.js Email Authentication using NextAuth
By Flavio Copes
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.
The code in this post targets NextAuth.js v4 (next-auth@4, the current latest on npm) with the Pages Router (pages/api/auth/[...nextauth].js). The next major, v5, was renamed Auth.js and is still in beta (next-auth@beta). It has different imports and a different setup, documented at authjs.dev, so don’t mix the two.
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 (the Email provider uses it to send the login link, and NextAuth does not bundle it) and the PostgreSQL library.
Then add to your .env file:
DATABASE_URL=<enter URL of the postgresql:// database>
EMAIL_SERVER=smtp://user:[email protected]:465
EMAIL_FROM=Your name <[email protected]>
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. NextAuth uses it to sign the JWT (older versions of this post called the variable SECRET, v4 reads NEXTAUTH_SECRET).
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)
This is not complete yet. The Email provider saves the login tokens it emails out in the database, and in v4 you connect the database through an adapter. The database: process.env.DATABASE_URL option that older versions accepted is gone.
Which adapter depends 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@7
npm install -D prisma@7
I pin Prisma to the 7 line because the latest tag of the prisma package on npm currently points at an 8.0 release candidate.
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 devany 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]'
authOptions is the object we exported from [...nextauth].js, that’s why it was exported separately. 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.
Want me to talk about your product? You can sponsor this site.
Related posts about next: