JSON Web Tokens sind der de-facto Standard für zustandslose Authentifizierung in modernen Web-Apps — und einer der häufigsten Quellen von Sicherheitslücken. In diesem Guide zeigen wir, wie Claude Code eine vollständige, sichere JWT-Implementierung für Next.js generiert: von der Token-Struktur über Refresh-Rotation bis hin zu Revocation und Middleware.
1. JWT Struktur: Header, Payload, Signature
Ein JWT besteht aus drei Base64URL-kodierten Teilen, getrennt durch Punkte:
Token-Anatomie
.
eyJ1c2VySWQiOiI0MiIsInJvbGUiOiJ1c2VyIn0
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
| Teil | Inhalt | Veränderbar? |
| Algorithmus (HS256/RS256) + Typ | Prüfen! |
| Payload | Claims: sub, exp, iat, role … | Öffentlich lesbar |
| Signature | HMAC/RSA über Header+Payload | Unfälschbar |
⚠️ Wichtig: Payload-Daten sind nur Base64-kodiert, nicht verschlüsselt. Speichere niemals Passwörter, Kreditkarten oder sensible PII im JWT-Payload!
Claude Code generiert den Token-Kern in wenigen Sekunden. Prompt-Beispiel:
// Prompt an Claude Code:
// "Erstelle eine JWT-Utility mit sign/verify für HS256 und RS256,
// TypeScript, jose-Library, strict typing"
import { SignJWT, jwtVerify, generateKeyPair } from 'jose'
export interface TokenPayload {
sub: string // User-ID
role: 'user' | 'admin'
iat?: number
exp?: number
}
const SECRET = new TextEncoder().encode(process.env.JWT_SECRET!)
export async function signAccessToken(payload: TokenPayload): Promise<string> {
return new SignJWT(payload)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('15m') // Kurze Laufzeit!
.sign(SECRET)
}
export async function verifyToken(token: string): Promise<TokenPayload> {
const { payload } = await jwtVerify(token, SECRET, {
algorithms: ['HS256'], // NIEMALS ['*'] oder 'none' erlauben!
})
return payload as TokenPayload
}
2. Access Token + Refresh Token Pattern
Zwei-Token-Strategie
Access Token — 15 min
Refresh Token — 7 Tage
Access Tokens sind kurzlebig → kompromittierte Tokens werden schnell ungültig. Refresh Tokens rotieren bei jeder Nutzung und werden in der DB revoked.
Claude Code generiert den kompletten Auth-Flow inklusive DB-Schema:
// lib/auth/tokens.ts — von Claude Code generiert
import { db } from '@/lib/db'
import { randomBytes } from 'crypto'
export async function issueTokenPair(userId: string, role: string) {
const accessToken = await signAccessToken({ sub: userId, role })
const refreshToken = randomBytes(64).toString('hex')
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
await db.refreshToken.create({
data: { token: refreshToken, userId, expiresAt, used: false }
})
return { accessToken, refreshToken }
}
export async function rotateRefreshToken(oldToken: string) {
const record = await db.refreshToken.findUnique({ where: { token: oldToken }})
if (!record || record.used || record.expiresAt < new Date()) {
// Refresh Token Reuse Detection: alle Tokens des Users invalidieren!
if (record?.used) await revokeAllUserTokens(record.userId)
throw new Error('Invalid refresh token')
}
await db.refreshToken.update({
where: { token: oldToken },
data: { used: true } // Alten Token sperren
})
return issueTokenPair(record.userId, record.role)
}
✅ Best Practice: Refresh Token Reuse Detection — wenn ein bereits genutzter Refresh Token erneut eingesetzt wird, liegt höchstwahrscheinlich ein Token-Diebstahl vor. Alle Sessions des Users sofort invalidieren!
3. JWT in Next.js: httpOnly Cookie vs. localStorage
| Speicherort | XSS-sicher | CSRF-Risiko | Empfehlung |
| localStorage | ❌ Nein | ✅ Nein | Nicht für JWTs! |
| sessionStorage | ❌ Nein | ✅ Nein | Nicht für Auth |
| httpOnly Cookie | ✅ Ja | ⚠️ Mitigation nötig | ✅ Empfohlen |
| Memory (React State) | ✅ Ja | ✅ Nein | Für kurze Sessions |
// app/api/auth/login/route.ts — Next.js App Router
import { NextResponse } from 'next/server'
import { cookies } from 'next/headers'
export async function POST(req: Request) {
const { email, password } = await req.json()
const user = await authenticateUser(email, password)
if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
const { accessToken, refreshToken } = await issueTokenPair(user.id, user.role)
const cookieStore = await cookies()
// Refresh Token: httpOnly, Secure, SameSite=Strict
cookieStore.set('refresh_token', refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 7, // 7 Tage
path: '/api/auth', // Nur Auth-Endpunkte
})
// Access Token: kurzlebig, für Client sichtbar (kein httpOnly)
return NextResponse.json({ accessToken, expiresIn: 900 })
}
4. Token Rotation und Revocation Strategien
Rotation-Endpunkt
// app/api/auth/refresh/route.ts
export async function POST(req: Request) {
const cookieStore = await cookies()
const oldRefresh = cookieStore.get('refresh_token')?.value
if (!oldRefresh) return NextResponse.json({ error: 'No token' }, { status: 401 })
try {
const { accessToken, refreshToken } = await rotateRefreshToken(oldRefresh)
cookieStore.set('refresh_token', refreshToken, {
httpOnly: true, secure: true, sameSite: 'strict',
maxAge: 60 * 60 * 24 * 7, path: '/api/auth'
})
return NextResponse.json({ accessToken })
} catch {
cookieStore.delete('refresh_token') // Session komplett beenden
return NextResponse.json({ error: 'Token invalid' }, { status: 401 })
}
}
Revocation via Denylist
Da Access Tokens zustandslos sind, werden sie nach Ablauf automatisch ungültig. Für sofortige Invalidierung (z. B. Logout, Passwort-Reset) nutzt Claude Code eine Redis-Denylist:
// lib/auth/revocation.ts
import { redis } from '@/lib/redis'
export async function revokeAccessToken(token: string, exp: number) {
const ttl = exp - Math.floor(Date.now() / 1000)
if (ttl > 0) await redis.setex(`revoked:${hashToken(token)}`, ttl, '1')
}
export async function isRevoked(token: string): Promise<boolean> {
return !!(await redis.get(`revoked:${hashToken(token)}`))
}
export async function revokeAllUserTokens(userId: string) {
await db.refreshToken.updateMany({
where: { userId, used: false },
data: { used: true }
})
await redis.set(`user_revoked:${userId}`, Date.now(), { ex: 86400 })
}
5. JWT Pitfalls: Was Claude Code automatisch verhindert
Häufige Fehler und ihre Fixes
| Pitfall | Risiko | Fix |
| Algorithm Confusion |
alg: "none" → kein Verify |
Whitelist erlaubter Algorithmen |
| Kein exp-Claim |
Token gilt ewig |
setExpirationTime('15m') Pflicht |
| Schwacher Secret |
Brute-Force möglich |
≥256 bit Secret, rotieren |
| JWT in localStorage |
XSS → Token-Diebstahl |
httpOnly Cookie |
| Kein Reuse Detection |
Replay-Angriff möglich |
used-Flag + Revocation |
⚠️ Algorithm Confusion Attack: Manche Libraries erlauben alg: "none" oder wechseln von RS256 zu HS256 mit dem Public Key als Secret. Immer explizit algorithms: ['HS256'] setzen!
6. Middleware für geschützte Routen
Claude Code erstellt Next.js Middleware, die alle geschützten Routen automatisch absichert — ohne dass jede API-Route eigene Auth-Logik braucht:
// middleware.ts (Next.js Root)
import { NextRequest, NextResponse } from 'next/server'
import { verifyToken } from '@/lib/auth/jwt'
import { isRevoked } from '@/lib/auth/revocation'
const PUBLIC_ROUTES = ['/login', '/signup', '/api/auth', '/blog']
export async function middleware(req: NextRequest) {
const { pathname } = req.nextUrl
// Public Routes überspringen
if (PUBLIC_ROUTES.some(r => pathname.startsWith(r)))
return NextResponse.next()
const authHeader = req.headers.get('authorization')
const token = authHeader?.replace('Bearer ', '')
if (!token) return unauthorized()
try {
const payload = await verifyToken(token)
// Denylist-Check (Redis)
if (await isRevoked(token)) return unauthorized()
// User-Info in Request-Header injizieren
const res = NextResponse.next()
res.headers.set('x-user-id', payload.sub)
res.headers.set('x-user-role', payload.role)
return res
} catch {
return unauthorized()
}
}
function unauthorized() {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*', '/admin/:path*']
}
✅ Middleware-Vorteil: Middleware läuft auf der Edge Runtime — extrem schnell, bevor der Request die eigentliche Route erreicht. Kein Auth-Boilerplate in jeder Route nötig.
7. Secret Management und Key Rotation
// .env.local — NIEMALS committen!
JWT_SECRET=$(openssl rand -base64 64)
JWT_SECRET_PREVIOUS=alter_secret_fuer_graceful_rotation
// lib/auth/jwt.ts — Graceful Key Rotation
const SECRETS = [
process.env.JWT_SECRET!,
process.env.JWT_SECRET_PREVIOUS, // Fallback für alte Tokens
].filter(Boolean).map(s => new TextEncoder().encode(s))
export async function verifyTokenWithRotation(token: string) {
for (const secret of SECRETS) {
try { return await jwtVerify(token, secret, { algorithms: ['HS256'] }) }
catch {}
}
throw new Error('Invalid token')
}
✅ Claude Code Workflow: Gib Claude Code dein Datenbankschema und deine Next.js-Projektstruktur. Es generiert den kompletten Auth-Stack inklusive Tests, Prisma-Schema, Redis-Config und Middleware — in unter 5 Minuten produktionsreif.
Zusammenfassung: JWT Security Checklist
- ✅ Algorithmus explizit whitelist-en (kein
none)
- ✅ Access Token max. 15 Minuten Laufzeit
- ✅ Refresh Token Rotation mit Reuse Detection
- ✅ httpOnly + Secure + SameSite=Strict Cookie
- ✅ Redis Denylist für sofortige Revocation
- ✅ Secret ≥ 256 bit, in Vault / Env, niemals in Code
- ✅ Middleware schützt alle Routen zentral
- ✅ Kein PII im Payload
Mit Claude Code sicher starten
JWT, OAuth, Passkeys — Claude Code implementiert jeden Auth-Stack produktionsreif. Teste es jetzt kostenlos.
Kostenlosen Trial starten →