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.
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 pg to install NextAuth 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
SECRET=
Make sure you add a SECRET code. You can use https://generate-secret.vercel.app/32 to generate it.
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 Providers from 'next-auth/providers'
export default NextAuth({
providers: [
Providers.Email({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
}),
],
database: process.env.DATABASE_URL,
secret: process.env.SECRET,
session: {
jwt: true,
maxAge: 30 * 24 * 60 * 60, // 30 days
},
jwt: {
secret: 'INp8IvdIyeMcoGAgFGoA61DdBglwwSqnXJZkgz8PSnX', //use a random secret token here
encryption: true,
},
debug: true,
})
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
and include it in [...nextauth].js:
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'
import { PrismaAdapter } from '@next-auth/prisma-adapter'
import prisma from 'lib/prisma'
export default NextAuth({
providers: [
Providers.Email({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM
})
],
database: process.env.DATABASE_URL,
secret: process.env.SECRET,
session: {
jwt: true,
maxAge: 30 * 24 * 60 * 60 // 30 days
},
jwt: {
secret: 'INp8IvdIyeMcoGAgFGoA61DdBglwwSqnXJZkgz8PSnX', //use a random secret token here
encryption: true
},
debug: true,
adapter: PrismaAdapter(prisma)
})
You need to add 4 models to your schema.prisma:
model VerificationRequest {
id String @id @default(cuid())
identifier String
token String @unique
expires DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([identifier, token])
}
model Account {
id String @id @default(cuid())
providerType String
providerId String
providerAccountId String
refreshToken String?
accessToken String?
accessTokenExpires DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
userId Int
@@unique([providerId, providerAccountId])
}
model Session {
id String @id @default(cuid())
expires DateTime
sessionToken String @unique
accessToken String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
userId Int
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
accounts Account[]
sessions Session[]
}
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 } from 'next-auth/react'
Then you use that to gather information on the state. loading is true when 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' && 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 { getSession } from 'next-auth/react'
then
const session = await getSession({ req })
to get the session data, either in an API route or inside getServerSideProps({ req }).
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.
Related posts about next: