Was in einem JSON Web Token steckt

Header, Payload und Signatur eines JWT erklärt, dazu die Standard-Claims und warum ein Token Base64url zu decodieren nicht dasselbe ist, wie es zu verifizieren.

Ein JSON Web Token (JWT) besteht aus drei Textblöcken, die durch Punkte verbunden sind. Jeder Block ist Base64url-codiert, und die ersten beiden sind schlicht JSON. Jeder, der das Token hat, kann lesen, was darin steht. Nichts in einem normalen JWT ist verschlüsselt.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9      <- header
eyJzdWIiOiIxMjM0NSIsIm5hbWUiOiJBbGV4In0   <- payload
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFW     <- signature

Mit Punkten verbunden ergibt das das Token, das du in eine Anfrage einfügst. Decodiere die ersten beiden Teile, und du bekommst ganz gewöhnliches JSON:

{ "alg": "HS256", "typ": "JWT" }
{ "sub": "12345", "name": "Alex" }

Eine echte Payload trägt meist zusätzlich Zeitstempel und eine Audience:

{
  "iss": "https://auth.example.com",
  "sub": "12345",
  "aud": "api.example.com",
  "iat": 1756000000,
  "exp": 1756003600,
  "roles": ["editor"]
}

Der Header

Der Header beschreibt, wie das Token signiert ist. Zwei Felder zählen am meisten:

  • alg nennt den Signaturalgorithmus, zum Beispiel HS256 (HMAC mit SHA-256 und einem gemeinsamen Geheimnis) oder RS256 (RSA-Signatur mit SHA-256, mit einem privaten Schlüssel signiert und mit einem öffentlichen geprüft).
  • kid, die Schlüssel-ID, ist optional und sagt dem Empfänger, welcher von mehreren Schlüsseln verwendet wurde, sodass Schlüssel rotiert werden können, ohne bestehende Tokens zu zerstören.

typ ist meist einfach JWT und hat keine sicherheitsrelevante Bedeutung.

Die Payload und ihre Claims

Die Payload ist ein JSON-Objekt aus Claims, und das ist schlicht das Wort der Spezifikation für Aussagen über das Subjekt des Tokens. Einige Claim-Namen sind vom Standard registriert, und sie sind alle kurz:

ClaimNameWas er enthält
issIssuerWer das Token erzeugt hat, meist eine URL oder ein Dienstname
subSubjectUm wen es im Token geht, meist eine stabile Benutzer-ID
audAudienceFür wen das Token bestimmt ist, damit ein Dienst Tokens ablehnen kann, die für einen anderen gedacht sind
expExpiration timeDer Zeitpunkt, nach dem das Token abgelehnt werden muss
nbfNot beforeDer Zeitpunkt, vor dem das Token abgelehnt werden muss
iatIssued atWann das Token erzeugt wurde
jtiJWT IDEine eindeutige Kennung, nützlich, um einzelne Tokens zu verfolgen oder zu sperren

exp, nbf und iat sind alle NumericDate-Werte, also Sekunden seit dem 1. Januar 1970 UTC. Es sind Sekunden, keine Millisekunden. Wenn ein Zeitstempel zu einem Datum im Jahr 57.000 decodiert, liest du Millisekunden und musst durch tausend teilen.

Alles andere in der Payload ist das, was der Aussteller hineingeschrieben hat: Rollen, Berechtigungen, eine E-Mail-Adresse, eine Mandanten-ID. Weil ein privater Claim-Name eines Tages mit einem registrierten kollidieren könnte, versehen Aussteller ihre eigenen oft mit einem URL-ähnlichen Präfix als Namensraum.

Die Signatur

Die Signatur wird über den exakten Text der ersten beiden Teile berechnet, verbunden durch einen Punkt, mit dem Algorithmus aus dem Header und einem Schlüssel. Ändere ein einziges Zeichen im Header oder in der Payload, und die Signatur passt nicht mehr.

Bei HS256 signiert und verifiziert dasselbe Geheimnis, jeder, der ein Token prüfen kann, kann also auch eines ausstellen. Bei RS256 oder ES256 signiert ein privater Schlüssel und ein öffentlicher verifiziert, und genau das erlaubt es einem Identitätsanbieter, Tokens auszugeben, die viele getrennte Dienste prüfen können, ohne etwas Geheimes zu halten.

Base64url ist keine Verschlüsselung

Base64url ist dieselbe Idee wie Base64, mit zwei Unterschieden: Es verwendet - und _ anstelle von + und /, damit das Ergebnis in URLs sicher ist, und die abschließende Auffüllung mit = wird meist weggelassen. Es ist eine Codierung, eine umkehrbare Art, Bytes als Text zu schreiben. Sie bietet keinerlei Vertraulichkeit.

Schreibe deshalb nie etwas Sensibles in eine JWT-Payload: keine Passwörter, keine Kartennummern, keine internen Notizen über den Benutzer. Geh davon aus, dass die Person, die das Token hält, und jeder, der es aus einer Logdatei oder dem Browserspeicher liest, jedes Feld sehen kann.

Es gibt ein eigenes Format, JWE, das die Payload tatsächlich verschlüsselt, und es hat fünf statt drei Teile. Wenn dein Token drei Teile hat, ist es signiert und lesbar.

Decodieren ist nicht Verifizieren

Das ist der entscheidende Punkt. Ein Decoder zeigt dir, was ein Token sagt. Er sagt dir nicht, ob das Token echt ist. Die Verifikation ist ein eigener Schritt und braucht den Schlüssel.

Um einem Token wirklich zu vertrauen, muss ein Server:

  1. Die Signatur gegen den richtigen Schlüssel prüfen.
  2. Auf dem Algorithmus bestehen, den er erwartet, statt dem Feld alg im Token zu vertrauen. Zwei klassische Angriffe entstehen daraus, dass man das auslässt: alg auf none zu setzen und die Signatur zu entfernen, und den öffentlichen RSA-Schlüssel eines Dienstes als HMAC-Geheimnis zu verwenden, sodass ein RS256-Prüfer dazu gebracht wird, HS256 auszuführen.
  3. exp und, falls vorhanden, nbf prüfen, mit einer kleinen Toleranz für Uhrenabweichung.
  4. Prüfen, dass iss und aud dem entsprechen, was dieser Dienst erwartet.

Erst dann bedeuten die Claims überhaupt etwas. Bis dahin ist die Payload eine unverifizierte Zeichenkette, die über das Netz angekommen ist.

Praktische Hinweise

  • Tokens werden meist in einem HTTP-Header als Authorization: Bearer <token> gesendet, halte die Payload also klein. Jeder Claim, den du hinzufügst, wird bei jeder Anfrage mitgeschickt.
  • Ein signiertes Token bleibt gültig, bis es abläuft. Es gibt keinen eingebauten Weg, eines zu widerrufen, und deshalb haben Access-Tokens meist kurze Lebensdauern, oft Minuten, während ein separates Refresh-Token neue beschafft.
  • Wenn ein Token fehlerhaft aussieht, zähle zuerst die Punkte. Zwei Punkte und drei Teile sind ein normales signiertes JWT. Ein abgeschnittenes Token, das aus einem Terminal eingefügt wurde, ist eine sehr häufige Ursache für „invalid signature“.
  • Ein Token beim Debuggen zu decodieren, um sein Ablaufdatum, sein Subjekt oder seine Rollen zu lesen, ist völlig ungefährlich und braucht kein Geheimnis. Genau dafür ist die Payload da.