Next.js Email Authentication using NextAuth
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:pass@smtp.mailtrap.io:465
EMAIL_FROM=Your name <you@email.com>
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.
download all my books for free
- javascript handbook
- typescript handbook
- css handbook
- node.js handbook
- astro handbook
- html handbook
- next.js pages router handbook
- alpine.js handbook
- htmx handbook
- react handbook
- sql handbook
- git cheat sheet
- laravel handbook
- express handbook
- swift handbook
- go handbook
- php handbook
- python handbook
- cli handbook
- c handbook
subscribe to my newsletter to get them
Terms: by subscribing to the newsletter you agree the following terms and conditions and privacy policy. The aim of the newsletter is to keep you up to date about new tutorials, new book releases or courses organized by Flavio. If you wish to unsubscribe from the newsletter, you can click the unsubscribe link that's present at the bottom of each email, anytime. I will not communicate/spread/publish or otherwise give away your address. Your email address is the only personal information collected, and it's only collected for the primary purpose of keeping you informed through the newsletter. It's stored in a secure server based in the EU. You can contact Flavio by emailing flavio@flaviocopes.com. These terms and conditions are governed by the laws in force in Italy and you unconditionally submit to the jurisdiction of the courts of Italy.