JWT-Tokens erklärt: Struktur, Sicherheit und Best Practices
· 12 Min. Lesezeit
JSON Web Tokens (JWTs) sind zum De-facto-Standard für zustandslose Authentifizierung in modernen Webanwendungen geworden. Egal, ob Sie eine REST-API, eine Microservices-Architektur oder eine Single-Page-Anwendung erstellen – das Verständnis der Funktionsweise von JWTs ist entscheidend für die Implementierung sicherer, skalierbarer Authentifizierung.
Dieser umfassende Leitfaden erklärt alles, was Sie über JWTs wissen müssen – von ihrer internen Struktur bis hin zu produktionsreifen Sicherheitspraktiken. Am Ende werden Sie nicht nur verstehen, wie man JWTs verwendet, sondern auch wann man sie einsetzt und wie man häufige Fallstricke vermeidet, die zu Sicherheitslücken führen.
Inhaltsverzeichnis
- Was ist ein JWT?
- JWT-Struktur: Header, Payload und Signatur
- Standard-Claims (RFC 7519)
- Wie JWT-Authentifizierung funktioniert
- Implementierungsbeispiele
- Sicherheits-Best-Practices
- Refresh-Tokens und Token-Rotation
- JWT vs. sitzungsbasierte Authentifizierung
- Häufige Fehler, die es zu vermeiden gilt
- Wo JWTs gespeichert werden sollten
- Häufig gestellte Fragen
- Verwandte Artikel
Was ist ein JWT?
Ein JSON Web Token (JWT) ist ein kompaktes, URL-sicheres Mittel zur Darstellung von Claims, die zwischen zwei Parteien übertragen werden sollen. Die Claims in einem JWT sind als JSON-Objekt kodiert, das mit JSON Web Signature (JWS) digital signiert wird.
Stellen Sie sich ein JWT als manipulationssicheren Container für Benutzerinformationen vor. Wenn sich ein Benutzer anmeldet, erstellt Ihr Server ein JWT, das dessen Benutzer-ID, Rollen und andere relevante Daten enthält. Dieses Token wird dann an den Client gesendet, der es bei jeder nachfolgenden Anfrage mitsendet. Der Server kann die Authentizität des Tokens überprüfen, ohne eine Datenbank abzufragen, was JWTs ideal für zustandslose, skalierbare Architekturen macht.
JWTs sind durch RFC 7519 definiert und werden in Programmiersprachen und Frameworks weitgehend unterstützt. Sie sind besonders beliebt bei:
- Single Sign-On (SSO)-Systemen, bei denen sich Benutzer einmal authentifizieren und auf mehrere Dienste zugreifen
- Microservices-Architekturen, bei denen Dienste die Benutzeridentität ohne gemeinsamen Sitzungsspeicher überprüfen müssen
- Mobilen Anwendungen, bei denen die Aufrechterhaltung serverseitiger Sitzungen unpraktisch ist
- API-Authentifizierung, bei der zustandslose Überprüfung die Skalierbarkeit verbessert
Schneller Tipp: Verwenden Sie unseren JWT-Decoder, um jedes JWT-Token sofort zu inspizieren und zu debuggen. Es ist vollständig clientseitig, sodass Ihre Tokens niemals Ihren Browser verlassen.
JWT-Struktur: Header, Payload und Signatur
Ein JWT besteht aus drei Base64URL-kodierten Teilen, die durch Punkte (.) getrennt sind. So sieht ein echtes JWT aus:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Aufgeschlüsselt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 ← Header
.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ ← Payload
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ← Signatur
| Teil | Enthält | Beispiel (dekodiert) |
|---|---|---|
| Header | Algorithmus + Token-Typ | {"alg": "HS256", "typ": "JWT"} |
| Payload | Claims (Benutzerdaten) | {"sub": "1234567890", "name": "John Doe", "iat": 1516239022} |
| Signatur | Verifizierungs-Hash | HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) |
Der Header
Der Header besteht typischerweise aus zwei Teilen: dem Token-Typ (typ) und dem Signaturalgorithmus (alg). Gängige Algorithmen sind:
- HS256 (HMAC mit SHA-256): Symmetrischer Algorithmus mit einem gemeinsamen Geheimnis
- RS256 (RSA mit SHA-256): Asymmetrischer Algorithmus mit öffentlichen/privaten Schlüsselpaaren
- ES256 (ECDSA mit SHA-256): Asymmetrischer Algorithmus mit kleineren Schlüsselgrößen
Der Payload
Der Payload enthält die Claims – Aussagen über eine Entität (typischerweise den Benutzer) und zusätzliche Metadaten. Claims sind nicht verschlüsselt, sondern nur kodiert, daher sollten Sie niemals sensible Informationen wie Passwörter in einem JWT-Payload ablegen.
Die Signatur
Die Signatur stellt sicher, dass das Token nicht manipuliert wurde. Sie wird erstellt, indem der kodierte Header, der kodierte Payload, ein geheimer Schlüssel genommen und der im Header angegebene Algorithmus angewendet wird. Wenn jemand den Header oder Payload ändert, schlägt die Signaturüberprüfung fehl.
Profi-Tipp: Generieren Sie Test-JWTs mit benutzerdefinierten Claims mit unserem JWT-Generator-Tool. Perfekt für Entwicklungs- und Testszenarien.
Standard-Claims (RFC 7519)
RFC 7519 definiert mehrere Standard-Claims, die Interoperabilität zwischen verschiedenen JWT-Implementierungen ermöglichen. Obwohl diese Claims optional sind, verbessert ihre korrekte Verwendung Sicherheit und Kompatibilität.
| Claim | Name | Zweck | Beispiel |
|---|---|---|---|
iss |
Aussteller | Identifiziert, wer das Token erstellt hat | "https://auth.example.com" |
sub |
Subjekt | Identifiziert, worum es bei dem Token geht (normalerweise Benutzer-ID) | "user_12345" |
aud |
Zielgruppe | Identifiziert, für wen das Token bestimmt ist | "https://api.example.com" |
exp |
Ablauf | Wann das Token abläuft (Unix-Zeitstempel) | 1735689600 |
nbf |
Nicht vor | Token ist vor dieser Zeit nicht gültig | 1735603200 |
iat |
Ausgestellt am | Wann das Token erstellt wurde | 1735603200 |
jti |
JWT-ID | Eindeutige Kennung für das Token (nützlich für Widerruf) | "a1b2c3d4-e5f6" |
Benutzerdefinierte Claims
Über Standard-Claims hinaus können Sie benutzerdefinierte Claims hinzufügen, die spezifisch für Ihre Anwendung sind. Häufige Beispiele sind:
roleoderroles: Benutzerberechtigungen (z.B."admin","user")email: E-Mail-Adresse des Benutzersname: Anzeigename des Benutzersscope: OAuth 2.0-Bereiche für API-Zugrifftenant_id: Mandantenkennung für Multi-Tenant-Anwendungen
Halten Sie Ihren Payload klein – jedes Byte erhöht den Netzwerk-Overhead. Fügen Sie nur die Claims ein, die Sie für Autorisierungsentscheidungen benötigen. Wenn Sie zusätzliche Benutzerdaten benötigen, rufen Sie diese aus Ihrer Datenbank ab, indem Sie den sub-Claim als Suchschlüssel verwenden.
Wie JWT-Authentifizierung funktioniert
Das Verständnis des vollständigen Authentifizierungsablaufs hilft Ihnen, JWTs korrekt und sicher zu implementieren. Hier ist die typische Abfolge:
- Benutzeranmeldung: Benutzer sendet Anmeldedaten (Benutzername + Passwort) an Ihren Authentifizierungs-Endpunkt
- Überprüfung der Anmeldedaten: Server validiert Anmeldedaten gegen Ihre Benutzerdatenbank
- Token-Generierung: Server erstellt ein JWT mit Benutzer-Claims und signiert es mit einem geheimen Schlüssel
- Token-Übermittlung: Server gibt das JWT an den Client zurück (typischerweise im Antworttext oder als httpOnly-Cookie)
- Token-Speicherung: Client speichert das JWT sicher (mehr dazu im Abschnitt Speicheroptionen)
- Authentifizierte Anfragen: Client fügt das JWT im
Authorization-Header für nachfolgende Anfragen ein - Token-Überprüfung: Server überprüft die Signatur und prüft den Ablauf – keine Datenbankabfrage erforderlich
- Zugriff gewährt: Wenn gültig, verarbeitet der Server die Anfrage unter Verwendung der Claims aus dem Token
Hier ist eine visuelle Darstellung:
┌────────┐ ┌────────┐
│ Client │ │ Server │
└───┬────┘ └───┬────┘
│ │
│ POST /login {username, password} │
│──────────────────────────────────────────>│
│ │
│ [Anmeldedaten überprüfen]
│ [JWT generieren]
│ │
│ 200 OK {token: "eyJhbG..."} │
│<──────────────────────────────────────────│
│ │
[Token speichern] │
│ │
│ GET /api/profile │
│ Authorization: Bearer eyJhbG... │
│──────────────────────────────────────────>│
│ │
│ [Signatur überprüfen]
│ [Ablauf prüfen]
│ [Claims extrahieren]
│ │
│ 200 OK {user data} │
│<──────────────────────────────────────────│
│ │
Profi-Tipp: Die Schönheit von JWTs liegt darin, dass Schritt 7 (Token-Überprüfung) keine Datenbankabfrage erfordert. Der Server kann Authentizität überprüfen und Benutzerinformationen direkt aus dem Token extrahieren, was Ihre API hochgradig skalierbar macht.
Implementierungsbeispiele
Schauen wir uns praktische Implementierungen in beliebten Programmiersprachen und Frameworks an.
Node.js mit jsonwebtoken
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
// Login-Endpunkt
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// Benutzer in Datenbank finden
const user = await User.findOne({ username });
if (!user) {
return res.status(401).json({ error: 'Ungültige Anmeldedaten' });
}
// Passwort überprüfen
const validPassword = await bcrypt.compare(password, user.passwordHash);
if (!validPassword) {
return res.status(401).json({ error: 'Ungültige Anmeldedaten' });
}
// JWT erstellen
const token = jwt.sign(
{
sub: user.id,
email: user.email,
role: user.role
},
process.env.JWT_SECRET,
{
expiresIn: '15m',
issuer: 'https://api.example.com',
audience: 'https://example.com'
}
);
res.json({ token });
});
// Geschützte Route Middleware
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({ error: 'Kein Token bereitgestellt' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
return res.status(403).json({ error: 'Ungültiges oder abgelaufenes Token' });
}
};
// Middleware verwenden
app.get('/api/profile', authenticateToken, (req, res) => {
res.json({ userId: req.user.sub, email: req.user.email });
});
Python mit PyJWT
import jwt
import datetime
from flask import Flask, request, jsonify
app = Flask(__name__)
SECRET_KEY = 'your-secret-key'
@app.route('/login', methods=['POST'])
def login():
data = request.get_json()
username = data.get('username')
password = data.get('password')
# Anmeldedaten überprüfen (vereinfacht)
user = verify_credentials(username, password)
if not user:
return jsonify({'error': 'Ungültige Anmeldedaten'}), 401
# JWT erstellen
payload = {
'sub': user['id'],
'email': user['email'],
'role': user['role'],
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=15),
'iat': datetime.datetime.utcnow()
}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
return jsonify({'token': token})
def token_required(f):
def decorator(*args, **kwargs):
token = request.headers.get('Authorization')
if not token:
return jsonify({'error': 'Kein Token bereitgestellt'}), 401
try:
# 'Bearer '-Präfix entfernen
token = token.split(' ')[1]
decoded = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
request.user = decoded
except jwt.ExpiredSignatureError:
return jsonify({'error': 'Token abgelaufen'}), 403
except jwt.InvalidTokenError:
return jsonify({'error': 'Ungültiges Token'}), 403
return f(*args, **kwargs)
return decorator
@app.route('/api/profile')
@token_required
def profile():
return jsonify({'userId': request.user['sub']})
Go mit golang-jwt
package main
import (
"time"
"github.com/golang-jwt/jwt/v5"
)
type Claims struct {
UserID string `json:"sub"`
Email string `json:"email"`
Role string `json:"role"`
jwt.RegisteredClaims
}
func generateToken(userID, email, role string) (string, error) {
claims := Claims{
UserID: userID,
Email: emai