Claude Code & JWT: Sichere Authentifizierung implementieren
JWT-Authentifizierung ist eines jener Themen, bei denen die Grundidee schnell verstanden ist — aber die sichere Umsetzung steckt voller Details, die man beim ersten Mal fast zwangsläufig falsch macht: Secrets im Code, zu lange Token-Laufzeiten, fehlende Signaturverifikation, kein Refresh-Mechanismus. Claude Code beschleunigt nicht nur die Implementierung, sondern hilft dabei, genau diese Pitfalls von Anfang an zu vermeiden.
Dieser Artikel zeigt, wie du mit Claude Code eine vollständige JWT-Authentifizierung in Node.js aufbaust — von den Grundlagen bis zu NestJS Guards und JWKS.
Claude Code Mastery — Authentication, Agents, Hooks auf Deutsch
JWT ist ein Thema im Kurs. Lerne außerdem Agents, MCP-Server, Hooks und produktive Entwicklungsworkflows mit Claude Code — vollständig auf Deutsch, einmalig bezahlt.
Zum Kurs — Jetzt starten → Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht1. JWT-Grundlagen: Header, Payload, Signature
Ein JSON Web Token besteht aus drei Base64url-codierten Teilen, getrennt durch Punkte: header.payload.signature. Der Header beschreibt den Token-Typ und den verwendeten Algorithmus. Der Payload enthält die Claims — also die eigentlichen Daten wie User-ID, Rollen oder Ablaufzeit. Die Signature stellt sicher, dass der Token nicht manipuliert wurde.
// Beispiel eines decodierten JWT-Payloads
{
"sub": "user_123",
"email": "user@example.com",
"role": "admin",
"iat": 1725955200,
"exp": 1725958800
}
Entscheidend: Ein JWT ist signiert, nicht verschlüsselt. Der Payload ist lesbar für jeden, der den Token hat. Speichere keine sensiblen Informationen wie Passwörter oder Zahlungsdaten im Payload. Was der JWT garantiert, ist die Integrität — dass die Daten nicht verändert wurden — und die Authentizität — dass der Token vom Server ausgestellt wurde, der das Secret kennt.
Claude Code Tipp: Übergib Claude Code deinen bestehenden Auth-Code und frage explizit: "Prüfe diesen Code auf JWT-Sicherheitslücken." Claude Code liest alle relevanten Dateien und nennt konkrete Probleme — nicht generische Warnungen.
2. Node.js: jsonwebtoken-Library einrichten
Die Standardlibrary für JWT in Node.js ist jsonwebtoken. Installation und Grundstruktur:
npm install jsonwebtoken
npm install --save-dev @types/jsonwebtoken
import jwt from 'jsonwebtoken';
const JWT_SECRET = process.env.JWT_SECRET;
const JWT_EXPIRES_IN = '15m';
export function signToken(payload: object): string {
if (!JWT_SECRET) throw new Error('JWT_SECRET not set');
return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
}
export function verifyToken(token: string): jwt.JwtPayload {
if (!JWT_SECRET) throw new Error('JWT_SECRET not set');
return jwt.verify(token, JWT_SECRET) as jwt.JwtPayload;
}
Das Secret kommt ausschließlich aus der Umgebungsvariable process.env.JWT_SECRET — niemals hartcodiert im Code. Claude Code prüft beim Review automatisch, ob Secrets versehentlich in den Code geschrieben wurden, und warnt entsprechend.
3. Access Token + Refresh Token Pattern
Das größte Sicherheitsproblem bei JWTs ist eine zu lange Laufzeit. Ein gestohlener Token mit 24-Stunden-Gültigkeit kann 24 Stunden lang missbraucht werden. Die Lösung: kurze Access Tokens kombiniert mit langlebigen Refresh Tokens.
export function generateTokenPair(userId: string) {
const accessToken = jwt.sign(
{ sub: userId, type: 'access' },
process.env.JWT_SECRET!,
{ expiresIn: '15m' }
);
const refreshToken = jwt.sign(
{ sub: userId, type: 'refresh' },
process.env.JWT_REFRESH_SECRET!,
{ expiresIn: '7d' }
);
return { accessToken, refreshToken };
}
export function refreshAccessToken(refreshToken: string): string {
const payload = jwt.verify(
refreshToken,
process.env.JWT_REFRESH_SECRET!
) as jwt.JwtPayload;
if (payload.type !== 'refresh') {
throw new Error('Invalid token type');
}
return jwt.sign(
{ sub: payload.sub, type: 'access' },
process.env.JWT_SECRET!,
{ expiresIn: '15m' }
);
}
Access Tokens leben 15 Minuten, Refresh Tokens 7 Tage. Der Client speichert den Refresh Token sicher (HttpOnly Cookie) und tauscht ihn bei Bedarf gegen einen neuen Access Token aus. Der Server validiert dabei den Typ des Tokens — ein Access Token darf nicht zum Refreshen verwendet werden.
4. Secret sicher speichern
Das JWT_SECRET ist der kritischste Teil der gesamten Authentifizierung. Wer es kennt, kann beliebige gültige Tokens ausstellen. Drei Regeln:
- Niemals in den Code commiten.
.envin.gitignoreeintragen, auf Produktionssystemen Umgebungsvariablen über den Deployment-Prozess setzen. - Mindestens 256 Bit Entropie. Kein selbst gewählter String, sondern kryptografisch zufällig generiert:
openssl rand -hex 32 - Access und Refresh verwenden unterschiedliche Secrets. Wenn ein Secret kompromittiert wird, sind nur Tokens eines Typs betroffen.
# .env (niemals in Git)
JWT_SECRET=<openssl rand -hex 32 ausgabe>
JWT_REFRESH_SECRET=<openssl rand -hex 32 ausgabe>
Häufiger Fehler: Das Secret direkt im Code als Fallback zu hinterlegen: process.env.JWT_SECRET || 'fallback-secret'. Das ist gefährlich — in Produktionsumgebungen ohne gesetzte Umgebungsvariable läuft die App mit einem bekannten, unsicheren Secret. Besser: explizit werfen, wenn das Secret fehlt.
5. Middleware für Express und Fastify
Die Middleware validiert den Token bei jedem geschützten Request und legt die User-Daten am Request-Objekt ab:
Express Middleware
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
export function authenticate(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing token' });
}
const token = authHeader.slice(7);
try {
const payload = jwt.verify(token, process.env.JWT_SECRET!) as jwt.JwtPayload;
req.user = { id: payload.sub!, role: payload.role };
next();
} catch {
return res.status(401).json({ error: 'Invalid or expired token' });
}
}
Fastify Plugin
import fp from 'fastify-plugin';
import jwt from 'jsonwebtoken';
export default fp(async (fastify) => {
fastify.decorate('authenticate', async (request: any, reply: any) => {
const token = request.headers.authorization?.slice(7);
if (!token) return reply.status(401).send({ error: 'Missing token' });
try {
request.user = jwt.verify(token, process.env.JWT_SECRET!);
} catch {
return reply.status(401).send({ error: 'Invalid token' });
}
});
});
6. Token-Blacklisting
JWTs sind zustandslos — einmal ausgestellt, gelten sie bis zur Ablaufzeit. Das ist ein Problem beim Logout: Der Client löscht den Token, aber ein Angreifer, der den Token kopiert hat, kann ihn weiter verwenden. Die Lösung ist eine Blacklist für invalidierte Tokens.
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);
export async function blacklistToken(token: string, expiresIn: number) {
await redis.setex(`blacklist:${token}`, expiresIn, '1');
}
export async function isBlacklisted(token: string): Promise {
const result = await redis.get(`blacklist:${token}`);
return result !== null;
}
// In der Middleware
const blacklisted = await isBlacklisted(token);
if (blacklisted) {
return res.status(401).json({ error: 'Token revoked' });
}
Die TTL der Redis-Einträge entspricht der Restlaufzeit des Tokens — danach bereinigt sich die Blacklist automatisch. Für Access Tokens mit 15-minütiger Laufzeit ist die Blacklist damit minimal belastet.
7. JWKS und RS256
Für verteilte Systeme — mehrere Services, die Tokens validieren müssen — ist das symmetrische HS256 problematisch: Jeder Service braucht das Secret, und ein kompromittierter Service gefährdet alle anderen. Die Lösung ist RS256 mit einem asymmetrischen Schlüsselpaar.
# Schlüsselpaar generieren
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem
// Ausstellen mit Private Key (nur Auth-Service)
const token = jwt.sign(payload, process.env.JWT_PRIVATE_KEY!, {
algorithm: 'RS256',
expiresIn: '15m',
keyid: 'key-2026-09'
});
// Validieren mit Public Key (alle Services)
const decoded = jwt.verify(token, process.env.JWT_PUBLIC_KEY!, {
algorithms: ['RS256']
});
Der Public Key kann über einen JWKS-Endpoint (JSON Web Key Set) öffentlich verfügbar gemacht werden. Services laden ihn beim Start und cachen ihn. Der Private Key bleibt ausschließlich beim Auth-Service.
8. NestJS Guards
NestJS bietet mit Guards eine elegante Abstraktion für Authentifizierung und Autorisierung:
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise {
const request = context.switchToHttp().getRequest();
const token = this.extractToken(request);
if (!token) throw new UnauthorizedException();
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: process.env.JWT_SECRET,
});
request['user'] = payload;
} catch {
throw new UnauthorizedException();
}
return true;
}
private extractToken(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}
// Verwendung am Controller
@UseGuards(JwtAuthGuard)
@Get('profile')
getProfile(@Request() req) {
return req.user;
}
Claude Code kann den gesamten Guard-Code aus einer Beschreibung deiner Anforderungen generieren: Welche Rollen es gibt, welche Endpoints öffentlich bleiben, ob du Decorator-basierte Ausnahmen brauchst (@Public()). Die Implementierung eines vollständigen Role-Guards dauert damit Minuten statt einer Stunde.
9. Sicherheits-Pitfalls
Die häufigsten JWT-Fehler, auf die Claude Code beim Code-Review hinweist:
- Algorithm: "none" akzeptieren. Manche Libraries akzeptierten früher Tokens ohne Signatur. Immer explizit
algorithms: ['HS256']oder['RS256']angeben — nie einen leeren Array oder kein Argument. - Fehlende Typ-Prüfung. Ohne Prüfung, ob
payload.type === 'access', kann ein Refresh Token als Access Token verwendet werden. - Token im localStorage. Zugänglich für JavaScript, anfällig für XSS. Besser: HttpOnly Cookie für Refresh Tokens, Memory für Access Tokens.
- Kein exp-Claim. Tokens ohne Ablaufzeit gelten ewig. Immer
expiresInsetzen. - Sub als Integer. Die User-ID im
sub-Claim sollte ein String sein — Integer-Typen können in bestimmten JSON-Parsern zu Vergleichsproblemen führen.
"Gib mir diesen Auth-Code und liste alle JWT-Sicherheitslücken auf — mit Schweregrad und konkretem Fix."
Dieser Prompt reicht, damit Claude Code eine vollständige Sicherheitsanalyse liefert. Kein generisches Checklist-Durchklicken, sondern eine Analyse des tatsächlichen Codes, der vor dir liegt.
Zwei verwandte Artikel die auf diesem Thema aufbauen:
- Claude Code Debugging — Stack Traces analysieren und Root-Cause finden
- Claude Code für Unternehmen — Deployment, Zugriffskontrolle und Team-Workflows
Claude Code Mastery — von JWT bis zum produktiven Agenten
Authentifizierung ist ein Kapitel. Der Kurs zeigt außerdem Agents, MCP-Server, Hooks, Multi-Agent-Workflows und mehr — vollständig auf Deutsch, einmalig bezahlt.
Jetzt starten → Einmalzahlung · Kein Abo · 14 Tage RückgaberechtKurs · Claude Code Mastery
Von JWT-Auth zum produktiven AI-Agenten
Authentication. Agents. MCP. Hooks. Multi-Agent-Workflows. Alles auf Deutsch, einmalig bezahlt — kein Abo, keine Plattformabhängigkeit.
Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht