Skills api-auth-nextauth

Auth.js (NextAuth v5) authentication patterns - configuration, providers, session strategies, middleware, database adapters, role-based access, Edge compatibility

install
source · Clone the upstream repo
git clone https://github.com/agents-inc/skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/agents-inc/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/src/skills/api-auth-nextauth" ~/.claude/skills/agents-inc-skills-api-auth-nextauth-ead662 && rm -rf "$T"
manifest: src/skills/api-auth-nextauth/SKILL.md
source content

Auth.js (NextAuth v5) Patterns

Quick Guide: Configure Auth.js in a root

auth.ts
file exporting
{ auth, handlers, signIn, signOut }
from
NextAuth()
. Use the unified
auth()
function everywhere (Server Components, Route Handlers, middleware). Default session strategy is JWT (cookie-based); add a database adapter for persistent sessions. Protect routes via middleware or per-page
auth()
checks.


<critical_requirements>

CRITICAL: Before Using This Skill

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,

import type
, named constants)

(You MUST configure Auth.js in a root

auth.ts
file and export
{ auth, handlers, signIn, signOut }
from
NextAuth()
)

(You MUST use the unified

auth()
function for server-side session access - NOT the deprecated
getServerSession()
,
getSession()
, or
getToken()
)

(You MUST use

AUTH_SECRET
environment variable -
NEXTAUTH_SECRET
is deprecated in v5)

(You MUST use

AUTH_
prefixed environment variables for provider credentials (e.g.,
AUTH_GITHUB_ID
,
AUTH_GITHUB_SECRET
) - they are auto-detected)

(You MUST split auth config into

auth.config.ts
(Edge-compatible) and
auth.ts
(with adapter) when using database sessions with middleware)

(You MUST check session inside Server Actions and API routes - middleware alone is NOT sufficient for authorization)

</critical_requirements>


Auto-detection: Auth.js, NextAuth, next-auth, authjs, auth.ts, auth.config.ts, NextAuth(), signIn, signOut, auth(), handlers, SessionProvider, useSession, AUTH_SECRET, OAuth provider, credentials provider, database adapter, @auth/prisma-adapter, @auth/drizzle-adapter, authorized callback, jwt callback, session callback, proxy auth, middleware auth

When to use:

  • Adding authentication to Next.js, SvelteKit, Express, or Qwik apps
  • Implementing OAuth login (GitHub, Google, Discord, etc.) with 80+ built-in providers
  • Building email/magic link authentication flows
  • Need JWT or database-backed session management
  • Projects requiring Edge-compatible middleware authentication

When NOT to use:

  • Building a custom auth system from scratch (Auth.js is opinionated)
  • Need fine-grained organization/team management out of the box
  • Mobile-only apps without web frontend
  • Need self-hosted auth with plugin architecture

Key patterns covered:

  • Auth configuration (
    auth.ts
    ,
    auth.config.ts
    )
  • OAuth providers (GitHub, Google, Credentials, Email)
  • Session strategies (JWT vs database)
  • Session access (Server Components, Route Handlers, Client Components)
  • Middleware/proxy route protection
  • Database adapters (Prisma, Drizzle)
  • Callbacks (jwt, session, signIn, redirect)
  • Role-based access control
  • Edge compatibility split configuration

Detailed Resources:

  • For decision frameworks and anti-patterns, see reference.md

Core patterns:


<philosophy>

Philosophy

Auth.js (v5) consolidates authentication into a single, unified API. The

auth()
function replaces
getServerSession
,
getSession
,
withAuth
, and
getToken
from v4 for server-side use.
useSession()
remains the correct client-side API. Configuration lives in a root file, not in API routes.

Core principles:

  1. Framework-agnostic - Works with Next.js, SvelteKit, Express, Qwik
  2. Unified API - Single
    auth()
    function for all server-side contexts
  3. Provider ecosystem - 80+ built-in OAuth providers with auto-detection of
    AUTH_*
    env vars
  4. JWT by default - Stateless sessions in encrypted cookies, no database required
  5. Edge-compatible - Middleware runs on Edge runtime with split configuration (Next.js 16 proxy runs on Node.js)
  6. Progressive complexity - Start with OAuth, add database adapter, then customize callbacks
</philosophy>
<patterns>

Core Patterns

Pattern 1: Auth Configuration

The central configuration file exports everything you need from

NextAuth()
.

Basic OAuth Setup

// auth.ts
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
import Google from "next-auth/providers/google";

export const { auth, handlers, signIn, signOut } = NextAuth({
  providers: [
    GitHub, // Auto-detects AUTH_GITHUB_ID and AUTH_GITHUB_SECRET
    Google, // Auto-detects AUTH_GOOGLE_ID, AUTH_GOOGLE_SECRET
  ],
});
// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth";

export const { GET, POST } = handlers;

Why good: Single config file exports all auth utilities, providers auto-detect

AUTH_*
env vars, API route is minimal

Environment Variables

# .env.local
AUTH_SECRET="generate-with-npx-auth-secret"  # Required
AUTH_GITHUB_ID="your-github-client-id"       # Auto-detected by GitHub provider
AUTH_GITHUB_SECRET="your-github-secret"      # Auto-detected by GitHub provider
AUTH_GOOGLE_ID="your-google-id"       # Auto-detected by Google provider
AUTH_GOOGLE_SECRET="your-google-secret"

Why good:

AUTH_
prefix is standardized in v5,
AUTH_SECRET
replaces deprecated
NEXTAUTH_SECRET
, providers auto-detect credentials


Pattern 2: Providers

Auth.js supports OAuth, email/magic link, and credentials authentication. 80+ built-in OAuth providers auto-detect

AUTH_*
env vars.

// OAuth: customize profile mapping
GitHub({
  profile(profile) {
    return {
      id: String(profile.id),
      name: profile.name ?? profile.login,
      role: "user",
    };
  },
});

// Credentials: validate input, return null on failure
Credentials({
  async authorize(credentials) {
    const parsed = LoginSchema.safeParse(credentials);
    if (!parsed.success) return null;
    const user = await getUserByEmail(parsed.data.email);
    if (
      !user ||
      !(await verifyPassword(parsed.data.password, user.hashedPassword))
    )
      return null;
    return { id: user.id, name: user.name, email: user.email };
  },
});

Key rules: Validate input before DB lookup, always hash passwords, return

null
on failure (never throw - it leaks info). See examples/core.md for complete implementations.


Pattern 3: Callbacks

Four callbacks customize auth behavior. Data flows: jwt callback (enrich token) -> session callback (expose to client).

callbacks: {
  jwt({ token, user }) {
    if (user) { token.id = user.id; token.role = user.role ?? "user"; }
    return token;
  },
  session({ session, token }) {
    session.user.id = token.id as string;
    session.user.role = token.role as string;
    return session;
  },
}

Key rules: JWT callback runs on EVERY

auth()
call (keep lightweight),
user
param is only present at sign-in, never expose OAuth tokens to client. See examples/core.md for complete callback implementations including
signIn
and
redirect
.


Pattern 4: Session Access

The unified

auth()
function replaces
getServerSession
,
getSession
, and
getToken
from v4 for server-side use.
useSession()
remains for Client Components.

ContextHow to access session
Server Component
const session = await auth()
Route Handler
export const GET = auth(function GET(req) { req.auth })
Server Action
const session = await auth()
Middleware/Proxy
export { auth as middleware }
or
authorized
callback
Client Component
useSession()
(requires
SessionProvider
in layout)

Key rules: Server-side imports come from

@/auth
, client-side imports from
next-auth/react
. Never call
auth()
in Client Components. See examples/session.md for complete implementations.


Pattern 5: Sign In / Sign Out Actions

Two approaches: Server Actions (recommended, progressive enhancement) or client-side.

// Server-side (recommended): import from @/auth, use Server Actions in forms
import { signIn, signOut } from "@/auth";
// In form action: await signIn("github", { redirectTo: "/dashboard" })
// In form action: await signOut({ redirectTo: "/" })

// Client-side: import from next-auth/react, use onClick handlers
import { signIn, signOut } from "next-auth/react";
// onClick: signIn("github", { callbackUrl: "/dashboard" })

Key rules: Server-side uses

redirectTo
, client-side uses
callbackUrl
.
signIn()
throws a NEXT_REDIRECT exception internally -- don't wrap in try/catch expecting a return value. See examples/core.md for complete implementations.


Pattern 6: TypeScript Extensions

Extend session and JWT types via declaration merging in

types/next-auth.d.ts
. Declare custom fields (e.g.,
id
,
role
) on
Session
,
User
, and
JWT
interfaces using
& DefaultSession["user"]
to preserve defaults. See examples/core.md for the complete type declaration example.

</patterns>
<integration>

Integration Guide

Auth.js is the authentication layer. It handles identity verification, session management, and route protection. It does NOT handle authorization logic (role checks, permission systems) -- that is application code.

Framework support: Auth.js works with multiple web frameworks via framework-specific packages (

next-auth
,
@auth/sveltekit
,
@auth/express
).

Database adapters: For database sessions, Auth.js provides adapter packages (

@auth/prisma-adapter
,
@auth/drizzle-adapter
, etc.) that integrate with your ORM. See examples/database.md.

Session strategy depends on your needs:

  • JWT (default) - No database needed, works on Edge, stateless
  • Database - Requires adapter, server-side session store, supports immediate revocation

Auth.js does NOT handle: fine-grained authorization/RBAC, rate limiting, or database queries beyond auth -- those are application-level concerns.

</integration>

<red_flags>

RED FLAGS

  • Using
    getServerSession(authOptions)
    -- deprecated in v5; use
    auth()
    from your
    auth.ts
  • Using
    NEXTAUTH_SECRET
    or
    NEXTAUTH_URL
    -- deprecated; use
    AUTH_SECRET
    (URL is auto-detected)
  • Credentials provider without rate limiting -- vulnerable to brute-force attacks
  • Exposing OAuth tokens to client via session callback -- keep
    accessToken
    /
    refreshToken
    server-side only
  • Middleware/proxy as sole authorization -- runs before rendering but does not replace per-route checks in Server Actions/API routes
  • Database adapter imported in middleware -- database ORMs can't run on Edge runtime (Next.js 14/15); split config into
    auth.config.ts
    +
    auth.ts
  • Wrapping
    signIn()
    in try/catch
    -- it throws a NEXT_REDIRECT exception internally (this is intentional)
  • JWT callback querying database on every call -- runs on EVERY
    auth()
    invocation; keep it lightweight

See reference.md for the complete anti-pattern list, gotchas, and migration table.

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

All code must follow project conventions in CLAUDE.md

(You MUST configure Auth.js in a root

auth.ts
file and export
{ auth, handlers, signIn, signOut }
from
NextAuth()
)

(You MUST use the unified

auth()
function for server-side session access - NOT the deprecated
getServerSession()
,
getSession()
, or
getToken()
)

(You MUST use

AUTH_SECRET
environment variable -
NEXTAUTH_SECRET
is deprecated in v5)

(You MUST use

AUTH_
prefixed environment variables for provider credentials (e.g.,
AUTH_GITHUB_ID
,
AUTH_GITHUB_SECRET
) - they are auto-detected)

(You MUST split auth config into

auth.config.ts
(Edge-compatible) and
auth.ts
(with adapter) when using database sessions with middleware)

(You MUST check session inside Server Actions and API routes - middleware alone is NOT sufficient for authorization)

Failure to follow these rules will cause authentication failures, expose deprecated patterns, or create security vulnerabilities.

</critical_reminders>