---
title: "JWT erklärt: Was ein JSON Web Token ist und wie es bricht"
description: "Ein JWT ist ein signiertes JSON-Objekt aus drei Base64url-Teilen: Header, Payload und Signatur. Jeder kann es lesen, nur der Schlüsselinhaber kann es signieren. Aufbau, korrekte Prüfung, HS256 vs. RS256 vs. ES256, die Angriffe, die heute noch funktionieren (alg none, Algorithm Confusion, schwache Secrets, kid- und jku-Injection), und wie man ein Token dekodiert, ohne es preiszugeben."
author: Aleksei Aleinikov
date: 2026-10-26
lang: de
tags: [jwt, json-web-token, authentifizierung, oauth, web-security, api-security, kryptografie]
canonical: https://www.alekseialeinikov.com/de/blog/topics/security/jwt-erklaert-json-web-token
source: alekseialeinikov.com
---

# JWT erklärt: Was ein JSON Web Token ist und wie es bricht

Fast jeder Login, den Sie heute nutzen, endet mit einem **JSON Web Token**. Der Identity Provider gibt Ihrer App eines, die App schickt es bei jedem API-Aufruf mit, und die API vertraut ihm, ohne jemanden zu fragen. Genau das ist der Sinn eines JWT, und genau deshalb wird aus einem kleinen Fehler bei der Prüfung schnell „jeder kann Admin sein".

Dieser Leitfaden erklärt, was ein JWT ist, Teil für Teil, wie ein Server es prüfen sollte, welcher Signaturalgorithmus passt und welche Angriffe auf reale Systeme heute noch funktionieren. Jedes Beispiel-Token lässt sich in den kostenlosen [JWT-Decoder mit Security-Analyse](https://www.alekseialeinikov.com/de/tools/jwt-analyzer) einfügen, der vollständig im Browser läuft.

![JWT erklärt: Ein JSON Web Token funktioniert wie eine Bordkarte. Jeder kann sie lesen, wer sie hält, kommt rein, und nur der Aussteller kann das Siegel drucken; eine Karte mit alg none ist ungültig.](https://www.alekseialeinikov.com/blog/jwt-explained.webp)

## Was ist ein JWT? Ein JSON Web Token als Beispiel

**Ein JWT (JSON Web Token, ausgesprochen wie das englische „jot") ist ein kompakter, URL-sicherer String, der eine Menge von Claims und eine Signatur darüber trägt.** Definiert ist es in RFC 7519; die Formate für Signatur und Verschlüsselung darunter heißen JWS (RFC 7515) und JWE (RFC 7516).

Ein signiertes JWT hat drei Teile, getrennt durch Punkte:

```text
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzQyIiwicm9sZSI6ImVkaXRvciIsImlhdCI6MTc2NzIyNTYwMCwiZXhwIjoxNzY3MjI2NTAwfQ.92XVsnsq7eHmUoHn1v4cCfyufbIv5l0Pm8L_Ec6PDH8
```

Dekodiert man die ersten beiden Teile aus Base64url, erhält man zwei gewöhnliche JSON-Objekte:

```json title="header"
{ "alg": "HS256", "typ": "JWT" }
```

```json title="payload"
{ "sub": "user_42", "role": "editor", "iat": 1767225600, "exp": 1767226500 }
```

Der dritte Teil ist die **Signatur**: 32 Byte HMAC-SHA256 über `header.payload`, berechnet mit einem Secret, das nur der Aussteller kennt. Ändert man ein Zeichen der Payload, etwa `"role": "admin"`, passt die Signatur nicht mehr.

Mehr ist ein JWT nicht: Claims plus Beweis, wer sie geschrieben hat. Spannend ist alles drumherum.

## Aufbau eines JSON Web Tokens

![JWT-Aufbau: Header, Payload und Signatur sind Base64url-kodiert und durch Punkte verbunden; die Signatur wird mit dem Schlüssel des Ausstellers über den kodierten Header und die Payload berechnet.](https://www.alekseialeinikov.com/blog/jwt-anatomy.webp "Zwei lesbare JSON-Objekte und eine Signatur, die sie an einen Schlüssel bindet.")

### Der Header

Der Header sagt, wie das Token zu prüfen ist. Die wichtigen Felder:

| Feld | Bedeutung | Vorsicht bei |
|---|---|---|
| `alg` | Signaturalgorithmus: `HS256`, `RS256`, `ES256`, `EdDSA` … | `none` oder einem Algorithmus, den Ihr Server nie ausstellt |
| `typ` | Tokentyp: `JWT` oder `at+jwt` für OAuth-Access-Tokens (RFC 9068) | Ohne Typ werden Token-Verwechslungen leichter |
| `kid` | Key ID: welcher Schlüssel aus dem Key Set signiert hat | Verwendung als Dateipfad oder SQL-Wert |
| `jku`, `x5u` | URL eines Key Sets oder Zertifikats | Wird vom Verifier blind abgerufen |
| `jwk` | Öffentlicher Schlüssel direkt im Token | Wird als Prüfschlüssel vertraut |

### Die Payload: Claims

Die Payload ist ein JSON-Objekt aus **Claims**. RFC 7519 registriert sieben davon; alles andere legt der Aussteller fest.

| Claim | Name | Was der Verifier damit tut |
|---|---|---|
| `iss` | Issuer (Aussteller) | Muss zum vertrauenswürdigen Identity Provider passen |
| `sub` | Subject | Der Nutzer oder Client, um den es geht |
| `aud` | Audience (Zielgruppe) | Muss die Kennung *Ihrer* API enthalten |
| `exp` | Expiration Time | Nach diesem Zeitpunkt ablehnen |
| `nbf` | Not Before | Vor diesem Zeitpunkt ablehnen |
| `iat` | Issued At | Altersprüfung, Debugging |
| `jti` | JWT ID | Eindeutige ID für Einmalnutzung oder Widerruf |

Zeiten sind **NumericDate**: ganze Sekunden seit 1970-01-01 UTC. Erstaunlich viele Fehler entstehen, weil Millisekunden geschrieben werden (`Date.now()` in JavaScript): Das Token läuft dann erst in Zehntausenden Jahren ab.

### Die Signatur

Bei HS256 lautet die Signatur:

```text
HMAC-SHA256( base64url(header) + "." + base64url(payload), secret )
```

Bei RS256, ES256 oder EdDSA ist es eine Public-Key-Signatur über dieselbe Eingabe. In beiden Fällen deckt sie den *kodierten* Header und die Payload ab, der Server prüft also genau die Bytes, die er erhalten hat.

## Signiert ist nicht verschlüsselt

**Base64url ist eine Kodierung, keine Verschlüsselung.** Wer ein JWT hat, liest jeden Claim, ganz ohne Schlüssel:

```bash
node -e 'console.log(JSON.parse(Buffer.from(process.argv[1].split(".")[1], "base64url")))' "$TOKEN"
```

Das ist das häufigste Missverständnis rund um JWTs, und es hat Folgen. Tokens landen im Browser-Speicher, in Logs, Analytics, Crash-Reports und Proxy-Caches. Was in der Payload steht, veröffentlichen Sie an alle diese Orte.

> [!KEY]
> Ein JWT ist eine Bordkarte. Wer sie sieht, liest den Namen darauf, wer sie hält, steigt ein, und nur der Aussteller kann das Siegel drucken, das sie gültig macht.

Die Payload also auf Kennungen und Berechtigungen beschränken. Keine Passwörter, API-Keys, Kartennummern und keine personenbezogenen Daten über das hinaus, was die API braucht. Muss der Inhalt wirklich vertraulich sein, ein verschlüsseltes JWT (**JWE**, fünf statt drei Teile) verwenden oder die Daten auf dem Server halten und eine undurchsichtige ID ins Token schreiben.

## Wie man ein JWT richtig prüft

Dekodieren zeigt, was ein Token *behauptet*. Prüfen entscheidet, ob man es glaubt. Die Prüfung ist eine Checkliste, und die Reihenfolge zählt: Jeder Schritt schließt einen bestimmten Angriff.

![JWT-Prüfung als Checkliste: Größe begrenzen, Algorithmus festlegen, Schlüssel aus dem eigenen Key Set wählen, Signatur prüfen, dann exp und nbf, dann iss, aud und typ. Jeder Schritt blockiert einen benannten Angriff.](https://www.alekseialeinikov.com/blog/jwt-verification-checklist.webp "Die meisten JWT-Vorfälle überspringen einen dieser Schritte; keiner bricht die Kryptografie.")

1. **Eingabe begrenzen.** Übergroße Tokens vor dem Parsen ablehnen.
2. **Algorithmus festlegen.** Nur die Algorithmen akzeptieren, die Sie tatsächlich ausstellen, konfiguriert auf dem Server. Nie das `alg` des Tokens entscheiden lassen.
3. **Den Schlüssel selbst wählen.** Die `kid` im *eigenen* Key Set nachschlagen (ein JWKS Ihres Identity Providers). `jwk`, `jku` und `x5u` aus dem Token ignorieren.
4. **Signatur prüfen.**
5. **Zeit prüfen:** `exp` und `nbf` mit kleiner Toleranz für Uhrabweichungen (30–60 Sekunden), und `exp` als Pflichtfeld verlangen.
6. **Kontext prüfen:** `iss` ist Ihr Identity Provider, `aud` enthält Ihre API, `typ` ist das, was dieser Endpunkt erwartet.

Mit einer gepflegten Bibliothek sind das wenige Zeilen. In Node.js mit `jose`:

```js title="verify.js" {6-10}
import { createRemoteJWKSet, jwtVerify } from 'jose';

const jwks = createRemoteJWKSet(new URL('https://auth.example.com/.well-known/jwks.json'));

const { payload } = await jwtVerify(token, jwks, {
    algorithms: ['ES256'],
    issuer: 'https://auth.example.com',
    audience: 'https://api.example.com',
    typ: 'at+jwt',
    clockTolerance: 30,
});
```

Und in Python mit PyJWT:

```python title="verify.py" {6-10}
import jwt

jwks = jwt.PyJWKClient("https://auth.example.com/.well-known/jwks.json")
key = jwks.get_signing_key_from_jwt(token).key

claims = jwt.decode(
    token, key,
    algorithms=["ES256"],
    issuer="https://auth.example.com",
    audience="https://api.example.com",
    leeway=30,
    options={"require": ["exp", "iat", "iss", "aud", "sub"]},
)
```

Die hervorgehobenen Zeilen sind die Sicherheit. Streicht man eine davon, „funktioniert" der Code in jedem Test weiter, und genau deshalb werden sie gestrichen.

> [!WARNING]
> Nie eine Decode-Funktion aufrufen, um Claims zu lesen und darauf eine Berechtigungsentscheidung zu stützen. Funktionen namens `decode` überspringen in vielen Bibliotheken die Signaturprüfung, absichtlich oder per Flag. Nur Claims aus einem erfolgreichen *Verify* sind Fakten.

## HS256 vs. RS256 vs. ES256: welcher Algorithmus?

Die eigentliche Frage ist nicht Geschwindigkeit oder Schlüssellänge, sondern **wer Tokens ausstellen kann**.

![HS256 vs. RS256 und ES256: Bei HS256 hält jeder Dienst, der Tokens prüft, das gemeinsame Secret und könnte Tokens fälschen; bei RS256 oder ES256 hält nur der Aussteller den privaten Schlüssel, Dienste prüfen mit dem öffentlichen JWKS.](https://www.alekseialeinikov.com/blog/jwt-hs256-vs-rs256.webp "Mit einem gemeinsamen Secret ist jeder Prüfer auch ein möglicher Aussteller.")

| Algorithmus | Typ | Signaturgröße | Einsetzen, wenn |
|---|---|---|---|
| **HS256** | HMAC, gemeinsames Secret | 32 Byte | Ein Dienst seine eigenen Tokens ausstellt *und* prüft |
| **RS256** | RSA PKCS#1 v1.5 | 256 Byte (2048-Bit-Schlüssel) | Breiteste Unterstützung; Standard der meisten Identity Provider |
| **PS256** | RSA-PSS | 256 Byte (2048-Bit-Schlüssel) | RSA mit dem moderneren Padding |
| **ES256** | ECDSA P-256 | 64 Byte | Kleine Tokens, breite Unterstützung; guter Standard für neue Systeme |
| **EdDSA** | Ed25519 | 64 Byte | Einfachste sichere Signaturen, wo Ihre Bibliotheken es unterstützen |

Bei HS256 *ist* der Prüfschlüssel der Signaturschlüssel. Prüfen fünf Microservices Tokens, halten fünf Dienste ein Secret, mit dem sie für jeden beliebigen Nutzer ein Token erzeugen können, und ein Leck im am schlechtesten geschützten Dienst kompromittiert alle. Mit einem asymmetrischen Verfahren hält nur der Identity Provider den privaten Schlüssel; Dienste holen die öffentlichen Schlüssel von einer JWKS-URL, und Schlüsselrotation bedeutet einfach eine neue `kid`.

Wer HS256 nutzt, braucht ein zufälliges, langes Secret: RFC 7518 verlangt einen Schlüssel mindestens so lang wie die Hash-Ausgabe (256 Bit bei HS256), und RFC 8725 schließt merkbare Passwörter als HMAC-Schlüssel aus.

```bash
openssl rand -base64 32
```

## Sechs JWT-Angriffe, die heute noch funktionieren

Keiner davon bricht HMAC, RSA oder ECDSA. Sie nutzen den Verifier aus: Code, der dem Token glaubt, wie es geprüft werden will. Tim McLeans Analyse kritischer Schwachstellen in JWT-Bibliotheken von 2015 hat die ersten beiden berühmt gemacht, und sie tauchen in Eigenbau-Code und alten Abhängigkeiten immer noch auf.

### 1. `alg: none`

JWS definiert einen „ungesicherten" Algorithmus namens `none` mit leerer Signatur. Ein Angreifer nimmt ein gültiges Token, ändert die Payload, setzt `"alg": "none"`, entfernt die Signatur und lässt den letzten Punkt stehen. Ein Verifier, der dem Header folgt, akzeptiert es. Varianten wie `None` oder `nOnE` rutschen an naiven Stringvergleichen vorbei.

**Fix:** eine explizite Allow-List der Algorithmen auf dem Server, wie im Code oben.

### 2. Algorithm Confusion (RS256 zu HS256)

Der Server erwartet RS256 und prüft mit dem *öffentlichen* Schlüssel des Ausstellers. Der Angreifer ändert `alg` auf `HS256` und signiert das Token per HMAC mit genau diesem öffentlichen Schlüssel als Secret, der ja öffentlich ist. Eine Bibliothek, die den Algorithmus aus dem Header nimmt, rechnet nun HMAC mit denselben Schlüssel-Bytes, und das gefälschte Token ist gültig.

**Fix:** den Algorithmus festlegen und Bibliotheken nutzen, die einen Schlüssel an genau einen Algorithmustyp binden.

### 3. Schwache HMAC-Secrets

Ein HS256-Token ist ein Offline-Rätsel: Wer ein Token hat, kann Kandidaten-Secrets testen, ohne Ihren Server zu berühren. `secret`, `changeme`, der Beispielwert des Frameworks oder `your-256-bit-secret` aus einem Tutorial fallen in Sekunden; hashcat hat sogar einen eigenen Modus für JWTs (16500).

**Fix:** ein zufälliges 256-Bit-Secret aus einem Secret Manager oder der Wechsel zu einem asymmetrischen Verfahren.

### 4. Schlüssel-Injection über den Header

`jwk` bettet einen öffentlichen Schlüssel ins Token ein, `jku` oder `x5u` zeigen auf eine URL mit dem Schlüssel. Ein Verifier, der sie nutzt, lässt den Angreifer mit dem eigenen Schlüssel signieren und den passenden öffentlichen Schlüssel gleich mitliefern. `kid` ist subtiler: Es ist nur ein String, und Code, der ihn als Dateipfad (`../../dev/null`) oder in einer SQL-Abfrage verwendet, macht daraus Path Traversal oder Injection.

**Fix:** Schlüssel nur aus dem eigenen, konfigurierten Key Set nehmen; `kid` als undurchsichtigen Lookup-Schlüssel behandeln. RFC 8725 sagt es ausdrücklich: empfangenen Claims nicht vertrauen.

### 5. Fehlende Prüfung von Audience und Issuer

Ihr Unternehmen hat zwei APIs, die demselben Identity Provider vertrauen. Ein Token für die harmlose API wird gegen die Payment-API wiederverwendet und ist gültig, denn Signatur und Ablauf stimmen. Niemand hat `aud` geprüft.

**Fix:** Jede API prüft, dass `aud` sie selbst nennt und `iss` der erwartete Aussteller ist. Explizite `typ`-Werte verhindern, dass ein ID-Token als Access Token durchgeht.

### 6. Tokens, die ewig leben

Ein Token ohne `exp` oder mit Monaten Laufzeit ist ein langlebiges Passwort, das sich nicht ändern lässt. Ein gestohlenes funktioniert weiter, nachdem sich der Nutzer abmeldet, sein Passwort ändert oder das Unternehmen verlässt.

**Fix:** Access Tokens mit Laufzeiten in Minuten, `exp` als Pflichtfeld im Verifier und rotierende Refresh Tokens, die der Server widerrufen kann.

Auch Bibliotheksfehler kommen vor: 2022 akzeptierte die ECDSA-Implementierung von Java (Versionen 15 bis 18) eine Signatur aus lauter Nullen als gültig für jede Nachricht. Damit war auf ungepatchten Laufzeitumgebungen jedes ES256-Token fälschbar (CVE-2022-21449, „Psychic Signatures"). Krypto-Laufzeiten und JWT-Bibliotheken aktuell zu halten, gehört zur Checkliste.

## JWT-Decoder: ein Token lesen, ohne es preiszugeben

Ein JWT ist ein **Bearer-Credential**: Wer ein gültiges Produktions-Token hat, kann meist Ihre API im Namen dieses Nutzers aufrufen. Es in irgendeine Website einzufügen, die es an einen Server hochlädt, heißt, es zu verschenken.

Drei sichere Wege:

- **Kommandozeile:** der Node.js-Einzeiler oben oder das Pendant Ihrer Sprache. Nichts verlässt den Rechner.
- **Ein lokaler Browser-Decoder:** Der [JWT-Decoder mit Security-Analyse](https://www.alekseialeinikov.com/de/tools/jwt-analyzer) auf dieser Website dekodiert, prüft und auditiert Tokens mit Web Crypto direkt in der Seite; Tokens, Secrets und Schlüssel werden nie hochgeladen, gespeichert oder in die URL geschrieben.
- **Abgelaufene oder Test-Tokens**, wenn Sie nur die Struktur sehen wollen.

So sieht der Bericht für ein Token aus, das mit `your-256-bit-secret` signiert wurde, dem Beispiel-Secret aus unzähligen Tutorials:

![JWT-Decoder mit Security-Analyse zeigt Note F für ein HS256-Token: mit einem bekannten Secret signiert und vier Jahre gültig, mit dekodiertem Header und Payload und einer Lösung pro Fund.](https://www.alekseialeinikov.com/blog/jwt-analyzer-example-de.webp "Der Analyzer probiert lokal über 100 bekannte Secrets und erklärt jeden Fund mit einem Fix.")

Zuerst meldet er die fälschbare Signatur, dann die mehrjährige Laufzeit und die fehlenden `iss` und `aud`, jeweils mit Fix. Fügen Sie den öffentlichen Schlüssel oder das JWKS des Ausstellers ein, prüft er auch RS256-, PS256-, ES256- und EdDSA-Signaturen. Für KI-Agenten steht dieselbe Analyse als WebMCP-Tool `analyze_jwt` bereit.

## JWT oder Session-Cookie: wann ein JWT passt

JWTs sind so populär, dass sie auch dort landen, wo eine Session einfacher wäre. Ein ehrlicher Vergleich:

| | Serverseitige Session | JWT |
|---|---|---|
| **Zustand** | Auf dem Server, Cookie enthält eine ID | In sich geschlossen, mit Schlüssel geprüft |
| **Widerruf** | Sofort: Session löschen | Wartet auf `exp`, außer man ergänzt Serverzustand |
| **Skalierung** | Braucht gemeinsamen Session-Store | Jeder Dienst mit dem öffentlichen Schlüssel kann prüfen |
| **Über Dienste und Domains** | Umständlich | Genau dafür gebaut |
| **Größe pro Request** | Ein paar Dutzend Byte | Hunderte Byte bis mehrere KB |

Eine **Session** für die klassische Web-App mit einem Backend. **JWTs**, wenn mehrere Dienste, ein API-Gateway oder Dritte die Identität ohne gemeinsamen Store prüfen müssen, und überall dort, wo OAuth 2.0 und OpenID Connect ohnehin eines liefern: ID-Tokens sind immer JWTs, viele Access Tokens ebenfalls.

### JWT vs. OAuth

Das sind keine Alternativen. **OAuth 2.0 ist ein Protokoll**, um im Namen eines Nutzers ein Access Token zu bekommen; **ein JWT ist ein Tokenformat**. Ein OAuth-Access-Token kann ein JWT sein (RFC 9068 standardisiert dieses Profil mit `typ: at+jwt`) oder ein undurchsichtiger Zufallsstring, den die API beim Authorization Server prüft. OpenID Connect, die Identitätsschicht über OAuth, nutzt für das ID-Token immer ein JWT.

Beides lässt sich gut kombinieren. In Browser-Apps gehört ein JWT in ein HttpOnly-Cookie, nicht in den `localStorage`; Details inklusive CSRF-Schutz stehen in [JWT nicht in LocalStorage speichern](https://www.alekseialeinikov.com/de/blog/topics/security/jwt-nicht-in-localstorage-cookie-auth-fuer-spas-2026). Wer den Identity Provider selbst betreibt, lässt Tokens von [Keycloak oder Authentik](https://www.alekseialeinikov.com/de/blog/topics/security/ist-keycloak-kostenlos-keycloak-vs-authentik-2026) ausstellen und signieren, und ein stärkerer Login davor, etwa mit [Passkeys](https://www.alekseialeinikov.com/de/blog/topics/security/passkeys-vs-passwoerter-wie-webauthn-funktioniert-2026), schützt den Moment, in dem ein Token entsteht.

Das HMAC hinter HS256 ist dasselbe Primitiv, mit dem Webhooks signiert werden; [Webhook-Signaturen](https://www.alekseialeinikov.com/de/blog/topics/architecture/webhooks-erklaert-signatur-retries-idempotenz) unterliegen denselben Regeln für Secret-Stärke und zeitkonstanten Vergleich.

## Fazit

Ein JWT ist ein lesbares JSON-Dokument mit Signatur. Das Format ist einfach und die Kryptografie solide; in der Praxis scheitert der Code drumherum. Algorithmus festlegen, Schlüssel selbst wählen, `exp` verlangen, `iss` und `aud` prüfen, Tokens kurzlebig halten und Secrets aus der Payload heraushalten. Mit einer gepflegten Bibliothek umgesetzt, ist ein JWT eines der bequemsten Credentials, die man ausgeben kann.

Und wenn Sie das nächste Mal sehen wollen, was in einem steckt: lokal dekodieren, nicht in einer Seite, die es hochlädt.
