Next.js Authentication — NextAuth.js v5 Guide

Sanjeev SharmaSanjeev Sharma
5 min read

Advertisement

Next.js Authentication — NextAuth.js v5 Guide

NextAuth.js v5 provides secure, flexible authentication for Next.js applications with support for multiple authentication methods.

Installation

npm install next-auth

Basic Setup

Create auth configuration:

// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth'
import Credentials from 'next-auth/providers/credentials'

const handler = NextAuth({
  providers: [
    Credentials({
      credentials: {
        email: { label: 'Email', type: 'email' },
        password: { label: 'Password', type: 'password' }
      },
      async authorize(credentials) {
        if (!credentials?.email || !credentials?.password) {
          return null
        }

        const user = await db.users.findUnique({
          where: { email: credentials.email }
        })

        if (!user) return null

        const isPasswordValid = await bcrypt.compare(
          credentials.password,
          user.password
        )

        if (!isPasswordValid) return null

        return {
          id: user.id,
          email: user.email,
          name: user.name
        }
      }
    })
  ],
  pages: {
    signIn: '/login',
    error: '/auth/error',
  }
})

export { handler as GET, handler as POST }

GitHub OAuth

// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'

const handler = NextAuth({
  providers: [
    GitHub({
      clientId: process.env.GITHUB_ID!,
      clientSecret: process.env.GITHUB_SECRET!,
    })
  ]
})

export { handler as GET, handler as POST }

Environment variables:

GITHUB_ID=your_github_id
GITHUB_SECRET=your_github_secret
NEXTAUTH_SECRET=your_secret_key

Multiple Providers

import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'
import Google from 'next-auth/providers/google'
import Credentials from 'next-auth/providers/credentials'

const handler = NextAuth({
  providers: [
    GitHub({
      clientId: process.env.GITHUB_ID!,
      clientSecret: process.env.GITHUB_SECRET!,
    }),
    Google({
      clientId: process.env.GOOGLE_ID!,
      clientSecret: process.env.GOOGLE_SECRET!,
    }),
    Credentials({
      // credentials config
    })
  ],
  pages: {
    signIn: '/login',
  },
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id
      }
      return token
    },
    async session({ session, token }) {
      session.user.id = token.id as string
      return session
    }
  }
})

export { handler as GET, handler as POST }

Login Page

// app/login/page.tsx
'use client'

import { signIn } from 'next-auth/react'
import { useState } from 'react'

export default function LoginPage() {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [error, setError] = useState('')

  async function handleSubmit(e) {
    e.preventDefault()

    const result = await signIn('credentials', {
      email,
      password,
      redirect: false
    })

    if (!result?.ok) {
      setError('Invalid credentials')
    }
  }

  return (
    <div className="max-w-md mx-auto mt-8">
      <form onSubmit={handleSubmit} className="space-y-4">
        {error && <p className="text-red-600">{error}</p>}

        <div>
          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            placeholder="Email"
            className="w-full px-4 py-2 border rounded"
          />
        </div>

        <div>
          <input
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            placeholder="Password"
            className="w-full px-4 py-2 border rounded"
          />
        </div>

        <button
          type="submit"
          className="w-full bg-blue-600 text-white py-2 rounded"
        >
          Sign In
        </button>
      </form>

      <div className="mt-4 space-y-2">
        <button
          onClick={() => signIn('github')}
          className="w-full bg-gray-900 text-white py-2 rounded"
        >
          Sign in with GitHub
        </button>
        <button
          onClick={() => signIn('google')}
          className="w-full bg-blue-500 text-white py-2 rounded"
        >
          Sign in with Google
        </button>
      </div>
    </div>
  )
}

Protected Pages

// app/dashboard/page.tsx
import { redirect } from 'next/navigation'
import { getServerSession } from 'next-auth'

export default async function DashboardPage() {
  const session = await getServerSession()

  if (!session?.user) {
    redirect('/login')
  }

  return (
    <div>
      <h1>Welcome, {session.user.name}</h1>
      <p>Email: {session.user.email}</p>
    </div>
  )
}

Client-Side Session

// app/components/user-menu.tsx
'use client'

import { useSession, signOut } from 'next-auth/react'

export function UserMenu() {
  const { data: session } = useSession()

  if (!session?.user) {
    return <a href="/login">Sign In</a>
  }

  return (
    <div className="flex items-center gap-4">
      <span>{session.user.email}</span>
      <button onClick={() => signOut()}>Sign Out</button>
    </div>
  )
}

Middleware Protection

// middleware.ts
import { auth } from '@/auth'

export default auth(function middleware(req) {
  // req.auth will have session info
})

export const config = {
  matcher: ['/dashboard/:path*', '/admin/:path*']
}
import NextAuth from 'next-auth'
import Email from 'next-auth/providers/email'

const handler = NextAuth({
  providers: [
    Email({
      server: process.env.EMAIL_SERVER_URL,
      from: process.env.EMAIL_FROM
    })
  ]
})

export { handler as GET, handler as POST }

User Creation

// app/register/page.tsx
'use client'

import { useState } from 'react'

export default function RegisterPage() {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [name, setName] = useState('')

  async function handleSubmit(e) {
    e.preventDefault()

    const response = await fetch('/api/register', {
      method: 'POST',
      body: JSON.stringify({ email, password, name })
    })

    if (response.ok) {
      window.location.href = '/login'
    }
  }

  return (
    <form onSubmit={handleSubmit} className="max-w-md mx-auto mt-8 space-y-4">
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Full Name"
        className="w-full px-4 py-2 border rounded"
      />
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
        className="w-full px-4 py-2 border rounded"
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
        className="w-full px-4 py-2 border rounded"
      />
      <button type="submit" className="w-full bg-blue-600 text-white py-2 rounded">
        Register
      </button>
    </form>
  )
}
// app/api/register/route.ts
import bcrypt from 'bcryptjs'
import { NextRequest, NextResponse } from 'next/server'

export async function POST(request: NextRequest) {
  const { email, password, name } = await request.json()

  const existingUser = await db.users.findUnique({ where: { email } })
  if (existingUser) {
    return NextResponse.json(
      { error: 'User exists' },
      { status: 400 }
    )
  }

  const hashedPassword = await bcrypt.hash(password, 10)

  const user = await db.users.create({
    data: { email, password: hashedPassword, name }
  })

  return NextResponse.json(user)
}

FAQ

Q: How do I store custom user fields? A: Create a custom User model in your database and include it in the callbacks.

Q: Should I use JWT or database sessions? A: JWT for stateless APIs, database sessions for traditional web apps. NextAuth.js handles both.

Q: How do I refresh sessions? A: NextAuth.js handles session refresh automatically. Manual refresh uses useSession({ required: true }).


NextAuth.js simplifies authentication implementation in Next.js applications.

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro