JWT-Anatomie: Header, Payload, Signatur, base64url und warum Dekodieren nicht Verifizieren ist
Drei Punkte: die Struktur eines JSON Web Tokens
Ein JSON Web Token ist eine Zeichenkette aus genau drei base64url-kodierten Werten, getrennt durch Punkte: base64url(Header).base64url(Payload).base64url(Signatur). RFC 7519, veroffentlicht im Mai 2015, definiert dieses Format. Ob ein JWT in einem Authorization-Header, einem Cookie oder einem URL-Abfrageparameter erscheint -- er folgt stets dieser Struktur. Die drei Teile lassen sich unabhangig dekodieren, und die Signatur verbindet sie mit kryptografischen Garantien.
Das Format wurde als kompakt und URL-sicher konzipiert. Vor JWT setzte API-Authentifizierung haufig auf serverseitig gespeicherte, opake Session-Identifier, die bei jeder Anfrage eine Datenbankabfrage erforderten. JWTs betten die Claims direkt in den Token ein, sodass ein zustandsloser Server sie ohne Abfrage verifizieren kann. Diese Bequemlichkeit bringt Verantwortung mit sich: Anders als ein Session-Token, den der Server sofort widerrufen kann, bleibt ein signierter JWT gultig, bis sein exp-Claim ablauft -- es sei denn, der Server fuhrt eine Sperrliste.
base64url-Kodierung: die URL-sichere Variante von base64
Standard-base64 kodiert Binardaten mit 64 Zeichen: A-Z, a-z, 0-9, + und /, mit Auffullung durch =. Zwei dieser Zeichen (+ und /) haben in URLs und HTTP-Abfrage-Strings besondere Bedeutung. base64url, definiert in RFC 4648, Abschnitt 5, ersetzt + durch - und / durch _ und lasst die Auffullung = vollstandig weg. Das Ergebnis kann direkt in URLs, HTTP-Headern und Cookies verwendet werden, ohne weitere Kodierung.
Die Aufblahrate entspricht der von Standard-base64: Aus je 3 Eingabebytes werden 4 base64url-Zeichen -- ein Faktor von 4/3, also etwa 33 % Zuwachs. Da die Kodierung umkehrbar und kein Schlussel erforderlich ist, kann jeder, der einen JWT besitzt, dessen Header und Payload durch Dekodierung lesen -- base64url ist Kodierung, keine Verschlusselung.
Der Header: Algorithmusauswahl und Token-Typ
Der Header ist ein JSON-Objekt, das dem Empfanger mitteilt, wie der Token zu verarbeiten ist. Ein minimaler Header enthalt zwei Felder: alg (den Signaturalgorithmus) und typ (fur Standard-Tokens immer "JWT"). RFC 7518 definiert die Algorithmus-Bezeichner. Die haufigsten sind: HS256 -- HMAC-SHA256, ein symmetrischer Algorithmus, bei dem dasselbe Geheimnis fur Signierung und Verifikation verwendet wird; RS256 -- RSA-PKCS1v1.5 mit SHA-256, asymmetrisch, der private Schlussel signiert, der offentliche Schlussel verifiziert; ES256 -- ECDSA mit P-256 und SHA-256, ebenfalls asymmetrisch, aber mit kurzeren Signaturen als RSA.
Die Wahl zwischen symmetrischen und asymmetrischen Algorithmen hat reale architektonische Konsequenzen. Bei HS256 muss jeder Dienst, der Tokens verifizieren muss, das gemeinsame Geheimnis besitzen -- ein Leak an einen Dienst ist ein Leak an alle. Bei RS256 oder ES256 halt der Aussteller den privaten Schlussel privat und veroffentlicht nur den offentlichen Schlussel (oft uber einen JWKS-Endpoint), sodass verifizierende Dienste kein Signaturmaterial handhaben. Fur Multi-Service- oder Drittanbieter-Integrationen sind asymmetrische Algorithmen vorzuziehen.
Der Payload: registrierte Claims und ihre Bedeutung
Der Payload ist ein JSON-Objekt mit Claims -- Aussagen uber eine Entitat, typischerweise den authentifizierten Nutzer, sowie Metadaten uber den Token selbst. RFC 7519, Abschnitt 4.1, definiert sieben registrierte Claim-Namen mit standardisierter Semantik: iss (Aussteller), sub (Subjekt, oft eine Nutzer-ID), aud (Zielgruppe, die beabsichtigten Empfanger), exp (Ablaufzeit, ein NumericDate, nach dem der Token nicht mehr akzeptiert werden darf), nbf (nicht vor, ein NumericDate, vor dem der Token nicht akzeptiert werden darf), iat (ausgegeben um) und jti (JWT-ID, ein eindeutiger Bezeichner zur Abwehr von Replay-Angriffen).
NumericDate ist die Anzahl der Sekunden seit 1970-01-01T00:00:00Z UTC (Unix-Epoche), nicht Millisekunden. Dies ist eine haufige Fehlerquelle fur Entwickler, die mit JavaScripts Date.now() (das Millisekunden zuruckgibt) vertraut sind. Der Payload eines Standard-JWTs ist signiert, aber nicht verschlusselt -- jede Partei, die den Token abfangt, kann alle Claims lesen. Enthalt der Payload sensible Daten, sollte der Token mit JWE (RFC 7516) verschlusselt oder ausschliesslich uber TLS ubertragen werden.
Die Signatur: was sie schutzt -- und der alg:none-Fallstrick
Die Signatur wird uber die Signatureingabe berechnet: base64url(Header) + "." + base64url(Payload). Bei HS256 ist die Signatur HMAC-SHA256(Geheimnis, Signatureingabe), base64url-kodiert. Da die Signatureingabe sowohl Header als auch Payload enthalt, macht jede Anderung an einem der beiden Teile -- selbst ein einziges Zeichen -- die Signatur ungu ltig. Ein Server, der die Signatur korrekt pruft, kann sicher sein, dass der Token seit der Ausstellung nicht manipuliert wurde.
Im Jahr 2015 wurde in mehreren JWT-Bibliotheken eine bekannte Schwachstellenklasse entdeckt: der alg:none-Angriff. RFC 7519 erlaubt technisch "alg":"none" fur einen ungesicherten JWT ohne Signatur. Der Fehler: Einige Bibliotheken lasen das alg-Feld aus dem (unverifizierten) Header und wahlten danach den Verifikationspfad -- anderte ein Angreifer den Header auf "alg":"none" und entfernte die Signatur, erklarte die Bibliothek den Token fur gultig, ohne irgendeine Signatur zu prufen. CVE-2015-9235 dokumentiert dies in der beliebten node-jsonwebtoken-Bibliothek. Die Losung: Die verifizierende Partei muss den erwarteten Algorithmus explizit angeben und jeden Token ablehnen, dessen Header einen anderen Algorithmus nennt.
Dekodieren vs. Verifizieren: warum der Unterschied wichtig ist
Dekodieren eines JWTs bedeutet, base64url-Dekodierung auf die drei Teile anzuwenden, um das JSON des Headers, den Payload und die rohen Signatur-Bytes wiederzugewinnen. Es wird kein Schlussel benotigt. Jeder, der einen JWT-String besitzt, kann ihn dekodieren und die Claims lesen. Das ist so gewollt -- JWTs sind keine opaken Container. Ein Entwickler, der ein Authentifizierungsproblem debuggt, ein Ingenieur, der ein Zugriffsprotokoll liest, ein Sicherheitsforscher, der ein System pruft: Alle konnen jeden JWT, den sie besitzen, dekodieren.
Verifizieren eines JWTs bedeutet, zu prufen, ob die Signatur vom richtigen Schlussel uber die exakten Header- und Payload-Bytes erzeugt wurde. Eine erfolgreiche Verifikation beweist zwei Dinge: Der Token wurde vom Inhaber des Signierschlussels erstellt (Authentizitat), und weder Header noch Payload wurden seit der Signierung geandert (Integritat). Die Verifikation erfordert den Schlussel -- das gemeinsame Geheimnis fur HMAC, den offentlichen Schlussel fur RSA/ECDSA.
Der kritische Fehler ist, einen dekodieren JWT als vertrauenswurdig zu behandeln, ohne ihn zu verifizieren. Ein Client, der Claims aus einem JWT im localStorage liest und auf dieser Basis handelt, ohne serverseitige Signaturprufung, vertraut Daten, die jeder Nutzer frei falsch en kann. Ein Angreifer kann {"sub":"admin","role":"superuser"} erstellen, mit beliebigem Header base64url-kodieren und eine gefalschte oder leere Signatur anhangen -- das lasst sich problemlos dekodieren. Autorisierungsentscheidungen mussen stets nach kryptografischer Verifikation auf dem Server getroffen werden. Ein JWT-Dekodierer ist das richtige Werkzeug fur Inspektion und Debugging; er ersetzt niemals die serverseitige Verifikation in einer laufenden Anwendung.