no more Umlaute
This commit is contained in:
@@ -0,0 +1,427 @@
|
||||
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 API‑Systemen.
|
||||
|
||||
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. 5–15 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.
|
||||
|
||||
Reference in New Issue
Block a user