Files
2026-03-27 12:58:01 +01:00

428 lines
15 KiB
Markdown
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Ein JSON Web Token (JWT) wirkt am Anfang oft etwas „magisch“. Wenn man es einmal verstanden hat, ist es aber ein relativ simples Baustein-konzept für Authentifizierung und Autorisierung in modernen Web und APISystemen.
Ich gehe Schritt für Schritt durch:
1. Grundidee: Was ist ein JWT?
2. Aufbau eines JWT
3. Abgrenzung zu verwandten Konzepten
4. Welche Probleme werden mit JWT gelöst?
5. Typische Anwendungsfälle (praxisnah erklärt)
6. Herausforderungen und Fallstricke
7. Kurz-Fazit
---
## 1. Grundidee: Was ist ein JWT?
**Definition (vereinfacht):**
Ein **JWT (JSON Web Token)** ist ein **kompakter, URL-tauglicher, digital signierter Datenblock**, der Informationen über einen Benutzer oder eine Aktion enthält (z.B. „Benutzer X ist eingeloggt und hat Rolle admin“).
Diese Informationen sind im JSON-Format strukturiert und werden mit einem kryptografischen Verfahren signiert (und optional verschlüsselt).
Damit kann ein Server (oder mehrere Server) **prüfen**, ob das Token:
- von einem vertrauenswürdigen Aussteller stammt,
- nicht manipuliert wurde,
- noch gültig ist (nicht abgelaufen).
Man kann sich einen JWT grob als „fälschungssichereren Ausweis“ für Benutzer (oder Clients) vorstellen, der vom Server ausgestellt wird und den der Client bei weiteren Anfragen vorzeigt.
---
## 2. Aufbau eines JWT
Ein JWT besteht aus **drei Teilen**, jeweils Base64URL-kodiert, durch Punkte getrennt:
```text
HEADER.PAYLOAD.SIGNATURE
```
Ein Beispiel (gekürzt, sieht in der Praxis ähnlich aus):
```text
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiIxMjM0NTYiLCJlbWFpbCI6Im1heC5tdXN0ZXJAbWFpbC5jb20iLCJyb2xlIjoiYWRtaW4iLCJleHAiOjE3MzAxNjkzNDN9
.
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
```
### 2.1 Header
Der Header beschreibt u.a.:
- welches Signaturverfahren genutzt wird,
- dass es sich um ein JWT handelt.
Beispiel:
```json
{
"alg": "HS256", // Algorithmus: HMAC-SHA256
"typ": "JWT"
}
```
### 2.2 Payload (Claims)
Die Payload enthält die eigentlichen Informationen („Claims“).
Beispiele:
```json
{
"sub": "123456", // subject: Benutzer-ID
"email": "max.mustermann@example.com",
"role": "admin",
"iat": 1730169334, // issued at (Unix-Timestamp)
"exp": 1730172934, // expiry: Ablaufzeitpunkt
"iss": "https://auth.meine-app.de", // issuer
"aud": "https://api.meine-app.de" // audience
}
```
Wichtige Standard-Claims (nicht verpflichtend, aber üblich):
- `iss` Issuer (Wer hat das Token ausgestellt?)
- `sub` Subject (Auf wen bezieht sich das Token? z.B. Benutzer-ID)
- `aud` Audience (Für welche Anwendung/Services ist das Token bestimmt?)
- `exp` Expiration Time (Wann läuft das Token ab?)
- `iat` Issued At (Wann wurde es ausgestellt?)
- `nbf` Not Before (Ab wann ist es gültig?)
Dazu kommen **anwendungsspezifische Claims**, z.B.:
- `email`, `username`
- `roles`: `["admin", "user"]`
- `scope`: `"read:orders write:orders"`
### 2.3 Signature
Die Signatur wird aus dem Header + Payload + einem Geheimnis (oder Schlüssel) gebildet.
Formel grob:
```text
signature = Sign( base64Url(header) + "." + base64Url(payload), secret_or_private_key )
```
Beim Prüfen:
- rekonstruiert der Server diese Signatur aus Header+Payload,
- vergleicht sie mit der Signatur im Token,
- wenn sie übereinstimmen → Token wurde nicht manipuliert.
---
## 3. Abgrenzung zu verwandten Begriffen
### 3.1 JWT vs klassische Session-Cookies
**Klassische Session:**
- Nach Login erzeugt der Server eine **Session-ID** und speichert alle Infos (Benutzer, Rollen usw.) **im Server-Speicher** (in Memory, Redis, DB).
- Die Session-ID wird im Browser z.B. als Cookie gespeichert.
- Bei jeder Anfrage sendet der Browser die Session-ID; Server schaut im eigenen Speicher nach, welche Daten dazu gehören.
**JWT:**
- Nach Login erzeugt der Server ein **Token**, das die Infos (Benutzer, Rollen, Ablaufzeit) **direkt im Token** enthält.
- Das Token wird typischerweise im Browser (Cookie oder Speicher der App) gehalten.
- Bei jeder Anfrage schickt der Client das Token mit (z.B. via HTTP-Header `Authorization: Bearer <token>`).
- Der Server liest und prüft das Token, braucht aber keinen Session-Speicher.
Kurz:
- Session: **zustandsbehaftet (stateful)** auf Server-Seite.
- JWT: **zustandslos (stateless)** auf Server-Seite.
### 3.2 JWT vs OAuth2 / OpenID Connect
- **OAuth2** ist ein **Protokoll für Autorisierung** (wer darf was). Es definiert Flows, Rollen (Client, Resource Owner, Authorization Server, Resource Server) etc.
- **OpenID Connect (OIDC)** baut auf OAuth2 auf und regelt **Authentifizierung** (wer bist du) inklusive ID-Token.
JWT ist hier eher ein **Datenformat**:
- In OAuth2/OIDC werden oft **Access Tokens** und **ID Tokens** als JWT umgesetzt.
- Aber OAuth2/OIDC kann theoretisch auch andere Tokenformate verwenden.
**Merksatz:**
OAuth2 / OIDC = das Protokoll;
JWT = ein mögliches Format für die Tokens.
### 3.3 JWT vs JWS / JWE
- **JWS (JSON Web Signature)**: Standard für signierte JSON-Daten.
- **JWE (JSON Web Encryption)**: Standard für verschlüsselte JSON-Daten.
Ein **typisches JWT ist ein JWS**: signiert, aber nicht verschlüsselt.
Es gibt aber auch **JWTs als JWE**: zusätzlich verschlüsselt (seltener in der Praxis).
Wichtig:
**Signiert heißt: Integrität und Echtheit** (nicht manipuliert, vom richtigen Aussteller),
**nicht automatisch: Geheimhaltung**.
Payload ist bei normalen JWTs **lesbar**, auch wenn sie signiert sind.
### 3.4 JWT vs API-Schlüssel (API Key)
- Ein **API-Key** ist meist ein zufälliger String, der einen Client identifiziert.
Logik: „Wer den Key kennt, darf die API nutzen.“
- Ein **JWT** enthält strukturierte Informationen (Claims) und ist signiert, so dass man viele Details im Token selbst hat (Benutzer, Rollen, Ablauf).
JWT ist in der Regel **ausdrucksstärker** und besser integrierbar in komplexe Auth-Zusammenhänge; ein API-Key ist eher „simple Zugriffskarte“.
### 3.5 JWT vs SAML
- **SAML** ist ein älterer Standard für Single Sign-On (SSO), benutzt XML statt JSON.
- JWT (bzw. OIDC mit JWT) ist moderner, leichtergewichtig, JSON-basiert.
Viele moderne Anwendungen bevorzugen OIDC + JWT statt SAML, vor allem im Web-/API-Bereich.
---
## 4. Welche Probleme werden mit JWT gelöst?
### 4.1 Skalierbare Authentifizierung in verteilten Systemen
Problem:
- Klassische Sessions erfordern zentralen Server-Speicher (Session Store).
- In modernen Microservice-Architekturen und skalierenden Web-Apps (viele Instanzen) ist das unpraktisch.
JWT-Lösung:
- **Zustandslose Tokens**: Jeder Service kann das Token **selbst verifizieren**, ohne zentralen Session Store.
- Mehrere Instanzen, mehrere Services, sogar andere Systeme (Partner-APIs) können mit dem gleichen Token arbeiten, solange sie den Signaturschlüssel kennen (oder das zugehörige Public Key).
### 4.2 Einfache Weitergabe von Benutzerinformationen
Problem:
- Mehrere Services müssen wissen, wer der Benutzer ist und welche Rechte er hat.
- Man möchte nicht jedes Mal eine zusätzliche Datenbankabfrage machen.
JWT-Lösung:
- Claims im Token: `sub`, `email`, `roles`, `scope`, …
- Jeder Service liest diese Claims aus dem Token und kann basierend darauf entscheiden.
### 4.3 Mobile / Single-Page-Applications (SPAs)
Problem:
- SPAs (z.B. React, Angular, Vue) und Mobile-Apps sprechen meist direkt mit APIs.
- Klassische serverseitige Sessions sind dafür unhandlich, weil es keinen klassischen Browser-Request/Response-Cookie-Workflow gibt oder dieser komplexer ist.
JWT-Lösung:
- Client erhält nach Login ein **Access Token (JWT)**.
- Bei jedem API-Request wird das JWT im `Authorization`-Header mitgeschickt.
- APIs können unabhängig vom Frontend arbeiten (auch mehrere Frontends nutzen die gleiche API).
---
## 5. Praxisnahe Anwendungsbeispiele
### 5.1 Web-App / API-Login mit JWT
Ablauf:
1. Benutzer gibt E-Mail + Passwort ein und klickt auf „Login“.
2. Frontend schickt diese Daten an `/auth/login` (Backend).
3. Backend prüft:
- Benutzer existiert?,
- Passwort korrekt?
4. Wenn ok:
- Backend erstellt ein JWT:
- `sub = Benutzer-ID`
- `email = Benutzer-Email`
- `role = "user"`
- `exp = in 15 Minuten`
- signiert das Token mit einem geheimen Schlüssel (z.B. HS256).
5. Backend schickt das Token an das Frontend zurück.
6. Frontend speichert das Token:
- z.B. in einem **httpOnly Cookie** (empfohlen),
- oder in einem sicheren Storage (abhängig vom Setup).
7. Bei jeder API-Anfrage:
- schickt das Frontend das Token mit, z.B.:
```http
GET /api/orders
Authorization: Bearer <JWT_HIER>
```
8. Die API:
- prüft die Signatur,
- prüft `exp` (nicht abgelaufen?),
- liest `sub`, `role`, `scope` aus,
- entscheidet, ob Zugriff erlaubt ist.
### 5.2 Microservices hinter einem API Gateway
Stell dir eine Plattform mit mehreren Microservices vor:
- `User-Service`
- `Order-Service`
- `Billing-Service`
Es gibt einen **Auth-Service**, der JWTs ausstellt.
Alle Services kennen den Public Key (bei asymmetrischer Signatur) oder den Secret Key (bei symmetrischer Signatur).
Ablauf:
- Benutzer loggt sich über Auth-Service ein → bekommt JWT.
- Frontend ruft `Order-Service` auf mit `Authorization: Bearer <JWT>`.
- `Order-Service` prüft JWT:
- gültig?
- `scope` beinhaltet `read:orders`?
- Wenn ja → Bestellung anzeigen.
Vorteil:
Jeder Service muss keinen eigenen Session-Store haben, sondern nur das JWT validieren.
### 5.3 E-Mail-Bestätigung / Passwort-Reset
JWTs eignen sich auch für **zeitlich begrenzte Links**:
Beispiel: Passwort-Reset:
1. Benutzer klickt „Passwort vergessen“.
2. Backend erzeugt ein JWT mit Claims:
- `sub = Benutzer-ID`
- `exp = in 30 Minuten`
- `purpose = "password_reset"`
3. Backend verschickt E-Mail mit Link:
```text
https://meine-app.de/reset-password?token=<JWT_HIER>
```
4. Benutzer klickt den Link.
5. Frontend schickt den Token an das Backend, z.B. POST `/auth/reset-password`.
6. Backend prüft:
- Signatur,
- `exp` (noch gültig?),
- `purpose == "password_reset"`.
7. Wenn alles passt → Passwort darf geändert werden.
---
## 6. Herausforderungen und Fallstricke
JWTs lösen einige Probleme, bringen aber eigene Herausforderungen mit sich.
### 6.1 Sicherheit bei Signatur und Schlüssel
Wichtige Punkte:
- **Algorithmuswahl:**
Typisch:
- Symmetrisch: `HS256` (ein gemeinsames Secret für Signatur & Prüfung)
- Asymmetrisch: `RS256` (privater Schlüssel signiert, öffentlicher Schlüssel prüft)
- **Keys sicher verwalten:**
- nicht im Code hardcoden,
- in sicheren Secret-Stores halten,
- Schlüssel regelmäßig rotieren.
Gefährliche Anti-Pattern:
- `alg: none` akzeptieren (nie tun),
- Server so konfigurieren, dass er das `alg` aus dem Token blind vertraut (statt fest vorzugeben, was erlaubt ist).
### 6.2 Token-Ablauf und „Logout“
JWT ist stateless das macht „Logout“ tricky:
- Klassische Session:
- Session im Server-Speicher löschen → Benutzer abgemeldet.
- JWT:
- Token ist selbstständig gültig, bis `exp` erreicht ist.
- Serverseitig gibt es **keine zentrale Liste** aller aktiven JWTs.
Mögliche Lösungen:
- Tokens **kurzlebig** machen, z.B. 515 Minuten.
- Für längere Sessions:
- **Access Token** kurzlebig,
- **Refresh Token** längerlebig und serverseitig verwaltbar (z.B. in DB),
- bei Logout → Refresh Token invalidieren, Access Token läuft automatisch bald ab.
- Optional: Blacklists/Blocklists oder Token-Revocation-Mechanismen.
### 6.3 Wo speichert man JWT im Browser?
Varianten:
- **httpOnly Cookie**:
- Vorteil: nicht via JavaScript auslesbar → Schutz gegen XSS.
- Nachteil: CSRF muss beachtet werden (CSRF-Protection implementieren).
- **localStorage / sessionStorage**:
- Vorteil: einfache Handhabung im JS-Code.
- Nachteil: bei XSS kann ein Angreifer das Token auslesen → sehr riskant.
In der Praxis:
- Oft Kombination aus httpOnly Cookies + CSRF-Schutz,
- oder Speziallösungen mit sog. „Double Submit Cookies“, „SameSite“-Cookies etc.
### 6.4 Token sind nicht geheim (wenn nur signiert)
Wichtiger Punkt:
- Ein **normaler signierter JWT ist nicht verschlüsselt**.
- Jeder, der das Token sieht, kann die Payload (Claims) lesen, da sie Base64URL-kodiert ist (was keine Verschlüsselung ist).
Daraus folgt:
- Keine hochsensiblen Daten in die Payload packen (z.B. Passwörter, Zahlungsdaten, interne Geheimnisse).
- JWT nur über **HTTPS** übertragen, damit niemand auf dem Weg „mithören“ kann.
- Wenn wirklich Geheimhaltung nötig ist → **JWE (verschlüsseltes JWT)** oder andere Verschlüsselungsmechanismen.
### 6.5 Token-Größe
Wenn man sehr viele Claims ins Token packt:
- wird es groß,
- verursacht mehr Traffic,
- passt evtl. schlechter in Cookies oder Header.
Praktisch:
Nur wirklich relevante Informationen ins JWT aufnehmen:
- Benutzer-ID
- Rollen, Rechte oder Scope
- wenige zusätzliche Angaben
Den Rest kann man bei Bedarf aus der Datenbank abfragen.
### 6.6 Fehler bei der Prüfung des Tokens
Beim Validieren darf man nicht nur die Signatur prüfen, sondern auch:
- Ist das Token abgelaufen? (`exp`)
- Ist das Token noch nicht gültig? (`nbf`, `iat`)
- Stimmt `iss` (Issuer) mit dem erwarteten Aussteller überein?
- Stimmt `aud` (Audience) mit der eigenen Anwendung/API überein?
Beispiel (Node.js mit `jsonwebtoken`):
```js
const jwt = require('jsonwebtoken');
const token = '...'; // vom Client erhalten
const publicKey = '...'; // Public Key (bei RS256) oder Secret (bei HS256)
try {
const payload = jwt.verify(token, publicKey, {
algorithms: ['RS256'],
issuer: 'https://auth.meine-app.de',
audience: 'https://api.meine-app.de',
});
console.log('Token gültig, Benutzer-ID:', payload.sub);
} catch (err) {
console.error('Ungültiges Token:', err.message);
}
```
---
## 7. Kurz-Fazit
- Ein **JWT** ist ein **signierter, kompakter Datenträger im JSON-Format**, der Claims über Benutzer/Clients enthält.
- Er wird verwendet für:
- Authentifizierung (z.B. „Benutzer X ist eingeloggt“),
- Autorisierung (z.B. „Benutzer X hat die Rolle admin“),
- zeitlich begrenzte Aktionen (Passwort-Reset, E-Mail-Bestätigung),
- verteilte Systeme (Microservices), die ohne zentralen Session-Speicher auskommen sollen.
- JWTs sind besonders sinnvoll in:
- Web-APIs,
- SPAs,
- mobilen Apps,
- Microservice- und Cloud-Architekturen.
- Abgrenzung:
- JWT ≠ OAuth2/OIDC (Protokoll vs. Tokenformat),
- JWT ≠ Session-Cookie (zustandslos vs. zustandsbehaftet),
- JWTs sind meist JWS (signiert, aber nicht verschlüsselt).
- Herausforderungen:
- Sichere Schlüsselverwaltung, korrekte Token-Prüfung,
- Umgang mit Logout/Token-Invalidierung,
- sicherer Speicherort im Client (XSS/CSRF),
- kein Hineinpacken hochsensibler Daten.