Skip to content

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 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 } 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.


→ Get my Next.js (pages router) Handbook

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.

Related posts about next: