Next.js Email Authentication using NextAuth

Managing authentication in Next.js can be done in many different ways.

I chose to implement email-based authentication with JWT tokens via NextAuth.js

An external database is needed. I used https://railway.app to create it. I chose Postgres 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 Postgres library.

Then add to your .env file:

USER_DATABASE_URL=<enter URL from Railway>

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. Use https://generate-secret.vercel.app/32 to generate it.

I use https://mailtrap.io to test the emails, it’s quite handy.

Then 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.USER_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 open pages/_app.js and add

import { Provider } from 'next-auth/client'

And wrap your <Component /> call:

return <Component {...pageProps} key={router.asPath} />

with it:

return (
<Provider session={pageProps.session}>
  <Component {...pageProps} key={router.asPath} />
</Provider>
)

Now add where you want a link that points to /api/auth/signin. This will be the login form.

Finally, in pages you want to protect, you first import the useSession hook:

import { useSession } from 'next-auth/client'

Then you use that to gather information on the state. loading is true when the session info is still loading.

const [session, loading] = useSession()

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

That’s it! Super simple.

Now when the user enters the email in the form, the information is stored in the verification_requests table in the database. Once the email is verified, the user’s info is stored in the users table.

That database is not going to be read any more, until the user logs out and logs in again. The user has the JWT token stored in the cookies and that’s enough to authenticate.

The NextAuth package is very complete and provides tons of options and customizations, check them out.

Download my free Next.js Handbook!

👀 Looking for a job? look at DEVJOBS (BETA) 🔥