Next.js provides multiple authentication solutions, and developers can choose the appropriate approach based on project requirements. Here are commonly used authentication methods in Next.js:
1. NextAuth.js
NextAuth.js is the most popular authentication library for Next.js, providing a complete authentication solution.
Basic Configuration
javascript// app/api/auth/[...nextauth]/route.js import NextAuth from 'next-auth'; import CredentialsProvider from 'next-auth/providers/credentials'; import GoogleProvider from 'next-auth/providers/google'; export const { handlers, auth, signIn, signOut } = NextAuth({ providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, }), CredentialsProvider({ name: 'Credentials', credentials: { email: { label: "Email", type: "email" }, password: { label: "Password", type: "password" } }, async authorize(credentials) { const user = await authenticateUser(credentials); if (user) { return user; } return null; } }), ], pages: { signIn: '/auth/signin', error: '/auth/error', }, callbacks: { async jwt({ token, user }) { if (user) { token.id = user.id; token.role = user.role; } return token; }, async session({ session, token }) { session.user.id = token.id; session.user.role = token.role; return session; }, }, session: { strategy: 'jwt', }, secret: process.env.NEXTAUTH_SECRET, }); export { handlers as GET, handlers as POST };
Using Session
javascript'use client'; import { useSession, signIn, signOut } from 'next-auth/react'; export default function Navbar() { const { data: session, status } = useSession(); if (status === 'loading') { return <div>Loading...</div>; } return ( <nav> {session ? ( <> <p>Welcome, {session.user.name}</p> <button onClick={() => signOut()}>Sign Out</button> </> ) : ( <button onClick={() => signIn()}>Sign In</button> )} </nav> ); }
Getting Session Server-side
javascriptimport { auth } from '@/auth'; export default async function Dashboard() { const session = await auth(); if (!session) { redirect('/auth/signin'); } return ( <div> <h1>Welcome, {session.user.name}</h1> <p>Role: {session.user.role}</p> </div> ); }
Protecting Routes
javascriptimport { auth } from '@/auth'; export default async function ProtectedPage() { const session = await auth(); if (!session) { redirect('/auth/signin'); } return <div>Protected content</div>; }
2. Custom JWT Implementation
Implement custom authentication using Next.js API Routes and JWT.
JWT Utility Functions
javascript// lib/auth.js import jwt from 'jsonwebtoken'; const SECRET = process.env.JWT_SECRET; export function signToken(payload) { return jwt.sign(payload, SECRET, { expiresIn: '7d' }); } export function verifyToken(token) { try { return jwt.verify(token, SECRET); } catch (error) { return null; } } export function setAuthCookie(res, token) { res.setHeader('Set-Cookie', `token=${token}; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=604800`); } export function clearAuthCookie(res) { res.setHeader('Set-Cookie', 'token=; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=0'); }
Login API
javascript// app/api/auth/login/route.js import { signToken, setAuthCookie } from '@/lib/auth'; import { compare } from 'bcryptjs'; import { db } from '@/lib/db'; export async function POST(request) { try { const { email, password } = await request.json(); const user = await db.user.findUnique({ where: { email } }); if (!user || !await compare(password, user.password)) { return Response.json( { error: 'Invalid credentials' }, { status: 401 } ); } const token = signToken({ userId: user.id, email: user.email }); const response = Response.json({ user: { id: user.id, email: user.email, name: user.name } }); setAuthCookie(response, token); return response; } catch (error) { return Response.json( { error: 'Internal server error' }, { status: 500 } ); } }
Register API
javascript// app/api/auth/register/route.js import { hash } from 'bcryptjs'; import { db } from '@/lib/db'; export async function POST(request) { try { const { email, password, name } = await request.json(); const existingUser = await db.user.findUnique({ where: { email } }); if (existingUser) { return Response.json( { error: 'User already exists' }, { status: 400 } ); } const hashedPassword = await hash(password, 10); const user = await db.user.create({ data: { email, password: hashedPassword, name, }, }); return Response.json( { user: { id: user.id, email: user.email, name: user.name } }, { status: 201 } ); } catch (error) { return Response.json( { error: 'Internal server error' }, { status: 500 } ); } }
Getting Current User
javascript// lib/getCurrentUser.js import { cookies } from 'next/headers'; import { verifyToken } from '@/lib/auth'; import { db } from '@/lib/db'; export async function getCurrentUser() { const cookieStore = await cookies(); const token = cookieStore.get('token')?.value; if (!token) { return null; } const decoded = verifyToken(token); if (!decoded) { return null; } const user = await db.user.findUnique({ where: { id: decoded.userId }, select: { id: true, email: true, name: true, role: true } }); return user; }
3. Middleware Route Protection
javascript// middleware.js import { NextResponse } from 'next/server'; import { verifyToken } from '@/lib/auth'; export function middleware(request) { const token = request.cookies.get('token')?.value; const { pathname } = request.nextUrl; // Public routes const publicPaths = ['/auth/signin', '/auth/register', '/api/auth/login', '/api/auth/register']; if (publicPaths.some(path => pathname.startsWith(path))) { return NextResponse.next(); } // API routes if (pathname.startsWith('/api')) { if (!token || !verifyToken(token)) { return NextResponse.json( { error: 'Unauthorized' }, { status: 401 } ); } return NextResponse.next(); } // Page routes if (!token || !verifyToken(token)) { return NextResponse.redirect(new URL('/auth/signin', request.url)); } return NextResponse.next(); } export const config = { matcher: [ '/((?!_next/static|_next/image|favicon.ico).*)', ], };
4. OAuth Integration
Google OAuth
javascript// app/api/auth/google/route.js import { OAuth2Client } from 'google-auth-library'; import { signToken, setAuthCookie } from '@/lib/auth'; import { db } from '@/lib/db'; const client = new OAuth2Client(process.env.GOOGLE_CLIENT_ID); export async function POST(request) { try { const { idToken } = await request.json(); const ticket = await client.verifyIdToken({ idToken, audience: process.env.GOOGLE_CLIENT_ID, }); const payload = ticket.getPayload(); let user = await db.user.findUnique({ where: { email: payload.email } }); if (!user) { user = await db.user.create({ data: { email: payload.email, name: payload.name, image: payload.picture, provider: 'google', }, }); } const token = signToken({ userId: user.id, email: user.email }); const response = Response.json({ user: { id: user.id, email: user.email, name: user.name } }); setAuthCookie(response, token); return response; } catch (error) { return Response.json( { error: 'Authentication failed' }, { status: 401 } ); } }
5. Permission Control
Role-Based Access Control (RBAC)
javascript// lib/permissions.js export const ROLES = { ADMIN: 'admin', USER: 'user', GUEST: 'guest', }; export function hasPermission(userRole, requiredRole) { const roleHierarchy = { [ROLES.ADMIN]: 3, [ROLES.USER]: 2, [ROLES.GUEST]: 1, }; return roleHierarchy[userRole] >= roleHierarchy[requiredRole]; } // Usage example export default function AdminPanel({ user }) { if (!hasPermission(user.role, ROLES.ADMIN)) { return <div>Access denied</div>; } return <div>Admin content</div>; }
Server-side Permission Check
javascriptimport { getCurrentUser } from '@/lib/getCurrentUser'; import { ROLES, hasPermission } from '@/lib/permissions'; export default async function AdminPage() { const user = await getCurrentUser(); if (!user || !hasPermission(user.role, ROLES.ADMIN)) { redirect('/'); } return <div>Admin content</div>; }
6. Password Reset
Send Reset Email
javascript// app/api/auth/forgot-password/route.js import { db } from '@/lib/db'; import { signToken } from '@/lib/auth'; import { sendEmail } from '@/lib/email'; export async function POST(request) { try { const { email } = await request.json(); const user = await db.user.findUnique({ where: { email } }); if (!user) { // Don't reveal if user exists return Response.json({ message: 'If user exists, email sent' }); } const resetToken = signToken( { userId: user.id, type: 'password_reset' }, { expiresIn: '1h' } ); await sendEmail({ to: email, subject: 'Password Reset', html: `<a href="${process.env.APP_URL}/auth/reset-password?token=${resetToken}">Reset Password</a>`, }); return Response.json({ message: 'Email sent' }); } catch (error) { return Response.json( { error: 'Internal server error' }, { status: 500 } ); } }
Reset Password
javascript// app/api/auth/reset-password/route.js import { verifyToken } from '@/lib/auth'; import { hash } from 'bcryptjs'; import { db } from '@/lib/db'; export async function POST(request) { try { const { token, password } = await request.json(); const decoded = verifyToken(token); if (!decoded || decoded.type !== 'password_reset') { return Response.json( { error: 'Invalid or expired token' }, { status: 400 } ); } const hashedPassword = await hash(password, 10); await db.user.update({ where: { id: decoded.userId }, data: { password: hashedPassword }, }); return Response.json({ message: 'Password reset successful' }); } catch (error) { return Response.json( { error: 'Internal server error' }, { status: 500 } ); } }
Authentication Best Practices
- Use HTTPS: Always use HTTPS for transmitting sensitive data
- HttpOnly Cookies: Use HttpOnly to prevent XSS attacks
- CSRF Protection: Implement CSRF token protection
- Password Hashing: Use bcrypt or argon2 to hash passwords
- Rate Limiting: Prevent brute force attacks
- Security Headers: Set appropriate security headers
- Logging: Log authentication events for auditing
- Session Management: Set reasonable session expiration times
- Multi-factor Authentication: Implement MFA for sensitive operations
- Regular Updates: Keep dependencies and security patches updated
By properly implementing these authentication methods, you can build secure and reliable Next.js applications.