$ npx rulesync-cli pull✓ Wrote CLAUDE.md (2 rulesets)# Coding Standards- Always use async/await- Prefer named exports
Rule Writing

CLAUDE.md for Auth0 and NextAuth.js

AI builds custom JWT auth instead of using Auth0 or NextAuth. Rules for provider setup, session management, middleware protection, and callback patterns.

7 min read·October 9, 2025

AI builds custom JWT auth — Auth0 and NextAuth handle it securely in 10 lines

Provider setup, session callbacks, middleware protection, and token security

Why Auth Libraries Need 'Don't Build Custom Auth' Rules

Authentication is the most security-critical code in any application — and the code AI assistants get wrong most dangerously. AI generates custom JWT implementations: manual bcrypt hashing, hand-rolled token generation, DIY session management, and custom OAuth flows. Every custom implementation is a potential security vulnerability. Auth0 and NextAuth.js have been audited, battle-tested, and handle edge cases (token rotation, CSRF, session fixation) that AI code doesn't.

The rule is simple: never build custom auth when a library handles it. Auth0 provides hosted authentication with enterprise features. NextAuth.js (Auth.js v5) provides self-hosted authentication for Next.js with multiple providers. Both eliminate hundreds of lines of security-critical code.

These rules cover NextAuth.js v5 (Auth.js) for self-hosted auth and Auth0 for hosted auth. Specify which your project uses — the integration patterns differ significantly.

Rule 1: NextAuth.js Provider Configuration

The rule: 'Configure auth providers in auth.ts: export const { handlers, signIn, signOut, auth } = NextAuth({ providers: [Google({ clientId, clientSecret }), GitHub({ clientId, clientSecret }), Credentials({ ... })], adapter: DrizzleAdapter(db) }). Mount handlers in app/api/auth/[...nextauth]/route.ts: export { handlers as GET, handlers as POST }. Never implement OAuth flows manually — NextAuth handles the entire flow.'

For Credentials provider: 'Use Credentials only for email/password when OAuth isn't possible. Define authorize: async (credentials) => { const user = await db.getUserByEmail(credentials.email); if (!user || !await bcrypt.compare(credentials.password, user.passwordHash)) return null; return user; }. Credentials provider doesn't support sessions in the database by default — use JWT strategy.'

For session management: 'Use database sessions (strategy: "database") with an adapter for: user management, account linking, and session revocation. Use JWT sessions (strategy: "jwt") for: serverless deployments, stateless auth, and Credentials provider. Database sessions are more secure (revocable); JWT sessions are more scalable (no DB lookup per request).'

  • NextAuth({ providers, adapter }) — never manual OAuth or JWT
  • Google, GitHub, Discord providers — one line each to configure
  • Credentials provider only when OAuth isn't possible — bcrypt + authorize
  • Database sessions: revocable, account linking — JWT sessions: stateless, serverless
  • DrizzleAdapter, PrismaAdapter — never manual session table management
⚠️ Never Manual OAuth

AI implements OAuth flows from scratch — authorization URL, callback parsing, token exchange. NextAuth handles the entire OIDC flow. One provider config line replaces 100 lines of security-critical custom code.

Rule 2: Session and JWT Callbacks

The rule: 'Use callbacks to customize the session: callbacks: { session: ({ session, user }) => ({ ...session, user: { ...session.user, id: user.id, role: user.role } }), jwt: ({ token, user }) => { if (user) { token.id = user.id; token.role = user.role; } return token; } }. This adds custom fields (id, role) to the session object. Never query the database in every component to get user details — put them in the session.'

For the auth helper: 'Use the auth() function for server-side session access: const session = await auth(); if (!session) redirect("/login"). In client components: use useSession() from next-auth/react. In API routes: const session = await auth(); if (!session) return Response.json({ error: "Unauthorized" }, { status: 401 }).'

AI generates custom middleware that parses JWTs manually, queries the database per request, and stores user data in context. NextAuth's auth() function returns the session with your custom fields — one call, typed, with all the data you configured in callbacks.

Rule 3: Middleware for Route Protection

The rule: 'Use Next.js middleware with NextAuth for route protection: export { auth as middleware } from "@/auth"; export const config = { matcher: ["/dashboard/:path*", "/api/:path*"] }. This protects all matched routes — unauthenticated requests redirect to the sign-in page. For role-based protection, check session.user.role in the middleware or use NextAuth's authorized callback.'

For API protection: 'Protect API routes by checking the session: export async function GET(req: Request) { const session = await auth(); if (!session) return new Response("Unauthorized", { status: 401 }); ... }. Never rely on client-side auth checks alone — the API must independently verify authentication. Middleware handles page routes; explicit checks handle API routes.'

For the authorized callback: 'Use authorized in NextAuth config for custom middleware logic: callbacks: { authorized: ({ auth, request }) => { if (request.nextUrl.pathname.startsWith("/admin")) return auth?.user?.role === "admin"; return !!auth; } }. This runs in middleware — fast, edge-compatible, before the page renders.'

💡 Middleware = Edge Auth

export { auth as middleware } protects routes at the edge — before the page renders, before the API handler runs. Fast, consistent, impossible to bypass. authorized callback adds role checks in the same middleware.

Rule 4: Auth0 Integration Patterns

The rule: 'For Auth0: use the official @auth0/nextjs-auth0 SDK. Configure in middleware and api/auth routes. The SDK handles: login redirect, callback processing, session management, and token refresh. Access the session with getSession(). Use withMiddlewareAuthRequired for route protection. Use withApiAuthRequired for API protection. Never call Auth0's API directly for login flows — the SDK handles the entire OIDC flow.'

For custom claims: 'Add custom data to Auth0 tokens with Actions (Auth0 dashboard). Access in your app: const { user } = await getSession(); user.org_id, user.role. Actions run during the login flow — they modify the ID token and access token with your custom fields. Never query Auth0's Management API per request — cache user data after login.'

For the decision: 'Auth0 for: enterprise SSO (SAML, OIDC), compliance requirements (SOC 2, HIPAA), managed infrastructure, and teams that don't want to maintain auth. NextAuth.js for: full control, self-hosted, open source, lower cost at scale, and projects already on Next.js. Both are correct choices — pick based on your requirements.'

  • Auth0: @auth0/nextjs-auth0 SDK — never manual OIDC flow
  • getSession() for session — withMiddlewareAuthRequired for routes
  • Actions for custom claims — modify tokens during login flow
  • Auth0 for: enterprise SSO, compliance, managed — NextAuth for: self-hosted, control, cost
  • Never Auth0 Management API per request — cache after login

Rule 5: Auth Security Patterns

The rule: 'Set secure cookie options: cookies: { sessionToken: { options: { httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax" } } }. Enable CSRF protection (NextAuth includes it by default). Use short-lived access tokens (15 min) with refresh rotation. Store refresh tokens server-side only — never in localStorage or client-accessible cookies.'

For token security: 'Never expose access tokens to client-side JavaScript — use httpOnly cookies. If your API needs bearer tokens, use NextAuth's jwt callback to include them in the encrypted session cookie — the server reads them, the client never sees them. For external APIs, exchange the session for an API-specific token on the server.'

For account linking: 'Use NextAuth's adapter for automatic account linking: a user who signs in with Google and later with GitHub (same email) gets linked to one account. Configure: allowDangerousEmailAccountLinking: true (only if you trust the email providers). Without linking, the same person has two separate accounts.'

ℹ️ Never Tokens in localStorage

localStorage is accessible to any JavaScript on the page — one XSS vulnerability exposes all tokens. httpOnly cookies are invisible to JavaScript. NextAuth stores sessions in encrypted httpOnly cookies by default.

Complete Auth Rules Template

Consolidated rules for authentication with Auth0 or NextAuth.js.

  • Never custom auth: NextAuth/Auth0 handle OAuth, sessions, CSRF, token rotation
  • NextAuth: providers + adapter + callbacks in auth.ts — handlers in [...nextauth]/route.ts
  • Session callbacks: add id, role to session — auth() for server, useSession() for client
  • Middleware for route protection — authorized callback for role-based access
  • Auth0: @auth0/nextjs-auth0 SDK — Actions for custom claims — never manual OIDC
  • httpOnly secure cookies — never tokens in localStorage — short-lived access tokens
  • Database adapter for account linking — session revocation — user management
  • Check session in API routes independently — never trust client-side auth alone
CLAUDE.md for Auth0 and NextAuth.js — RuleSync Blog