Authentication Patterns in Next.js Overview Next.js supports multiple authentication strategies. This skill covers common patterns including NextAuth.js (Auth.js), middleware-based protection, and session management. Authentication Libraries | Library | Best For | |---------|----------| | NextAuth.js (Auth.js) | Full-featured auth with providers | | Clerk | Managed auth service | | Lucia | Lightweight, flexible auth | | Supabase Auth | Supabase ecosystem | | Custom JWT | Full control | NextAuth.js v5 Setup Installation Configuration API Route Handler Middleware Protection Getting Session Data…

\n )\n return regex.test(pathname)\n })\n}\n\nexport async function middleware(request: NextRequest) {\n const { pathname } = request.nextUrl\n const token = request.cookies.get('token')?.value\n\n if (!token) {\n return NextResponse.redirect(new URL('/login', request.url))\n }\n\n const userData = await getTokenData(token)\n\n if (!userData) {\n const response = NextResponse.redirect(new URL('/login', request.url))\n response.cookies.delete('token')\n return response\n }\n\n // Check admin routes\n if (matchesRoute(pathname, roleRoutes.admin)) {\n if (userData.role !== 'admin') {\n return NextResponse.redirect(new URL('/unauthorized', request.url))\n }\n }\n\n // Check editor routes\n if (matchesRoute(pathname, roleRoutes.editor)) {\n if (!['admin', 'editor'].includes(userData.role)) {\n return NextResponse.redirect(new URL('/unauthorized', request.url))\n }\n }\n\n return NextResponse.next()\n}\n```\n\n## JWT Token Verification\n\n### With jose Library\n\n```tsx\n// middleware.ts\nimport { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\nimport { jwtVerify, JWTPayload } from 'jose'\n\ninterface TokenPayload extends JWTPayload {\n userId: string\n email: string\n role: string\n}\n\nasync function verifyToken(token: string): Promise\u003cTokenPayload | null> {\n try {\n const secret = new TextEncoder().encode(process.env.JWT_SECRET)\n const { payload } = await jwtVerify(token, secret, {\n algorithms: ['HS256'],\n })\n return payload as TokenPayload\n } catch (error) {\n console.error('Token verification failed:', error)\n return null\n }\n}\n\nexport async function middleware(request: NextRequest) {\n const token = request.cookies.get('auth-token')?.value\n\n if (!token) {\n return NextResponse.redirect(new URL('/login', request.url))\n }\n\n const payload = await verifyToken(token)\n\n if (!payload) {\n const response = NextResponse.redirect(new URL('/login', request.url))\n response.cookies.delete('auth-token')\n return response\n }\n\n // Add user info to headers for downstream use\n const requestHeaders = new Headers(request.headers)\n requestHeaders.set('x-user-id', payload.userId)\n requestHeaders.set('x-user-role', payload.role)\n\n return NextResponse.next({\n request: {\n headers: requestHeaders,\n },\n })\n}\n```\n\n## Session Refresh\n\n```tsx\n// middleware.ts\nimport { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\n\nexport async function middleware(request: NextRequest) {\n const sessionToken = request.cookies.get('session')?.value\n\n if (!sessionToken) {\n return NextResponse.redirect(new URL('/login', request.url))\n }\n\n // Verify and potentially refresh session\n try {\n const response = await fetch(\n `${request.nextUrl.origin}/api/auth/verify`,\n {\n headers: {\n cookie: `session=${sessionToken}`,\n },\n }\n )\n\n if (!response.ok) {\n const loginResponse = NextResponse.redirect(\n new URL('/login', request.url)\n )\n loginResponse.cookies.delete('session')\n return loginResponse\n }\n\n const data = await response.json()\n\n // If session was refreshed, update the cookie\n if (data.newToken) {\n const nextResponse = NextResponse.next()\n nextResponse.cookies.set('session', data.newToken, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: 60 * 60 * 24 * 7, // 7 days\n })\n return nextResponse\n }\n\n return NextResponse.next()\n } catch (error) {\n console.error('Session verification error:', error)\n return NextResponse.redirect(new URL('/login', request.url))\n }\n}\n```\n\n## API Route Protection\n\n```tsx\n// middleware.ts\nimport { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\n\nexport async function middleware(request: NextRequest) {\n const { pathname } = request.nextUrl\n\n // API route protection\n if (pathname.startsWith('/api/')) {\n // Skip public API routes\n if (\n pathname.startsWith('/api/auth/') ||\n pathname.startsWith('/api/public/')\n ) {\n return NextResponse.next()\n }\n\n // Check for API key or Bearer token\n const authHeader = request.headers.get('authorization')\n\n if (!authHeader?.startsWith('Bearer ')) {\n return NextResponse.json(\n { error: 'Missing authentication' },\n { status: 401 }\n )\n }\n\n const token = authHeader.split(' ')[1]\n\n try {\n const isValid = await verifyApiToken(token)\n\n if (!isValid) {\n return NextResponse.json(\n { error: 'Invalid token' },\n { status: 401 }\n )\n }\n } catch (error) {\n return NextResponse.json(\n { error: 'Authentication failed' },\n { status: 401 }\n )\n }\n }\n\n return NextResponse.next()\n}\n```\n\n## Rate Limiting in Middleware\n\n```tsx\n// middleware.ts\nimport { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\nimport { Ratelimit } from '@upstash/ratelimit'\nimport { Redis } from '@upstash/redis'\n\nconst ratelimit = new Ratelimit({\n redis: Redis.fromEnv(),\n limiter: Ratelimit.slidingWindow(100, '1 m'),\n analytics: true,\n})\n\nexport async function middleware(request: NextRequest) {\n if (request.nextUrl.pathname.startsWith('/api/')) {\n const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1'\n const { success, limit, reset, remaining } = await ratelimit.limit(ip)\n\n if (!success) {\n return NextResponse.json(\n { error: 'Too many requests' },\n {\n status: 429,\n headers: {\n 'X-RateLimit-Limit': limit.toString(),\n 'X-RateLimit-Remaining': remaining.toString(),\n 'X-RateLimit-Reset': reset.toString(),\n },\n }\n )\n }\n }\n\n return NextResponse.next()\n}\n```\n\n## Matcher Configuration\n\n### Precise Matching\n\n```tsx\nexport const config = {\n matcher: [\n // Match all paths except static files\n '/((?!_next/static|_next/image|favicon.ico).*)',\n ],\n}\n```\n\n### Multiple Matchers\n\n```tsx\nexport const config = {\n matcher: [\n '/dashboard/:path*',\n '/api/:path*',\n '/settings/:path*',\n ],\n}\n```\n\n### Regex Patterns\n\n```tsx\nexport const config = {\n matcher: [\n // Match paths starting with /api but not /api/public\n '/api/((?!public).*)',\n // Match all dashboard routes\n '/dashboard/(.*)',\n ],\n}\n```\n\n## Conditional Middleware\n\n```tsx\n// middleware.ts\nimport { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\n\nexport function middleware(request: NextRequest) {\n const { pathname } = request.nextUrl\n\n // Different logic for different paths\n if (pathname.startsWith('/api/')) {\n return handleApiAuth(request)\n }\n\n if (pathname.startsWith('/admin')) {\n return handleAdminAuth(request)\n }\n\n if (pathname.startsWith('/dashboard')) {\n return handleUserAuth(request)\n }\n\n return NextResponse.next()\n}\n\nfunction handleApiAuth(request: NextRequest) {\n const apiKey = request.headers.get('x-api-key')\n if (!apiKey || apiKey !== process.env.API_KEY) {\n return NextResponse.json({ error: 'Invalid API key' }, { status: 401 })\n }\n return NextResponse.next()\n}\n\nfunction handleAdminAuth(request: NextRequest) {\n const token = request.cookies.get('admin-token')?.value\n if (!token) {\n return NextResponse.redirect(new URL('/admin/login', request.url))\n }\n return NextResponse.next()\n}\n\nfunction handleUserAuth(request: NextRequest) {\n const session = request.cookies.get('session')?.value\n if (!session) {\n return NextResponse.redirect(new URL('/login', request.url))\n }\n return NextResponse.next()\n}\n```\n\n## Headers and Cookies\n\n```tsx\n// middleware.ts\nexport function middleware(request: NextRequest) {\n const response = NextResponse.next()\n\n // Add security headers\n response.headers.set('X-Frame-Options', 'DENY')\n response.headers.set('X-Content-Type-Options', 'nosniff')\n response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')\n\n // Set CORS headers for API routes\n if (request.nextUrl.pathname.startsWith('/api/')) {\n response.headers.set('Access-Control-Allow-Origin', '*')\n response.headers.set(\n 'Access-Control-Allow-Methods',\n 'GET, POST, PUT, DELETE, OPTIONS'\n )\n }\n\n return response\n}\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":10824,"content_sha256":"9e47d31cd6e1feb26c84fe9242def8a6fe3844aaa7064ff223bf479c0d47ef82"},{"filename":"references/session-management.md","content":"# Session Management in Next.js\n\n## Session Strategies\n\n### 1. Stateless JWT Sessions\n\nStore session data in the token itself:\n\n```tsx\n// lib/jwt.ts\nimport { SignJWT, jwtVerify } from 'jose'\n\nconst secret = new TextEncoder().encode(process.env.JWT_SECRET)\n\nexport interface SessionPayload {\n userId: string\n email: string\n role: string\n expiresAt: Date\n}\n\nexport async function createSession(payload: Omit\u003cSessionPayload, 'expiresAt'>) {\n const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 7 days\n\n const token = await new SignJWT({ ...payload, expiresAt })\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime('7d')\n .sign(secret)\n\n return { token, expiresAt }\n}\n\nexport async function verifySession(token: string): Promise\u003cSessionPayload | null> {\n try {\n const { payload } = await jwtVerify(token, secret, {\n algorithms: ['HS256'],\n })\n return payload as SessionPayload\n } catch {\n return null\n }\n}\n```\n\n### 2. Database Sessions\n\nStore session in database, only ID in cookie:\n\n```tsx\n// lib/session.ts\nimport { cookies } from 'next/headers'\nimport { nanoid } from 'nanoid'\n\nexport async function createSession(userId: string) {\n const sessionId = nanoid(32)\n const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)\n\n await db.session.create({\n data: {\n id: sessionId,\n userId,\n expiresAt,\n },\n })\n\n const cookieStore = await cookies()\n cookieStore.set('session', sessionId, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n expires: expiresAt,\n path: '/',\n })\n\n return sessionId\n}\n\nexport async function getSession() {\n const cookieStore = await cookies()\n const sessionId = cookieStore.get('session')?.value\n\n if (!sessionId) return null\n\n const session = await db.session.findUnique({\n where: { id: sessionId },\n include: { user: true },\n })\n\n if (!session || session.expiresAt \u003c new Date()) {\n return null\n }\n\n return session\n}\n\nexport async function deleteSession() {\n const cookieStore = await cookies()\n const sessionId = cookieStore.get('session')?.value\n\n if (sessionId) {\n await db.session.delete({ where: { id: sessionId } })\n cookieStore.delete('session')\n }\n}\n```\n\n## Cookie Configuration\n\n### Secure Cookie Settings\n\n```tsx\n// lib/cookies.ts\nimport { cookies } from 'next/headers'\n\nexport async function setSessionCookie(sessionId: string, maxAge: number) {\n const cookieStore = await cookies()\n\n cookieStore.set('session', sessionId, {\n httpOnly: true, // Not accessible via JavaScript\n secure: process.env.NODE_ENV === 'production', // HTTPS only in prod\n sameSite: 'lax', // CSRF protection\n maxAge, // Expiration in seconds\n path: '/', // Available on all paths\n })\n}\n\nexport async function getSessionCookie() {\n const cookieStore = await cookies()\n return cookieStore.get('session')?.value\n}\n\nexport async function clearSessionCookie() {\n const cookieStore = await cookies()\n cookieStore.delete('session')\n}\n```\n\n### SameSite Options\n\n```tsx\n// 'strict' - Cookie only sent on same-site requests\n// 'lax' - Cookie sent on same-site + top-level navigation\n// 'none' - Cookie sent on all requests (requires secure: true)\n\n// For OAuth callbacks from external providers:\ncookieStore.set('oauth-state', state, {\n httpOnly: true,\n secure: true,\n sameSite: 'none', // Required for cross-site OAuth\n maxAge: 600, // 10 minutes\n})\n```\n\n## Session Refresh\n\n### Sliding Window Sessions\n\n```tsx\n// lib/session.ts\nconst SESSION_DURATION = 7 * 24 * 60 * 60 * 1000 // 7 days\nconst REFRESH_THRESHOLD = 24 * 60 * 60 * 1000 // 1 day\n\nexport async function getSessionWithRefresh() {\n const cookieStore = await cookies()\n const sessionId = cookieStore.get('session')?.value\n\n if (!sessionId) return null\n\n const session = await db.session.findUnique({\n where: { id: sessionId },\n include: { user: true },\n })\n\n if (!session || session.expiresAt \u003c new Date()) {\n await deleteSession()\n return null\n }\n\n // Refresh if session expires within threshold\n const timeUntilExpiry = session.expiresAt.getTime() - Date.now()\n\n if (timeUntilExpiry \u003c REFRESH_THRESHOLD) {\n const newExpiresAt = new Date(Date.now() + SESSION_DURATION)\n\n await db.session.update({\n where: { id: sessionId },\n data: { expiresAt: newExpiresAt },\n })\n\n cookieStore.set('session', sessionId, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n expires: newExpiresAt,\n path: '/',\n })\n }\n\n return session\n}\n```\n\n### Token Rotation\n\n```tsx\n// lib/session.ts\nexport async function rotateSession(oldSessionId: string) {\n const oldSession = await db.session.findUnique({\n where: { id: oldSessionId },\n })\n\n if (!oldSession) return null\n\n // Create new session\n const newSessionId = nanoid(32)\n const expiresAt = new Date(Date.now() + SESSION_DURATION)\n\n await db.$transaction([\n // Create new session\n db.session.create({\n data: {\n id: newSessionId,\n userId: oldSession.userId,\n expiresAt,\n },\n }),\n // Delete old session\n db.session.delete({\n where: { id: oldSessionId },\n }),\n ])\n\n const cookieStore = await cookies()\n cookieStore.set('session', newSessionId, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n expires: expiresAt,\n path: '/',\n })\n\n return newSessionId\n}\n```\n\n## Server-Side Session Access\n\n### In Server Components\n\n```tsx\n// app/dashboard/page.tsx\nimport { getSession } from '@/lib/session'\nimport { redirect } from 'next/navigation'\n\nexport default async function DashboardPage() {\n const session = await getSession()\n\n if (!session) {\n redirect('/login')\n }\n\n return (\n \u003cdiv>\n \u003ch1>Welcome, {session.user.name}\u003c/h1>\n \u003cp>Email: {session.user.email}\u003c/p>\n \u003c/div>\n )\n}\n```\n\n### In Server Actions\n\n```tsx\n// actions/profile.ts\n'use server'\n\nimport { getSession } from '@/lib/session'\nimport { revalidatePath } from 'next/cache'\n\nexport async function updateProfile(formData: FormData) {\n const session = await getSession()\n\n if (!session) {\n throw new Error('Unauthorized')\n }\n\n await db.user.update({\n where: { id: session.user.id },\n data: {\n name: formData.get('name') as string,\n bio: formData.get('bio') as string,\n },\n })\n\n revalidatePath('/profile')\n}\n```\n\n### In Route Handlers\n\n```tsx\n// app/api/user/route.ts\nimport { NextResponse } from 'next/server'\nimport { getSession } from '@/lib/session'\n\nexport async function GET() {\n const session = await getSession()\n\n if (!session) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n return NextResponse.json({\n user: {\n id: session.user.id,\n name: session.user.name,\n email: session.user.email,\n },\n })\n}\n```\n\n## Session Context for Client\n\n```tsx\n// lib/auth.tsx\n'use client'\n\nimport { createContext, useContext, useEffect, useState } from 'react'\n\ninterface User {\n id: string\n name: string\n email: string\n}\n\ninterface SessionContextType {\n user: User | null\n loading: boolean\n refresh: () => Promise\u003cvoid>\n}\n\nconst SessionContext = createContext\u003cSessionContextType | null>(null)\n\nexport function SessionProvider({ children }: { children: React.ReactNode }) {\n const [user, setUser] = useState\u003cUser | null>(null)\n const [loading, setLoading] = useState(true)\n\n const fetchSession = async () => {\n try {\n const res = await fetch('/api/auth/session')\n if (res.ok) {\n const data = await res.json()\n setUser(data.user)\n } else {\n setUser(null)\n }\n } catch {\n setUser(null)\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchSession()\n }, [])\n\n return (\n \u003cSessionContext.Provider value={{ user, loading, refresh: fetchSession }}>\n {children}\n \u003c/SessionContext.Provider>\n )\n}\n\nexport function useSession() {\n const context = useContext(SessionContext)\n if (!context) {\n throw new Error('useSession must be used within SessionProvider')\n }\n return context\n}\n```\n\n## Session Cleanup\n\n### Cleanup Expired Sessions\n\n```tsx\n// lib/session.ts\nexport async function cleanupExpiredSessions() {\n const result = await db.session.deleteMany({\n where: {\n expiresAt: {\n lt: new Date(),\n },\n },\n })\n\n console.log(`Cleaned up ${result.count} expired sessions`)\n return result.count\n}\n\n// Run via cron job or scheduled function\n// app/api/cron/cleanup/route.ts\nimport { NextResponse } from 'next/server'\nimport { cleanupExpiredSessions } from '@/lib/session'\n\nexport async function GET(request: Request) {\n // Verify cron secret\n const authHeader = request.headers.get('authorization')\n if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const count = await cleanupExpiredSessions()\n return NextResponse.json({ cleaned: count })\n}\n```\n\n### Revoke All Sessions\n\n```tsx\n// lib/session.ts\nexport async function revokeAllSessions(userId: string, exceptCurrent?: string) {\n await db.session.deleteMany({\n where: {\n userId,\n ...(exceptCurrent && { id: { not: exceptCurrent } }),\n },\n })\n}\n\n// actions/security.ts\n'use server'\n\nimport { getSession, revokeAllSessions } from '@/lib/session'\n\nexport async function logoutAllDevices() {\n const session = await getSession()\n\n if (!session) {\n throw new Error('Unauthorized')\n }\n\n // Keep current session, revoke all others\n await revokeAllSessions(session.user.id, session.id)\n\n return { success: true }\n}\n```\n\n## Multi-Device Session Management\n\n```tsx\n// app/settings/sessions/page.tsx\nimport { getSession } from '@/lib/session'\n\nexport default async function SessionsPage() {\n const currentSession = await getSession()\n\n const allSessions = await db.session.findMany({\n where: { userId: currentSession?.user.id },\n orderBy: { createdAt: 'desc' },\n })\n\n return (\n \u003cdiv>\n \u003ch1>Active Sessions\u003c/h1>\n \u003cul>\n {allSessions.map((session) => (\n \u003cli key={session.id}>\n \u003cp>Created: {session.createdAt.toLocaleDateString()}\u003c/p>\n \u003cp>Expires: {session.expiresAt.toLocaleDateString()}\u003c/p>\n {session.id === currentSession?.id ? (\n \u003cspan className=\"text-green-600\">Current session\u003c/span>\n ) : (\n \u003cform action={revokeSession.bind(null, session.id)}>\n \u003cbutton type=\"submit\">Revoke\u003c/button>\n \u003c/form>\n )}\n \u003c/li>\n ))}\n \u003c/ul>\n \u003c/div>\n )\n}\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":10710,"content_sha256":"00d3ecef2935d886d5d17cd7e1665e61737245ba6b2c24e590cf19a43aebbeef"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Authentication Patterns in Next.js","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Overview","type":"text"}]},{"type":"paragraph","content":[{"text":"Next.js supports multiple authentication strategies. This skill covers common patterns including NextAuth.js (Auth.js), middleware-based protection, and session management.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Authentication Libraries","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Library","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Best For","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"NextAuth.js (Auth.js)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Full-featured auth with providers","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Clerk","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Managed auth service","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Lucia","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Lightweight, flexible auth","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Supabase Auth","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Supabase ecosystem","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Custom JWT","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Full control","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"NextAuth.js v5 Setup","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Installation","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"npm install next-auth@beta","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Configuration","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"tsx"},"content":[{"text":"// auth.ts\nimport NextAuth from 'next-auth'\nimport GitHub from 'next-auth/providers/github'\nimport Credentials from 'next-auth/providers/credentials'\n\nexport const { handlers, auth, signIn, signOut } = NextAuth({\n providers: [\n GitHub({\n clientId: process.env.GITHUB_ID,\n clientSecret: process.env.GITHUB_SECRET,\n }),\n Credentials({\n credentials: {\n email: { label: 'Email', type: 'email' },\n password: { label: 'Password', type: 'password' },\n },\n authorize: async (credentials) => {\n const user = await getUserByEmail(credentials.email)\n if (!user || !verifyPassword(credentials.password, user.password)) {\n return null\n }\n return user\n },\n }),\n ],\n callbacks: {\n authorized: async ({ auth }) => {\n return !!auth\n },\n },\n})","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"API Route Handler","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"tsx"},"content":[{"text":"// app/api/auth/[...nextauth]/route.ts\nimport { handlers } from '@/auth'\n\nexport const { GET, POST } = handlers","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Middleware Protection","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"tsx"},"content":[{"text":"// middleware.ts\nexport { auth as middleware } from '@/auth'\n\nexport const config = {\n matcher: ['/dashboard/:path*', '/api/protected/:path*'],\n}","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Getting Session Data","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"In Server Components","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"tsx"},"content":[{"text":"// app/dashboard/page.tsx\nimport { auth } from '@/auth'\nimport { redirect } from 'next/navigation'\n\nexport default async function DashboardPage() {\n const session = await auth()\n\n if (!session) {\n redirect('/login')\n }\n\n return (\n \u003cdiv>\n \u003ch1>Welcome, {session.user?.name}\u003c/h1>\n \u003c/div>\n )\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"In Client Components","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"tsx"},"content":[{"text":"// components/user-menu.tsx\n'use client'\n\nimport { useSession } from 'next-auth/react'\n\nexport function UserMenu() {\n const { data: session, status } = useSession()\n\n if (status === 'loading') {\n return \u003cdiv>Loading...\u003c/div>\n }\n\n if (!session) {\n return \u003cSignInButton />\n }\n\n return (\n \u003cdiv>\n \u003cspan>{session.user?.name}\u003c/span>\n \u003cSignOutButton />\n \u003c/div>\n )\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Session Provider Setup","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"tsx"},"content":[{"text":"// app/providers.tsx\n'use client'\n\nimport { SessionProvider } from 'next-auth/react'\n\nexport function Providers({ children }: { children: React.ReactNode }) {\n return \u003cSessionProvider>{children}\u003c/SessionProvider>\n}\n\n// app/layout.tsx\nimport { Providers } from './providers'\n\nexport default function RootLayout({ children }) {\n return (\n \u003chtml>\n \u003cbody>\n \u003cProviders>{children}\u003c/Providers>\n \u003c/body>\n \u003c/html>\n )\n}","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Sign In/Out Components","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"tsx"},"content":[{"text":"// components/auth-buttons.tsx\nimport { signIn, signOut } from '@/auth'\n\nexport function SignInButton() {\n return (\n \u003cform\n action={async () => {\n 'use server'\n await signIn('github')\n }}\n >\n \u003cbutton type=\"submit\">Sign in with GitHub\u003c/button>\n \u003c/form>\n )\n}\n\nexport function SignOutButton() {\n return (\n \u003cform\n action={async () => {\n 'use server'\n await signOut()\n }}\n >\n \u003cbutton type=\"submit\">Sign out\u003c/button>\n \u003c/form>\n )\n}","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Middleware-Based Auth","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Basic Pattern","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"tsx"},"content":[{"text":"// middleware.ts\nimport { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\n\nconst protectedRoutes = ['/dashboard', '/settings', '/api/protected']\nconst authRoutes = ['/login', '/signup']\n\nexport function middleware(request: NextRequest) {\n const token = request.cookies.get('session')?.value\n const { pathname } = request.nextUrl\n\n // Redirect authenticated users away from auth pages\n if (authRoutes.some(route => pathname.startsWith(route))) {\n if (token) {\n return NextResponse.redirect(new URL('/dashboard', request.url))\n }\n return NextResponse.next()\n }\n\n // Protect routes\n if (protectedRoutes.some(route => pathname.startsWith(route))) {\n if (!token) {\n const loginUrl = new URL('/login', request.url)\n loginUrl.searchParams.set('callbackUrl', pathname)\n return NextResponse.redirect(loginUrl)\n }\n }\n\n return NextResponse.next()\n}\n\nexport const config = {\n matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"With JWT Verification","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"tsx"},"content":[{"text":"// middleware.ts\nimport { NextResponse } from 'next/server'\nimport { jwtVerify } from 'jose'\n\nconst secret = new TextEncoder().encode(process.env.JWT_SECRET)\n\nexport async function middleware(request: NextRequest) {\n const token = request.cookies.get('token')?.value\n\n if (!token) {\n return NextResponse.redirect(new URL('/login', request.url))\n }\n\n try {\n const { payload } = await jwtVerify(token, secret)\n // Token is valid, continue\n return NextResponse.next()\n } catch {\n // Token is invalid\n return NextResponse.redirect(new URL('/login', request.url))\n }\n}","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Role-Based Access Control","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Extending Session Types","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"tsx"},"content":[{"text":"// types/next-auth.d.ts\nimport { DefaultSession } from 'next-auth'\n\ndeclare module 'next-auth' {\n interface Session {\n user: {\n role: 'user' | 'admin'\n } & DefaultSession['user']\n }\n}\n\n// auth.ts\nexport const { handlers, auth } = NextAuth({\n callbacks: {\n session: ({ session, token }) => ({\n ...session,\n user: {\n ...session.user,\n role: token.role,\n },\n }),\n jwt: ({ token, user }) => {\n if (user) {\n token.role = user.role\n }\n return token\n },\n },\n})","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Role-Based Component","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"tsx"},"content":[{"text":"// components/admin-only.tsx\nimport { auth } from '@/auth'\nimport { redirect } from 'next/navigation'\n\nexport async function AdminOnly({ children }: { children: React.ReactNode }) {\n const session = await auth()\n\n if (session?.user?.role !== 'admin') {\n redirect('/unauthorized')\n }\n\n return \u003c>{children}\u003c/>\n}\n\n// Usage\nexport default async function AdminPage() {\n return (\n \u003cAdminOnly>\n \u003cAdminDashboard />\n \u003c/AdminOnly>\n )\n}","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Session Storage Options","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"JWT (Stateless)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"tsx"},"content":[{"text":"// auth.ts\nexport const { auth } = NextAuth({\n session: { strategy: 'jwt' },\n // JWT stored in cookies, no database needed\n})","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Database Sessions","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"tsx"},"content":[{"text":"// auth.ts\nimport { PrismaAdapter } from '@auth/prisma-adapter'\nimport { prisma } from '@/lib/prisma'\n\nexport const { auth } = NextAuth({\n adapter: PrismaAdapter(prisma),\n session: { strategy: 'database' },\n // Sessions stored in database\n})","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Custom Login Page","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"tsx"},"content":[{"text":"// app/login/page.tsx\n'use client'\n\nimport { signIn } from 'next-auth/react'\nimport { useSearchParams } from 'next/navigation'\n\nexport default function LoginPage() {\n const searchParams = useSearchParams()\n const callbackUrl = searchParams.get('callbackUrl') || '/dashboard'\n\n return (\n \u003cdiv className=\"flex flex-col gap-4\">\n \u003cbutton\n onClick={() => signIn('github', { callbackUrl })}\n className=\"btn\"\n >\n Sign in with GitHub\n \u003c/button>\n \u003cbutton\n onClick={() => signIn('google', { callbackUrl })}\n className=\"btn\"\n >\n Sign in with Google\n \u003c/button>\n \u003c/div>\n )\n}","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Security Best Practices","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use HTTPS","type":"text","marks":[{"type":"strong"}]},{"text":" in production","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Set secure cookie flags","type":"text","marks":[{"type":"strong"}]},{"text":" (HttpOnly, Secure, SameSite)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Implement CSRF protection","type":"text","marks":[{"type":"strong"}]},{"text":" (built into NextAuth)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Validate redirect URLs","type":"text","marks":[{"type":"strong"}]},{"text":" to prevent open redirects","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use environment variables","type":"text","marks":[{"type":"strong"}]},{"text":" for secrets","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Implement rate limiting","type":"text","marks":[{"type":"strong"}]},{"text":" on auth endpoints","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Hash passwords","type":"text","marks":[{"type":"strong"}]},{"text":" with bcrypt or argon2","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Resources","type":"text"}]},{"type":"paragraph","content":[{"text":"For detailed patterns, see:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/middleware-auth.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Advanced middleware patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/session-management.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Session strategies","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"examples/nextauth-setup.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Complete NextAuth.js setup","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"auth-patterns","author":"@skillopedia","source":{"stars":2998,"repo_name":"buildwithclaude","origin_url":"https://github.com/davepoon/buildwithclaude/blob/HEAD/plugins/nextjs-expert/skills/auth-patterns/SKILL.md","repo_owner":"davepoon","body_sha256":"212bb5efa018f810b20c51e15dfefac71389968e8ede2c516ea4e90c434efb0a","cluster_key":"abfceb8465b0dcc282f8a2a11b8fb37ee7eac38c1959e720015f58ead91365dd","clean_bundle":{"format":"clean-skill-bundle-v1","source":"davepoon/buildwithclaude/plugins/nextjs-expert/skills/auth-patterns/SKILL.md","attachments":[{"id":"cf935552-4a13-5ef1-8746-db53218acf2e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cf935552-4a13-5ef1-8746-db53218acf2e/attachment.md","path":"examples/nextauth-setup.md","size":13008,"sha256":"989e2cd805112be09d32b3c194d30c38d2380b33a0cf64b1f0f39dbf4d9f9c0d","contentType":"text/markdown; charset=utf-8"},{"id":"1b8f2681-bda6-597a-a2f8-d6d4be94ce01","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1b8f2681-bda6-597a-a2f8-d6d4be94ce01/attachment.md","path":"references/middleware-auth.md","size":10824,"sha256":"9e47d31cd6e1feb26c84fe9242def8a6fe3844aaa7064ff223bf479c0d47ef82","contentType":"text/markdown; charset=utf-8"},{"id":"97c9efcd-f616-508a-82ea-f6e0e942f4c1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/97c9efcd-f616-508a-82ea-f6e0e942f4c1/attachment.md","path":"references/session-management.md","size":10710,"sha256":"00d3ecef2935d886d5d17cd7e1665e61737245ba6b2c24e590cf19a43aebbeef","contentType":"text/markdown; charset=utf-8"}],"bundle_sha256":"8d6e47ca0eac516889a987fe316984ce25ab96c6b9ea8e71138f13e30b4696f8","attachment_count":3,"text_attachments":3,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"plugins/nextjs-expert/skills/auth-patterns/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"web-development","category_label":"Web"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"web-development","import_tag":"clean-skills-v1","description":"This skill should be used when the user asks about \"authentication in Next.js\", \"NextAuth\", \"Auth.js\", \"middleware auth\", \"protected routes\", \"session management\", \"JWT\", \"login flow\", or needs guidance on implementing authentication and authorization in Next.js applications."}},"renderedAt":1782979400450}

Authentication Patterns in Next.js Overview Next.js supports multiple authentication strategies. This skill covers common patterns including NextAuth.js (Auth.js), middleware-based protection, and session management. Authentication Libraries | Library | Best For | |---------|----------| | NextAuth.js (Auth.js) | Full-featured auth with providers | | Clerk | Managed auth service | | Lucia | Lightweight, flexible auth | | Supabase Auth | Supabase ecosystem | | Custom JWT | Full control | NextAuth.js v5 Setup Installation Configuration API Route Handler Middleware Protection Getting Session Data…