Skip to content

Next.js Email Authentication using NextAuth

New Course Coming Soon:

Get Really Good at Git

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

Are you intimidated by Git? Can’t figure out merge vs rebase? Are you afraid of screwing up something any time you have to do something in Git? Do you rely on ChatGPT or random people’s answer on StackOverflow to fix your problems? Your coworkers are tired of explaining Git to you all the time? Git is something we all need to use, but few of us really master it. I created this course to improve your Git (and GitHub) knowledge at a radical level. A course that helps you feel less frustrated with Git. Launching Summer 2024. Join the waiting list!

Here is how can I help you: