Zum Hauptinhalt springen

🎫 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

AnmeldungServer 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

AnmeldungJWT 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:

  1. Was ist HTTPS? (Dokument in Vorbereitung) - Sichere Token-Übertragung
  2. Was ist CORS? (Dokument in Vorbereitung) - API-Sicherheit
  3. 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! 🎫✨