🎫 JWT-Token vollständig verstehen
📖 Definition
JWT(JSON Web Token) ist ein JSON-basierter Token zur sicheren Übertragung von Informationen zwischen Client und Server. Er besteht aus drei Teilen (Header, Payload, Signature), wird vom Server ausgestellt und vom Client bei Anfragen mitgesendet zur Authentifizierung. Im Gegensatz zu Sessions speichert er keinen Status auf dem Server (stateless), daher hervorragende Skalierbarkeit.
🎯 Mit Analogie verstehen
Vergnügungspark-Ticket
JWT verglichen mit einem Vergnügungspark-Ticket:
Normales Ticket (Session-Methode)
├─ Armband beim Eingang erhalten
├─ Personal prüft Armband jedes Mal
└─ Personal muss sich alle Besucher merken (Server-Last)
VIP-Ticket (JWT-Methode)
├─ Ticket mit fälschungssicherem Stempel beim Eingang
├─ Ticket zeichnet Name, Gültigkeit, Grad auf
├─ Nur Ticket zeigen jedes Mal
└─ Personal prüft nur Stempel (geringe Server-Last)
JWT = Informationskarte mit Fälschungssicherheitsstempel
⚙️ Funktionsprinzip
1. JWT-Struktur
JWT = Header.Payload.Signature
Beispiel:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEyMywidXNlcm5hbWUiOiJraW0ifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
│ │ │
Header (Kopfzeile) Payload (Nutzdaten) Signature (Signatur)
Header (Kopfzeile)
{
"alg": "HS256", // Signaturalgorithmus
"typ": "JWT" // Token-Typ
}
// Base64-kodiert
Payload (Nutzdaten) - Tatsächliche Daten
{
"userId": 123,
"username": "kim",
"email": "kim@example.com",
"role": "admin",
"iat": 1640000000, // Issued At (Ausstellungszeit)
"exp": 1640086400 // Expiration (Ablaufzeit)
}
// Base64-kodiert
Signature (Signatur) - Fälschungsprävention
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret // Geheimschlüssel (nur Server kennt ihn)
)
2. JWT-Authentifizierungsfluss
1. Anmeldung
Client → Server: username, password
Server: Authentifizierung erfolgreich → JWT generieren und zurückgeben
2. JWT in Anfrage einschließen
Client → Server: Authorization: Bearer <JWT>
3. Server verifiziert JWT
- Signatur prüfen (ob gefälscht)
- Ablaufzeit prüfen
- Benutzerinformationen aus Payload extrahieren
4. Antwort
Server → Client: Angeforderte Daten
💡 Echte Beispiele
JWT-Generierung und -Verifizierung (Node.js)
const jwt = require('jsonwebtoken');
// Geheimschlüssel (Verwaltung mit Umgebungsvariablen empfohlen)
const SECRET_KEY = 'your-secret-key-keep-it-safe';
// ========== JWT-Generierung ==========
function generateToken(user) {
// Payload definieren
const payload = {
userId: user.id,
username: user.username,
email: user.email,
role: user.role
};
// Optionen
const options = {
expiresIn: '1h', // Nach 1 Stunde ablaufen
// expiresIn: '7d', // 7 Tage
// expiresIn: '30m', // 30 Minuten
issuer: 'my-app', // Aussteller
subject: 'user-auth' // Zweck
};
// JWT generieren
const token = jwt.sign(payload, SECRET_KEY, options);
return token;
}
// ========== JWT-Verifizierung ==========
function verifyToken(token) {
try {
const decoded = jwt.verify(token, SECRET_KEY);
console.log('Verifizierung erfolgreich:', decoded);
/*
{
userId: 123,
username: 'kim',
email: 'kim@example.com',
role: 'admin',
iat: 1640000000,
exp: 1640003600
}
*/
return decoded;
} catch (error) {
if (error.name === 'TokenExpiredError') {
console.error('Token abgelaufen');
} else if (error.name === 'JsonWebTokenError') {
console.error('Ungültiger Token');
}
return null;
}
}
// ========== Verwendungsbeispiel ==========
const user = {
id: 123,
username: 'kim',
email: 'kim@example.com',
role: 'admin'
};
const token = generateToken(user);
console.log('Generierter JWT:', token);
const decoded = verifyToken(token);
console.log('Dekodiert:', decoded);
Login-API-Implementierung
const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const app = express();
app.use(express.json());
const SECRET_KEY = process.env.JWT_SECRET || 'your-secret-key';
const users = []; // In Realität Datenbank verwenden
// ========== Registrierung ==========
app.post('/api/register', async (req, res) => {
const { username, password, email } = req.body;
// Passwort hashen
const hashedPassword = await bcrypt.hash(password, 10);
const user = {
id: users.length + 1,
username,
email,
password: hashedPassword
};
users.push(user);
res.json({ message: 'Registrierung erfolgreich', userId: user.id });
});
// ========== Anmeldung ==========
app.post('/api/login', async (req, res) => {
const { username, password } = req.body;
// Benutzer suchen
const user = users.find(u => u.username === username);
if (!user) {
return res.status(401).json({ error: 'Benutzer nicht gefunden' });
}
// Passwort prüfen
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
return res.status(401).json({ error: 'Falsches Passwort' });
}
// JWT generieren
const token = jwt.sign(
{
userId: user.id,
username: user.username,
email: user.email
},
SECRET_KEY,
{ expiresIn: '1h' }
);
// Auch Refresh Token ausstellen (optional)
const refreshToken = jwt.sign(
{ userId: user.id },
SECRET_KEY,
{ expiresIn: '7d' }
);
res.json({
message: 'Anmeldung erfolgreich',
accessToken: token,
refreshToken: refreshToken
});
});
// ========== Authentifizierungs-Middleware ==========
function authenticateToken(req, res, next) {
// Token aus Authorization-Header extrahieren
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // "Bearer TOKEN"
if (!token) {
return res.status(401).json({ error: 'Kein Token' });
}
jwt.verify(token, SECRET_KEY, (err, user) => {
if (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token abgelaufen' });
}
return res.status(403).json({ error: 'Ungültiger Token' });
}
// Verifizierung erfolgreich - Benutzerinformationen in req speichern
req.user = user;
next();
});
}
// ========== Geschützte Routen ==========
app.get('/api/profile', authenticateToken, (req, res) => {
// req.user ist aus JWT extrahierte Information
res.json({
message: 'Profilinformationen',
user: req.user
});
});
app.get('/api/admin', authenticateToken, (req, res) => {
// Berechtigungsprüfung
if (req.user.role !== 'admin') {
return res.status(403).json({ error: 'Administratorrechte erforderlich' });
}
res.json({ message: 'Administratorseite' });
});
app.listen(3000, () => {
console.log('Server läuft: http://localhost:3000');
});
JWT im Frontend verwenden
// ========== Anmeldung ==========
async function login(username, password) {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (response.ok) {
// JWT in LocalStorage speichern
localStorage.setItem('accessToken', data.accessToken);
localStorage.setItem('refreshToken', data.refreshToken);
console.log('Anmeldung erfolgreich!');
}
}
// ========== JWT in API-Anfrage einschließen ==========
async function fetchProfile() {
const token = localStorage.getItem('accessToken');
const response = await fetch('/api/profile', {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.status === 401) {
// Token abgelaufen → Zur Anmeldeseite
window.location.href = '/login';
return;
}
const data = await response.json();
console.log('Profil:', data);
}
// ========== Automatisierung mit Axios-Interceptors ==========
import axios from 'axios';
// Request-Interceptor: JWT automatisch zu allen Anfragen hinzufügen
axios.interceptors.request.use(
config => {
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => Promise.reject(error)
);
// Response-Interceptor: Automatisches Abmelden bei 401-Fehler
axios.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 401) {
localStorage.removeItem('accessToken');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
// Jetzt enthalten alle Anfragen automatisch JWT!
axios.get('/api/profile').then(response => {
console.log(response.data);
});
Refresh-Token-Implementierung
// ========== Access Token + Refresh Token Muster ==========
// Access Token: Kurze Lebensdauer (15 Minuten)
// Refresh Token: Lange Lebensdauer (7 Tage)
app.post('/api/login', async (req, res) => {
// ... Anmeldeverifizierung ...
const accessToken = jwt.sign(
{ userId: user.id, username: user.username },
SECRET_KEY,
{ expiresIn: '15m' } // 15 Minuten
);
const refreshToken = jwt.sign(
{ userId: user.id },
REFRESH_SECRET_KEY,
{ expiresIn: '7d' } // 7 Tage
);
// Refresh Token in DB speichern (optional)
// await saveRefreshToken(user.id, refreshToken);
res.json({ accessToken, refreshToken });
});
// ========== Access Token erneuern ==========
app.post('/api/refresh', (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(401).json({ error: 'Kein Refresh Token' });
}
jwt.verify(refreshToken, REFRESH_SECRET_KEY, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Ungültiger Refresh Token' });
}
// Neuen Access Token ausstellen
const accessToken = jwt.sign(
{ userId: user.userId },
SECRET_KEY,
{ expiresIn: '15m' }
);
res.json({ accessToken });
});
});
// ========== Frontend: Automatische Token-Erneuerung ==========
axios.interceptors.response.use(
response => response,
async error => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
// Neuen Access Token mit Refresh Token erhalten
const refreshToken = localStorage.getItem('refreshToken');
const response = await axios.post('/api/refresh', { refreshToken });
const { accessToken } = response.data;
localStorage.setItem('accessToken', accessToken);
// Ursprüngliche Anfrage erneut versuchen
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
return axios(originalRequest);
} catch (refreshError) {
// Refresh Token auch abgelaufen → Abmelden
localStorage.clear();
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
🤔 Häufig gestellte Fragen
F1. Was ist der Unterschied zwischen JWT und Session?
A:
// ========== Session-Methode ==========
// Server speichert Benutzerinformationen
Anmeldung → Server speichert Session
{
sessionId: 'abc123',
userId: 123,
username: 'kim'
}
Client speichert nur sessionId
Cookie: sessionId=abc123
Session vom Server mit sessionId bei Anfrage abfragen
Vorteil: Server kann sofort ungültig machen
Nachteil: Benötigt Server-Speicher/Storage, geringe Skalierbarkeit
// ========== JWT-Methode ==========
// Client speichert Token
Anmeldung → JWT generieren und zurückgeben
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Client speichert gesamten JWT
localStorage.setItem('token', jwt)
JWT in Anfrage einschließen, Server verifiziert nur Signatur
Vorteil: Keine Server-Last, gute Skalierbarkeit
Nachteil: Schwer Token ungültig zu machen, große Größe
F2. Ist JWT sicher?
A: Sicher bei richtiger Verwendung:
// ✅ Sichere Verwendung
1. HTTPS obligatorisch verwenden
- HTTP ermöglicht JWT-Abfangen
2. Sicherheit des Geheimschlüssels
- Mit Umgebungsvariablen verwalten
- Absolut verboten im Code zu hardcodieren
const SECRET = process.env.JWT_SECRET;
3. Kurze Ablaufzeit
- Access Token: 15 Minuten~1 Stunde
- Mit Refresh Token erneuern
4. Verbot sensible Informationen zu speichern
❌ { password: '1234', ssn: '123-45-6789' }
✅ { userId: 123, role: 'user' }
5. XSS-Prävention
- HttpOnly Cookie über LocalStorage empfohlen
res.cookie('token', jwt, {
httpOnly: true, // Kein JavaScript-Zugriff
secure: true, // Nur HTTPS
sameSite: 'strict'
});
// ❌ Gefährliche Verwendung
1. Keine Ablaufzeit
2. Über HTTP übertragen
3. In LocalStorage speichern (XSS-anfällig)
4. Sensible Informationen wie Passwort einschließen
F3. Wie kann man JWT ungültig machen?
A: Es gibt mehrere Methoden:
// 1. Blacklist (Schwarze Liste)
// - Token bei Abmeldung in DB/Redis speichern
// - Blacklist bei Anfrage prüfen
const blacklist = new Set();
app.post('/api/logout', authenticateToken, (req, res) => {
const token = req.headers.authorization.split(' ')[1];
blacklist.add(token);
res.json({ message: 'Abmeldung erfolgreich' });
});
function authenticateToken(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (blacklist.has(token)) {
return res.status(401).json({ error: 'Ungültig gemachter Token' });
}
// ... JWT-Verifizierung ...
}
// 2. In Redis speichern (Ablaufzeit festlegen)
const redis = require('redis');
const client = redis.createClient();
app.post('/api/logout', authenticateToken, async (req, res) => {
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.decode(token);
// Blacklist bis Token-Ablaufzeit beibehalten
const ttl = decoded.exp - Math.floor(Date.now() / 1000);
await client.setEx(`blacklist:${token}`, ttl, 'true');
res.json({ message: 'Abmeldung erfolgreich' });
});
// 3. Kurze Lebensdauer + Refresh Token
// - Access Token: 15 Minuten (keine Ungültigmachung erforderlich)
// - Nur Refresh Token in DB speichern und ungültig machen
// 4. Versionsverwaltung
// - Benutzer-Token-Version in DB speichern
// - Version bei Abmeldung erhöhen
const payload = {
userId: user.id,
tokenVersion: user.tokenVersion // Aus DB abrufen
};
// Abmelden
await db.users.update(
{ id: userId },
{ $inc: { tokenVersion: 1 } } // Version erhöhen
);
F4. Wo soll JWT gespeichert werden?
A:
// Option 1: LocalStorage (einfach aber XSS-anfällig)
localStorage.setItem('token', jwt);
// ❌ XSS-Angriff kann Token stehlen
// Option 2: SessionStorage (beim Schließen des Tabs löschen)
sessionStorage.setItem('token', jwt);
// ❌ XSS-anfällig
// Option 3: HttpOnly Cookie (am sichersten, empfohlen)
// Auf Server festlegen
res.cookie('token', jwt, {
httpOnly: true, // Kein JavaScript-Zugriff (XSS-Prävention)
secure: true, // Nur HTTPS
sameSite: 'strict',// CSRF-Prävention
maxAge: 3600000 // 1 Stunde
});
// Client sendet automatisch
fetch('/api/profile', {
credentials: 'include' // Cookie einschließen
});
// Option 4: Speicher (am sichersten aber Abmelden bei Aktualisierung)
let token = null;
function login() {
// ... Anmelden ...
token = response.accessToken; // Nur in Variable speichern
}
// Vor- und Nachteile im Vergleich
LocalStorage: Einfach aber XSS-anfällig
Cookie: Sicher aber komplexe Einrichtung
Memory: Am sichersten aber schlechte UX
F5. Access Token vs Refresh Token?
A:
// Access Token
// - Kurze Lebensdauer (15 Minuten~1 Stunde)
// - Bei jeder API-Anfrage senden
// - Läuft schnell ab auch wenn gestohlen
// Refresh Token
// - Lange Lebensdauer (7 Tage~30 Tage)
// - Nur beim Erneuern von Access Token verwenden
// - In DB speichern und verwalten
// Ablauf
1. Anmeldung
→ Access Token (15 Min) + Refresh Token (7 Tage) ausstellen
2. API-Anfrage
→ Access Token verwenden
3. Access Token abgelaufen (nach 15 Min)
→ Neuen Access Token mit Refresh Token erhalten
4. Refresh Token auch abgelaufen (nach 7 Tagen)
→ Erneute Anmeldung erforderlich
// Code-Beispiel
// Token ausstellen
const accessToken = jwt.sign(payload, SECRET, { expiresIn: '15m' });
const refreshToken = jwt.sign({ userId }, REFRESH_SECRET, { expiresIn: '7d' });
// Automatische Erneuerung
if (accessTokenExpired) {
const newAccessToken = await refreshAccessToken(refreshToken);
}
🎓 Nächste Schritte
Nach dem Verständnis von JWT, lernen Sie:
- Was ist HTTPS? (Dokument in Vorbereitung) - Sichere Token-Übertragung
- Was ist CORS? (Dokument in Vorbereitung) - API-Sicherheit
- Was ist Cookie? - Session vs JWT
Nützliche Tools
// JWT-Debugger
// https://jwt.io
// - JWT-Dekodierung und -Verifizierung
// Bibliotheken
// Node.js: jsonwebtoken
// Python: PyJWT
// Java: jjwt
// Go: jwt-go
// Test
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
const decoded = jwt.decode(token, { complete: true });
console.log(decoded);
/*
{
header: { alg: 'HS256', typ: 'JWT' },
payload: { userId: 123, ... },
signature: '...'
}
*/
🎬 Zusammenfassung
JWT ist die Standard-Authentifizierungsmethode des modernen Webs:
- Struktur: Header.Payload.Signature
- Vorteile: Stateless, Skalierbarkeit, Multiplattform-Unterstützung
- Sicherheit: HTTPS, HttpOnly Cookie, kurze Lebensdauer
- Muster: Access Token + Refresh Token
Bei richtiger Verwendung kann ein sicheres und effizientes Authentifizierungssystem aufgebaut werden! 🎫✨