Wie funktionieren JSON Web Tokens (JWT)?
JSON Web Tokens sind aus modernen Web-APIs kaum noch wegzudenken: Single Sign-On, OAuth-Flows, OpenID Connect, serverlose Authentifizierung — fast überall taucht das kleine, kompakte Token-Format auf. Hinter dem kryptisch wirkenden String aus drei base64url-kodierten Teilen steckt ein erstaunlich einfaches, aber sicherheitskritisches Konzept. Dieser Artikel erklärt Aufbau, Signaturen, Standard-Claims und die häufigsten Fehler.
Aufbau: Header.Payload.Signature
Ein JWT besteht aus drei base64url-kodierten Teilen, durch Punkte getrennt. Der Header ist ein kleines JSON-Objekt mit Typ-Information und dem Signatur-Algorithmus, zum Beispiel: { "alg": "HS256", "typ": "JWT" }. Der Payload (oder Claims-Set) ist ebenfalls JSON und enthält die eigentlichen Informationen über das Subjekt — User-ID, Berechtigungen, Ablaufzeit. Die Signatur sichert Header und Payload kryptografisch ab.
Wichtig: der Payload ist nur kodiert, nicht verschlüsselt. Jeder, der das Token sieht, kann den Inhalt lesen. Vertrauliche Daten gehören also nicht in den Payload. Wer Verschlüsselung braucht, nutzt JWE (JSON Web Encryption); JWT-Tokens, wie sie in der Praxis verwendet werden, sind in der Regel JWS — signiert, nicht verschlüsselt.
Signatur-Algorithmen
Im Header steht das Feld alg. Die verbreitetsten Varianten:
- HS256 (HMAC-SHA256): symmetrisch, ein gemeinsames Geheimnis zwischen Aussteller und Prüfer. Schnell und einfach, eignet sich gut, wenn beide Seiten unter eigener Kontrolle stehen.
- RS256 (RSA-SHA256): asymmetrisch, der Aussteller signiert mit einem privaten Schlüssel, beliebige Clients prüfen mit dem öffentlichen Schlüssel. Standard für OAuth-/OpenID-Identity-Provider, bei denen viele Clients Tokens validieren müssen.
- ES256 (ECDSA mit P-256 und SHA-256): elliptische Kurven, kleinere Schlüssel und Signaturen bei vergleichbarer Sicherheit zu RSA. Beliebt in mobilen und IoT-Kontexten.
- EdDSA (Ed25519): moderne Edwards-Kurven-Signatur, schnell, deterministisch und ohne klassische ECDSA-Implementierungsfallen. Wird in neueren Stacks zunehmend bevorzugt.
Standard-Claims
RFC 7519 definiert ein paar registrierte Claim-Namen, deren Bedeutung interoperabel ist:
- iss (issuer): wer hat das Token ausgestellt? Üblicherweise ein URL- oder Domain-Bezeichner.
- sub (subject): worüber spricht das Token? Meist die User-ID, gelegentlich eine Anwendung.
- aud (audience): für welche Empfänger ist das Token gedacht? Wer das Token validiert, sollte prüfen, ob er in dieser Liste steht.
- exp (expiration): UNIX-Timestamp, ab dem das Token abgelaufen ist. Bei der Prüfung zwingend einzuhalten.
- iat (issued at): UNIX-Timestamp der Ausstellung. Hilfreich, um sehr alte oder zukünftige Tokens als suspekt zu erkennen.
- jti (JWT ID): eindeutige ID des Tokens. Erlaubt, einzelne Tokens auf einer Sperrliste zu führen — wichtig für Revocation-Strategien.
Wo JWTs eingesetzt werden
In OAuth 2.0 sind Access Tokens und Refresh Tokens häufig JWTs. OpenID Connect setzt JWTs für das ID-Token ein, das dem Client Identitätsinformationen über den angemeldeten Nutzer mitteilt. APIs nutzen JWTs als Bearer-Tokens im Authorization-Header (Authorization: Bearer eyJhbGciOi...).
Der typische Lifecycle: Login schickt Username/Passwort an einen Auth-Server, der ein kurzlebiges Access-JWT (z. B. 15 Minuten) plus ein längerlebiges Refresh-Token zurückgibt. Folgeanfragen an die API tragen das Access-JWT mit, der Server prüft die Signatur und einige Claims. Läuft das Access-JWT ab, holt der Client mit dem Refresh-Token ein neues — bis die Sitzung am Auth-Server endet.
Häufige Fallen
- »alg: none"-Angriff: Bibliotheken, die alg blind vertrauen, lassen sich mit dem Wert „none" zum Akzeptieren unsignierter Tokens überreden. Die Algorithmen müssen vom Server, nicht vom Token, vorgegeben werden — etwa als explizite Whitelist.
- Fehlende exp-Prüfung: ohne aktive Validierung des exp-Claims bleibt ein einmal ausgestelltes Token unbegrenzt gültig. Jede Validierungsbibliothek bietet Optionen für Ablauf- und Clock-Skew-Prüfung.
- LocalStorage vs. HttpOnly-Cookie: ein JWT im localStorage ist via XSS auslesbar. Sicherer ist es, das Token in einem HttpOnly-, Secure-, SameSite-Cookie zu speichern — auch wenn das Cross-Origin-Setup etwas komplexer wird.
- Revocation: anders als eine klassische Session-ID lässt sich ein JWT nicht serverseitig »einfach invalidieren". Wer ein Token vor Ablauf sperren muss, braucht eine Sperrliste (jti-basiert) oder kurze Lebenszeiten plus Refresh-Tokens.
- Schwache HS256-Secrets: ein 8-Zeichen-»secret" lässt sich offline brute-forcen. HS256-Schlüssel sollten zufällig erzeugt sein und mindestens 256 Bit Entropie haben — sonst lieber gleich RS256.
Mein erster JWT-Bug in Produktion
2019 hatte ich einen Microservice gebaut, der JWTs zur Authentifizierung nutzte. Algorithmus HS256, Secret in der Umgebungsvariable, klassische Aufstellung. Drei Monate lief das problemlos. Dann meldete ein User: »ich kann mich nicht mehr einloggen«. Nach zwei Stunden Debug fanden wir es: die Server-Zeit war um 4 Minuten in die Zukunft gedriftet — der Client erzeugte ein Token mit iat = now, der Server prüfte es mit nbf <= now + 0, und das Token wurde als »noch nicht gültig« abgelehnt.
Die Lehre: JWT-Validation braucht eine Toleranz (»leeway«) von 30–60 Sekunden bei Zeit-Claims. Alle gängigen Libraries bieten das als Konfigurations-Parameter — fast niemand setzt ihn. Standardwert ist meist 0, was in der echten Welt regelmäßig zu Edge-Case-Fehlern führt. Seitdem prüfe ich in jedem JWT-Code, wo die Leeway sitzt.
Zweiter Lehrsatz: NTP-Synchronisation der Server. Ohne ordentliche Zeit-Synchronisation kannst du jede JWT-Implementierung kaputt schießen, selbst wenn der Code perfekt ist. systemd-timesyncd oder chrony sind Standard auf Linux, müssen aber tatsächlich aktiv sein. Bei Container-Deployments oft vergessen, weil der Container-Host die Zeit hält. In meinem Bug 2019 lief der Service auf einem Docker-Container, dessen Host seit 11 Tagen nicht mehr NTP gemacht hatte — und die Drift hatte sich kumuliert.
OAuth 2.0/2.1: wie JWT in das Gesamtbild passt
Viele Entwickler verwirren OAuth und JWT. OAuth 2.0 ist ein Autorisierungs-Framework, das definiert, wie Apps Berechtigungen erhalten. JWT ist ein konkretes Token-Format. Häufig sind die OAuth-Tokens als JWTs codiert (besonders bei OpenID Connect), aber das ist nicht zwingend. OAuth kann auch opake Strings zurückgeben — und JWTs werden auch außerhalb von OAuth-Flows verwendet (z. B. bei einfacher API-Authentifizierung ohne externen Identity Provider).
OAuth 2.1 (RFC-Status 2024, verbreitete Adoption ab 2026) zieht die Empfehlungen der letzten Jahre zusammen und macht einiges verbindlich: PKCE ist für alle Client-Typen Pflicht (war vorher nur für Mobile/SPA empfohlen). Implicit Flow ist deprecated — wer noch in Implicit-Flow läuft, muss zur Authorization-Code-mit-PKCE-Variante wechseln. Refresh-Token-Rotation ist empfohlen, einige IdPs (Auth0, Okta) erzwingen es bereits.
Praktisch heißt das für 2026: wer ein neues System mit OAuth aufsetzt, sollte direkt OAuth 2.1-konform starten. Wer ein bestehendes System hat, kann iterativ migrieren — PKCE zuerst, dann Implicit-Flow auf Authorization Code umstellen, dann Refresh-Token-Rotation aktivieren. Die meisten Identity-Provider haben einen Migrations-Pfad dokumentiert.
Refresh Tokens: das Stiefkind, das niemand richtig versteht
JWTs sollen kurzlebig sein (15 Minuten ist typisch). Aber kein Mensch will alle 15 Minuten neu einloggen. Die Lösung: Refresh Tokens. Das sind lange laufende (oft 30 Tage) Tokens, die nicht für API-Calls genutzt werden, sondern nur, um neue Access Tokens (=JWTs) auszustellen. Refresh Tokens werden typisch in einem httpOnly-Cookie gespeichert (kein Zugriff aus JavaScript), Access Tokens in-memory.
Refresh Token Rotation (OAuth 2.1 empfohlen): bei jedem Refresh-Vorgang wird ein NEUER Refresh Token ausgestellt, der alte wird sofort invalidiert. Wenn ein Refresh Token gestohlen wird, kann der Angreifer ihn maximal einmal benutzen — beim zweiten Versuch werden BEIDE Versuche (legitimer + bösartig) als Angriff erkannt und das gesamte Token-Family invalidiert. Das ist eines der eleganten Sicherheitsmuster der letzten Jahre.
Debugging-Workflow: was tun, wenn ein JWT nicht funktioniert?
Ein 5-Punkte-Workflow, der mir bei jedem JWT-Bug in den letzten Jahren geholfen hat:
- Token in den Decoder werfen. Unser JWT-Decoder zeigt dir Header und Payload sofort. Du siehst, welcher Algorithmus benutzt wird, welche Claims drin sind, und wann das Token abläuft.
- Server-Zeit vergleichen.
date -uauf dem Auth-Server, dann auf dem Resource-Server. Driften die um mehr als 30 Sekunden? Dann ist NTP kaputt und du musst das fixen, bevor du irgendwo anders schaust. - Key-ID (kid) im Header prüfen. Bei asymmetrischen Algorithmen (RS256, ES256) zeigt der
kid-Header an, welcher Public Key zur Verifikation genutzt werden soll. Wenn der Server diesen Key nicht kennt (z. B. nach einer Schlüssel-Rotation), wird das Token abgelehnt. - Algorithmus-Mismatch. Client signiert mit HS256, Server erwartet RS256 — Token wird abgelehnt. Manchmal liegt es an einer Library, die den Algorithmus automatisch wählt; manchmal an einer Konfiguration, die zwei verschiedene Werte erwartet. Header-Decode zeigt es schnell.
- Secret/Key-Inhalt. Bei HS256: ist das gleiche Geheimnis auf Sender und Empfänger? Trailing-Whitespace, Encoding-Probleme (Base64 vs Hex), Umgebungsvariablen mit Anführungszeichen — alles häufige Stolpersteine. Bei RS256: ist der Public Key der zum genutzten Private Key passende?
Mit diesem Workflow habe ich jeden produktionsrelevanten JWT-Bug der letzten 5 Jahre in unter 30 Minuten gelöst. Der häufigste Fehler ist Punkt 2 (Zeit-Drift), gefolgt von Punkt 5 (Secret-Mismatch). Punkt 1 (Decoder) sollte immer zuerst kommen, weil er das vorher unbekannte Token-Inneres explizit macht.
Häufige Fragen
Sind JWTs verschlüsselt?
Die in der Praxis meistens gemeinten JWTs (JWS) sind signiert, aber nicht verschlüsselt. Wer den Inhalt zusätzlich schützen will, kann sie als JWE-Tokens zusätzlich verschlüsseln — das ist aber selten nötig, wenn keine sensiblen Daten im Payload liegen.
Wie unterscheiden sich Access- und Refresh-Token?
Access-Tokens sind kurzlebig (Minuten) und gehen mit jeder API-Anfrage über die Leitung. Refresh-Tokens sind länger gültig (Stunden bis Wochen), liegen sicher beim Client und werden nur benutzt, um neue Access-Tokens zu erhalten. Damit bleibt der Schaden eines gestohlenen Access-Tokens klein.
Sollte ich JWT oder Sessions verwenden?
Für klassische, server-seitig gerenderte Anwendungen sind Sessions (Cookie-basiert, Server hält den State) oft einfacher und sicherer. Für API-only-Backends, mobile Apps oder verteilte Microservices spielen JWTs ihre Stärken aus: stateless, von beliebigen Instanzen prüfbar, gut interoperabel mit OAuth-Standards.
Was ist der Unterschied zwischen JWT und JWE?
JWT (JSON Web Token) ist signiert, aber nicht verschlüsselt — jeder kann den Inhalt lesen, aber niemand kann ihn ändern, ohne die Signatur zu invalidieren. JWE (JSON Web Encryption) ist verschlüsselt — der Inhalt ist nur für den Empfänger lesbar. Praktisch werden JWEs selten gebraucht: meist will man Authentifizierung, nicht Geheimhaltung des Inhalts. Im Header kann man am Algorithmus erkennen: HS256/RS256 = JWT, RSA-OAEP/A256GCM = JWE.
Wie lang sollte mein JWT-Secret sein?
Für HS256 mindestens 256 Bit (32 Byte) zufällige Daten — entsprechend einer 32-Zeichen-Base64-Sequenz. Längere Secrets bringen marginal mehr Sicherheit, kosten aber nichts. openssl rand -base64 64 erzeugt einen langen, sicheren Secret-Kandidaten. Wichtig: nicht erratbare Werte wie »supersecret123« — die werden in Dictionary-Angriffen sofort gefunden.
Kann ich ein JWT widerrufen, bevor es abläuft?
Eine der unangenehmsten JWT-Eigenschaften: ein einmal ausgestelltes JWT ist gültig, bis es abläuft — egal was passiert. Logout, Passwort-Änderung, Sperre durch Admin: das Token kann weiter benutzt werden, bis exp erreicht ist. Workarounds: (1) kurze Laufzeiten (15 Minuten), (2) serverseitige Blacklist mit gesperrten JTI-Werten (=stateful, untergräbt den »stateless«-Vorteil), (3) bei kritischen Aktionen zusätzliche Authentifizierung verlangen. Die meisten Production-Setups kombinieren (1) und (2) selektiv für High-Risk-Endpoints.
Kommentare
Die Kommentare werden von Disqus bereitgestellt. Bevor sie geladen werden, brauchen wir deine Einwilligung — Disqus ist ein Drittanbieter und setzt eigene Cookies.