NextAuth.js (Auth.js)

Authentication patterns for NextAuth.js v5 (Auth.js) in Next.js applications.

Overview#

NextAuth.js provides flexible authentication with:

  • Multiple OAuth providers (Google, GitHub, Apple, etc.)
  • Database adapters for session persistence
  • JWT or database session strategies
  • Credentials-based authentication
  • TypeScript support

Prerequisites:

  • Next.js 14+ with App Router
  • Database (optional, for session storage)

Installation#

npm install next-auth @auth/prisma-adapter
1# .env.local 2AUTH_SECRET=your-secret-key 3GITHUB_ID=your-github-client-id 4GITHUB_SECRET=your-github-client-secret 5GOOGLE_ID=your-google-client-id 6GOOGLE_SECRET=your-google-client-secret

Implementation#

Basic Setup#

1// auth.ts 2import NextAuth from 'next-auth' 3import GitHub from 'next-auth/providers/github' 4import Google from 'next-auth/providers/google' 5import { PrismaAdapter } from '@auth/prisma-adapter' 6import { prisma } from '@/lib/db' 7 8export const { handlers, auth, signIn, signOut } = NextAuth({ 9 adapter: PrismaAdapter(prisma), 10 session: { strategy: 'jwt' }, 11 providers: [ 12 GitHub({ 13 clientId: process.env.GITHUB_ID!, 14 clientSecret: process.env.GITHUB_SECRET! 15 }), 16 Google({ 17 clientId: process.env.GOOGLE_ID!, 18 clientSecret: process.env.GOOGLE_SECRET! 19 }) 20 ], 21 callbacks: { 22 async jwt({ token, user }) { 23 if (user) { 24 token.id = user.id 25 } 26 return token 27 }, 28 async session({ session, token }) { 29 if (session.user) { 30 session.user.id = token.id as string 31 } 32 return session 33 } 34 }, 35 pages: { 36 signIn: '/login', 37 error: '/login' 38 } 39})

Route Handler#

// app/api/auth/[...nextauth]/route.ts import { handlers } from '@/auth' export const { GET, POST } = handlers

Server-Side Auth Check#

1// lib/auth.ts 2import { auth } from '@/auth' 3 4export async function getSession() { 5 return auth() 6} 7 8export async function requireAuth() { 9 const session = await auth() 10 11 if (!session?.user) { 12 throw new Error('Unauthorized') 13 } 14 15 return session 16}

Credentials Provider#

1// auth.ts (add to providers) 2import Credentials from 'next-auth/providers/credentials' 3import bcrypt from 'bcryptjs' 4 5Credentials({ 6 credentials: { 7 email: { type: 'email' }, 8 password: { type: 'password' } 9 }, 10 async authorize(credentials) { 11 const user = await prisma.user.findUnique({ 12 where: { email: credentials.email as string } 13 }) 14 15 if (!user?.password) return null 16 17 const valid = await bcrypt.compare( 18 credentials.password as string, 19 user.password 20 ) 21 22 if (!valid) return null 23 24 return { id: user.id, email: user.email, name: user.name } 25 } 26})

Sign In/Out Components#

1// components/auth-buttons.tsx 2'use client' 3import { signIn, signOut } from 'next-auth/react' 4import { Button } from '@/components/ui/button' 5 6export function SignInButton() { 7 return ( 8 <Button onClick={() => signIn('github')}> 9 Sign In with GitHub 10 </Button> 11 ) 12} 13 14export function SignOutButton() { 15 return ( 16 <Button variant="outline" onClick={() => signOut()}> 17 Sign Out 18 </Button> 19 ) 20}

Usage Examples#

Protected Server Component#

1// app/dashboard/page.tsx 2import { auth } from '@/auth' 3import { redirect } from 'next/navigation' 4 5export default async function DashboardPage() { 6 const session = await auth() 7 8 if (!session) { 9 redirect('/login') 10 } 11 12 return <Dashboard user={session.user} /> 13}

Client Component with Session#

1// components/user-menu.tsx 2'use client' 3 4import { useSession } from 'next-auth/react' 5 6export function UserMenu() { 7 const { data: session, status } = useSession() 8 9 if (status === 'loading') { 10 return <div>Loading...</div> 11 } 12 13 if (!session) { 14 return <SignInButton /> 15 } 16 17 return ( 18 <div> 19 <span>{session.user?.name}</span> 20 <SignOutButton /> 21 </div> 22 ) 23}

Best Practices#

  1. Use JWT strategy for serverless - Database sessions require persistent connections
  2. Always validate in callbacks - Check email verification status
  3. Secure credentials flow - Hash passwords with bcrypt, minimum 12 rounds
  4. Set up session provider - Wrap app in SessionProvider for client access
  5. Use database adapter - For production, persist sessions and accounts