--- name: "NextAuth.js Authentication" description: "Implement authentication with NextAuth.js v5, Google OAuth, credentials provider, session management, and protected routes. Apply when building auth flows, protecting routes, managing sessions, or implementing RBAC." allowed-tools: Read, Write, Edit, Bash version: 1.1.0 compatibility: Claude Opus 4.5, Claude Code v2.x updated: 2026-01-24 --- # NextAuth.js Authentication Systematic authentication implementation with NextAuth.js v5, OAuth providers, and session management. ## Overview This Skill enforces: - NextAuth.js v5 configuration - Google OAuth and credentials providers - Session management with JWT - Protected routes with middleware - Role-based access control (RBAC) - Secure password hashing - Token refresh strategies Apply when implementing authentication, protecting routes, or managing user sessions. ## Setup NextAuth.js v5 ### Install Dependencies ```bash npm install next-auth@beta bcryptjs npm install -D @types/bcryptjs ``` ### Create Auth Configuration ```ts // lib/auth.ts import NextAuth from 'next-auth'; import Google from 'next-auth/providers/google'; import Credentials from 'next-auth/providers/credentials'; import { PrismaAdapter } from '@auth/prisma-adapter'; import { db } from '@/lib/db'; import bcrypt from 'bcryptjs'; export const { handlers, auth, signIn, signOut } = NextAuth({ adapter: PrismaAdapter(db), session: { strategy: 'jwt' }, providers: [ Google({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET! }), 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.user.findUnique({ where: { email: credentials.email as string } }); if (!user || !user.passwordHash) { return null; } const isValid = await bcrypt.compare( credentials.password as string, user.passwordHash ); if (!isValid) { return null; } return { id: user.id, email: user.email, name: user.name, role: user.role }; } }) ], callbacks: { async jwt({ token, user }) { if (user) { token.id = user.id; token.role = user.role; } return token; }, async session({ session, token }) { if (session.user) { session.user.id = token.id as string; session.user.role = token.role as string; } return session; } }, pages: { signIn: '/login', error: '/auth/error' }, secret: process.env.NEXTAUTH_SECRET }); ``` ### Environment Variables ``` # .env.local NEXTAUTH_URL=http://localhost:3000 NEXTAUTH_SECRET=your-secret-key-here GOOGLE_CLIENT_ID=your-google-client-id GOOGLE_CLIENT_SECRET=your-google-client-secret ``` ## Google OAuth Setup ### Create Google OAuth Credentials 1. Go to [Google Cloud Console](https://console.cloud.google.com/) 2. Create new project or select existing 3. Enable Google+ API 4. Go to Credentials → Create Credentials → OAuth 2.0 Client ID 5. Set authorized redirect URIs: - Development: `http://localhost:3000/api/auth/callback/google` - Production: `https://yourdomain.com/api/auth/callback/google` ### Configure Google Provider ```ts // lib/auth.ts Google({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, authorization: { params: { prompt: 'consent', access_type: 'offline', response_type: 'code' } } }) ``` ## API Route Handler ```ts // app/api/auth/[...nextauth]/route.ts import { handlers } from '@/lib/auth'; export const { GET, POST } = handlers; ``` ## Sign In Page ```tsx // app/login/page.tsx 'use client'; import { signIn } from 'next-auth/react'; import { useState } from 'react'; import { useRouter } from 'next/navigation'; export default function LoginPage() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); const router = useRouter(); const handleCredentialsSignIn = async (e: React.FormEvent) => { e.preventDefault(); const result = await signIn('credentials', { email, password, redirect: false }); if (result?.error) { setError('Invalid credentials'); } else { router.push('/dashboard'); } }; const handleGoogleSignIn = () => { signIn('google', { callbackUrl: '/dashboard' }); }; return (

Sign In

{/* Google Sign In */}
OR
{/* Credentials Sign In */}
setEmail(e.target.value)} placeholder="Email" required /> setPassword(e.target.value)} placeholder="Password" required /> {error &&

{error}

}
); } ``` ## Sign Up (Registration) ```tsx // app/api/auth/register/route.ts import { NextResponse } from 'next/server'; import bcrypt from 'bcryptjs'; import { db } from '@/lib/db'; export async function POST(request: Request) { try { const { email, password, name } = await request.json(); // Validate input if (!email || !password || !name) { return NextResponse.json( { error: 'Missing required fields' }, { status: 400 } ); } // Check if user exists const existingUser = await db.user.findUnique({ where: { email } }); if (existingUser) { return NextResponse.json( { error: 'User already exists' }, { status: 409 } ); } // Hash password const passwordHash = await bcrypt.hash(password, 10); // Create user const user = await db.user.create({ data: { email, name, passwordHash } }); return NextResponse.json( { message: 'User created', userId: user.id }, { status: 201 } ); } catch (error) { return NextResponse.json( { error: 'Failed to create user' }, { status: 500 } ); } } ``` ## Protected Routes with Middleware ```ts // middleware.ts import { auth } from '@/lib/auth'; import { NextResponse } from 'next/server'; export default auth((req) => { const { pathname } = req.nextUrl; const session = req.auth; // Public routes const publicRoutes = ['/', '/login', '/register']; if (publicRoutes.includes(pathname)) { return NextResponse.next(); } // Require authentication if (!session) { return NextResponse.redirect(new URL('/login', req.url)); } // Admin-only routes if (pathname.startsWith('/admin')) { if (session.user.role !== 'admin') { return NextResponse.redirect(new URL('/dashboard', req.url)); } } return NextResponse.next(); }); export const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'] }; ``` ## Server-Side Session Access ```tsx // app/dashboard/page.tsx import { auth } from '@/lib/auth'; import { redirect } from 'next/navigation'; export default async function DashboardPage() { const session = await auth(); if (!session) { redirect('/login'); } return (

Dashboard

Welcome, {session.user.name}!

Email: {session.user.email}

Role: {session.user.role}

); } ``` ## Client-Side Session Access ```tsx // app/components/UserProfile.tsx 'use client'; import { useSession, signOut } from 'next-auth/react'; export function UserProfile() { const { data: session, status } = useSession(); if (status === 'loading') { return
Loading...
; } if (!session) { return
Not signed in
; } return (

{session.user.name}

{session.user.email}

); } ``` ## Role-Based Access Control (RBAC) ```tsx // lib/rbac.ts export const PERMISSIONS = { admin: ['create', 'read', 'update', 'delete'], editor: ['create', 'read', 'update'], viewer: ['read'] }; export function hasPermission(role: string, action: string): boolean { return PERMISSIONS[role as keyof typeof PERMISSIONS]?.includes(action) ?? false; } // Usage in Server Component import { auth } from '@/lib/auth'; import { hasPermission } from '@/lib/rbac'; export default async function Page() { const session = await auth(); if (!session) { redirect('/login'); } const canDelete = hasPermission(session.user.role, 'delete'); return (
{canDelete && }
); } ``` ## Session Provider for Client Components ```tsx // app/providers.tsx 'use client'; import { SessionProvider } from 'next-auth/react'; export function Providers({ children }: { children: React.ReactNode }) { return {children}; } // app/layout.tsx import { Providers } from './providers'; export default function RootLayout({ children }: { children: React.ReactNode; }) { return ( {children} ); } ``` ## Anti-Patterns ```tsx // ❌ BAD: Storing password plaintext const user = await db.user.create({ data: { password: password // NEVER! } }); // ✅ GOOD: Hash password const passwordHash = await bcrypt.hash(password, 10); const user = await db.user.create({ data: { passwordHash } }); // ❌ BAD: No session check export default function DashboardPage() { return
Dashboard
; } // ✅ GOOD: Check session export default async function DashboardPage() { const session = await auth(); if (!session) redirect('/login'); return
Dashboard
; } // ❌ BAD: Client-side only protection 'use client'; export default function Page() { const { data: session } = useSession(); if (!session) return
Not allowed
; // Still accessible by disabling JS! } // ✅ GOOD: Server-side protection export default async function Page() { const session = await auth(); if (!session) redirect('/login'); return
Protected content
; } ``` ## Verification Before Production - [ ] NextAuth.js v5 configured - [ ] Google OAuth credentials set up - [ ] Credentials provider working - [ ] Passwords hashed with bcrypt (never plaintext) - [ ] Session management with JWT - [ ] Middleware protecting routes - [ ] RBAC implemented for admin routes - [ ] Sign in/sign up pages functional - [ ] Session provider wraps app - [ ] Environment variables configured - [ ] NEXTAUTH_SECRET set (never commit) ## Integration with Project Standards Enforces security best practices: - S-1: Passwords hashed with bcrypt - S-5: Secrets in environment variables - AP-1: Authentication required for admin routes - S-8: RBAC for authorization ## Resources - NextAuth.js v5: https://authjs.dev - Google OAuth Setup: https://console.cloud.google.com - Prisma Adapter: https://authjs.dev/reference/adapter/prisma --- **Last Updated:** January 24, 2026 **Compatibility:** Claude Opus 4.5, Claude Code v2.x **Status:** Production Ready > **January 2026 Update:** This skill is compatible with Claude Opus 4.5 and Claude Code v2.x. For complex tasks, use the `effort: high` parameter for thorough analysis.