Ein JWT (JSON Web Token, RFC 7519) ist eine Zeichenkette aus drei durch Punkte getrennten Teilen, header.payload.signatur, die Aussagen (Claims) über einen Nutzer oder eine Sitzung so transportiert, dass der Empfänger prüfen kann, dass niemand sie verändert hat. Die ersten beiden Teile sind Base64URL-kodiertes JSON: Jeder kann sie lesen. Der dritte ist eine Signatur, die nur erzeugen kann, wer den Schlüssel besitzt. Deshalb ist ein JWT zu dekodieren nicht dasselbe wie es zu validieren: Den Inhalt zu lesen ist trivial; ihm zu vertrauen verlangt, Signatur, Ablauf und Aussteller zu prüfen.
Dieser Artikel geht den Aufbau eines Tokens durch, die üblichen Claims, wie man es inspiziert, die klassischen Sicherheitsfehler und wo man es in einer Webanwendung speichert.
Aufbau eines Tokens
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0IiwibmFtZSI6IkFpbXJhbiIsImlhdCI6MTc5MTI4MDAwMCwiZXhwIjoxNzkxMjgzNjAwfQ.<signatur>
- Header (
{"alg":"HS256","typ":"JWT"}): Signaturalgorithmus und Token-Typ. - Payload (
{"sub":"1234","name":"Aimran","iat":1791280000,"exp":1791283600}): die Claims. - Signatur: das Ergebnis der Signierung von
base64url(header) + "." + base64url(payload)mit dem Algorithmus aus dem Header. BeiHS256ist das ein HMAC-SHA256 mit einem gemeinsamen geheimen Schlüssel; beiRS256oderES256eine asymmetrische Signatur mit privatem Schlüssel, die mit dem öffentlichen Schlüssel geprüft wird.
Die Kodierung ist Base64URL (eine Base64-Variante mit - und _ statt + und /, ohne =-Füllzeichen), die wir in Was ist Base64 behandeln. Das ist keine Verschlüsselung: Der Payload lässt sich mit einer Codezeile lesen.
Die üblichen Claims
| Claim | Name | Zweck |
|---|---|---|
iss |
Issuer | Wer das Token ausgestellt hat (dein Authentifizierungsserver). |
sub |
Subject | Auf wen es sich bezieht (die Nutzer-ID). |
aud |
Audience | Für wen es bestimmt ist (die API, die es akzeptieren soll). |
exp |
Expiration | Unix-Zeitpunkt, ab dem es nicht mehr gültig ist. |
nbf |
Not before | Unix-Zeitpunkt, vor dem es noch nicht gültig ist. |
iat |
Issued at | Wann es ausgestellt wurde. |
jti |
JWT ID | Eindeutige Kennung, nützlich zum Widerrufen. |
Laut RFC sind alle optional, aber exp sollte immer gesetzt sein: Ein Token ohne Ablauf gilt für immer, wenn es abhandenkommt. Eigene Claims (role, plan) sind erlaubt, mit einer Regel: nie sensible Daten, denn der Payload ist für jeden öffentlich, der das Token hat.
Ein JWT inspizieren
Um zu sehen, was ein Token enthält, genügt es, die ersten beiden Teile zu dekodieren. Der JWT-Decoder von AIMRAN Tools zeigt Header und Payload, übersetzt iat, exp und nbf in lesbare Daten und meldet, ob das Token abgelaufen oder noch nicht gültig ist, alles im Browser, ohne das Token an einen Server zu senden. Fügst du auch das Secret ein, prüft er die HMAC-Signatur. In Node.js ohne Bibliotheken:
const [header, payload] = token.split('.');
const decode = (part) => JSON.parse(Buffer.from(part, 'base64url').toString('utf8'));
console.log(decode(header), decode(payload));
Dieser Code liest, er validiert nicht. Zum Validieren nutze eine etablierte Bibliothek (jose oder jsonwebtoken in Node; PyJWT in Python), die die Signatur mit dem von dir erwarteten Algorithmus, den Ablauf und den Aussteller prüft.
Sicherheitsfehler, die immer wiederkehren
RFC 8725, die offiziellen Best Practices, deckt die meisten ab:
- Dem Payload vertrauen, ohne die Signatur zu prüfen. Jeder kann ein Token mit
"role":"admin"fälschen. Prüft deine API die Signatur nicht, hat sie gerade Rechte verschenkt. - Den Algorithmus akzeptieren, den der Header nennt. Der klassische
alg: none-Angriff schickt ein unsigniertes Token und hofft, dass der Server es akzeptiert. Die Bibliothek darf nur die von dir konfigurierten Algorithmen prüfen. - HS256/RS256-Verwechslung. Nutzt der Server RS256 (öffentlicher Schlüssel) und akzeptiert auch HS256, kann ein Angreifer mit dem öffentlichen Schlüssel signieren, als wäre er das HMAC-Secret. Gleiche Lösung: geschlossene Algorithmusliste.
- Schwache HMAC-Secrets. Ein kurzes Secret lässt sich offline per Brute Force knacken, weil der Angreifer das Token besitzt. Nutze zufällige Secrets mit mindestens 256 Bit.
- Ewige Tokens. Kein
expoder Laufzeiten von Monaten. Üblich: Access-Tokens für Minuten plus ein Refresh-Token zur Erneuerung. - Sensible Daten im Payload. E-Mails, Ausweisnummern, Adressen: Der Payload ist nicht verschlüsselt. Brauchst du Vertraulichkeit, gibt es JWE (verschlüsselte Tokens), was aber selten nötig ist.
Wo das Token im Browser liegen sollte
Zwei Optionen mit unterschiedlichen Kompromissen:
- Cookie
HttpOnly; Secure; SameSite: JavaScript kann es nicht lesen, ein XSS stiehlt es also nicht; der Browser sendet es automatisch; gegen CSRF braucht es Schutz (SameSite=LaxoderStrictdeckt die meisten Fälle ab). Das ist die übliche Empfehlung für klassische Webanwendungen. localStorageoder Arbeitsspeicher: Jedes Skript der Seite kann es lesen, ein XSS kompromittiert es also vollständig. Sinnvoll nur für Clients ohne Cookies (native Apps) oder wenn die API auf einer anderen Domain liegt und das Token nur im Speicher lebt, solange der Tab offen ist.
In beiden Fällen senkt eine strikte Content-Security-Policy das XSS-Risiko, das die eigentliche Bedrohung hinter dieser Debatte ist.
JWT gegenüber klassischen Sitzungen
Ein JWT erlaubt der API, die Identität ohne Datenbankabfrage bei jeder Anfrage zu prüfen: Alle Informationen reisen im Token mit. Dieser Vorteil hat einen Preis: Ein Token lässt sich nicht widerrufen, bevor es abläuft, es sei denn, du führst eine Sperrliste (womit die Datenbankabfrage zurückkehrt). Für eine Anwendung mit einem einzigen Server ist eine klassische Sitzung (opake Kennung im Cookie, Daten auf dem Server) einfacher und leichter zu invalidieren. JWTs glänzen, wenn mehrere Dienste dieselbe Identität akzeptieren müssen, ohne einen Sitzungsspeicher zu teilen.
Fazit
Ein JWT besteht aus drei Base64URL-Blöcken: Header, Payload und Signatur. Die ersten beiden kann jeder lesen; nur die Signatur, geprüft mit dem richtigen Algorithmus und dem richtigen Schlüssel, erlaubt es, ihnen zu vertrauen. Setze immer exp, begrenze die akzeptierten Algorithmen, halte sensible Daten aus dem Payload und speichere es, wenn möglich, in einem HttpOnly-Cookie. Und wenn du wissen willst, was ein Token enthält, dekodiere es ohne Scheu: Das ist tatsächlich umsonst.