JWT-Authentifizierungsfehler, die weiterhin in der Produktion auftauchen
JWT-Fallstricke, die 2026 weiterhin in den Produktionscode gelangen: Algorithmus-Verwirrung, schwache Geheimnisse, fehlende Ablaufzeit und wie Bibliotheken still versagen.
JWTs gibt es schon lange genug, dass die gängigen Fehler dokumentiert, als Blog-Posts veröffentlicht und trotzdem weiterhin im Produktionscode gefunden wurden. Das liegt nicht daran, dass Entwickler unachtsam sind - es liegt daran, dass die Fehlermodi genuinen subtil sind, Bibliotheken Standardwerte inkonsistent handhaben und die JWT-Spezifikation selbst einige Entscheidungen dem Implementierer überlässt, die wahrscheinlich nicht hätten überlassen werden sollen.
Das ist ein Durchgang durch die Fehler, die ich (und mehrere Freunde, die Sicherheitsreviews machen) 2024, 2025 und bisher 2026 haben ausgeliefert sehen.
Eine kurze Auffrischung
Ein JWT besteht aus drei durch Punkte getrennten Base64URL-kodierten Abschnitten:
<header>.<payload>.<signature>
Der Header deklariert den Signierungsalgorithmus (alg), der Payload enthält Claims (sub, exp, iat und beliebige benutzerdefinierte Felder), und die Signatur wird erzeugt, indem die <header>.<payload>-Zeichenkette entweder mit einem symmetrischen Schlüssel (HMAC-SHA256, abgekürzt HS256) oder einem privaten Schlüssel (RSA oder ECDSA) signiert wird.
Den Payload kann man ohne Schlüssel dekodieren - er ist Base64, nicht verschlüsselt. Ein Token in unseren JWT Decoder einfügen, um zu sehen, was drin ist. Es zu validieren ist eine andere Frage, und dort läuft alles schief.
Fehler 1: alg: none akzeptieren
Die JWT-Spezifikation erlaubt "alg": "none", um ein unsigniertes Token anzuzeigen. Mehrere weit verbreitete Bibliotheken haben historisch gesehen solch ein Token als gültig akzeptiert, wenn man die verify-Funktion aufgerufen hat, ohne explizit anzugeben, welche Algorithmen man erwartet.
Der Angriff: Der Angreifer nimmt ein echtes Token, dekodiert es, ändert den Payload auf {"sub": "admin"}, kodiert es erneut mit "alg": "none", leert den Signaturabschnitt und sendet es ein. Ein nachgiebiger Verifizierer akzeptiert es, weil das Algorithmus-Feld sagt “muss nicht geprüft werden.”
Das ist in modernen Bibliotheken behoben, aber nicht universell. Einige Bibliotheken akzeptieren immer noch none, wenn man keine explizite algorithms-Liste übergibt. Den Verifizierer immer mit dem genauen Algorithmus konfigurieren, mit dem man ausgestellt hat, nicht “irgendeinen Algorithmus, den das Token angibt zu verwenden.”
// Schlecht: vertraut beliebigem Algorithmus, den der Angreifer in den Header schreibt
jwt.verify(token, secret);
// Gut: schlagt bei jedem anderen Algorithmus geschlossen fehl
jwt.verify(token, secret, { algorithms: ['HS256'] });
Fehler 2: Algorithmus-Verwirrung (HS256 vs. RS256)
Das ist exotischer, wurde aber in echten Schwachstellen ausgenutzt. Der Angriff missbraucht Bibliotheken, die den Verifikationsalgorithmus aus dem Token-Header statt aus der Server-Konfiguration wählen.
Szenario: Der Server signiert mit RS256 und veröffentlicht den öffentlichen RSA-Schlüssel für Clients zur Verifikation. Ein Angreifer erstellt ein Token mit "alg": "HS256" und signiert es - den öffentlichen Schlüssel als HMAC-Geheimnis verwendend. Der Server, der den Header liest, lädt “den Schlüssel” (den öffentlichen Schlüssel, der kein Geheimnis ist), übergibt ihn an seinen HMAC-Verifizierer, und die Signatur stimmt überein. Das Token validiert.
Die Abwehr ist dieselbe wie für alg: none: explizit sein, welchen Algorithmus man akzeptiert, und nicht den Token-Header beeinflussen lassen, welcher Verifikationspfad läuft.
Fehler 3: Schwache HMAC-Geheimnisse
HS256 ist HMAC-SHA256. Es ist nur so stark wie der geheime Schlüssel. Wenn man ein kurzes oder erratbares Geheimnis wählt, ist es per Brute-Force angreifbar.
Die Schwachstelle, die ich dreimal über Code-Reviews gesehen habe: ein JWT_SECRET, das an einem Punkt auf changeme in einer Staging-Umgebung gesetzt und in die Produktion kopiert wurde. 9 Zeichen aus ASCII-Kleinbuchstaben sind unter 45 Bit Entropie. Ein vernünftig ausgestatteter Angreifer, der ein einziges gültiges Token abfängt, kann dieses Geheimnis in Minuten mit hashcat -m 16500 knacken.
Für HS256 sollte das Geheimnis mindestens 256 Bit Entropie haben - 32 zufällige Bytes. Mit unserem Hash Generator oder einem CSPRNG generieren:
openssl rand -base64 32
Das Geheimnis wie ein Datenbankpasswort behandeln: in einem Secret-Manager, nach einem definierten Zeitplan rotiert, niemals in git committet.
Fehler 4: Keine Ablaufzeit oder eine sehr lange
Ein JWT enthält einen exp-Claim (Ablaufzeit, Unix-Sekunden). Wenn man ein Token ohne exp ausgibt, ist es für immer gültig. Wenn etwas es leakt - ein Client-seitiger Logging-Fehler, eine Browser-Erweiterung, ein falsch konfiguriertes CDN - hat der Angreifer dauerhaften Zugang, bis man das Geheimnis rotiert (was jedes Token überall invalidiert).
Ein kurzes exp setzen. Wie kurz, hängt vom Anwendungsfall ab:
- Zugangstokens für APIs: 15 Minuten ist eine gängige Wahl
- Refresh-Tokens: länger (Stunden bis Wochen), aber sorgfältiger gespeichert
- Einmalige Aktionen (Passwort-Reset, E-Mail-Verifikation): 15 Minuten bis eine Stunde
Wenn die server-seitige Bibliothek eine “ohne Ablaufzeit verifizieren”-Option hat, diese in der Produktion niemals verwenden. Diese Flagge existiert für Tests.
Fehler 5: Tokens in localStorage speichern
Ein JWT in localStorage ist von jedem JavaScript, das auf der Seite läuft, lesbar, was einschließt:
- Jedes Drittanbieter-Skript, das man einbettet (Analytics, A/B-Tests, Chat-Widgets)
- Jeden XSS-Angriff, der es schafft, auf irgendeiner Seite der eigenen Domain auszuführen
- Jede Browser-Erweiterung, die berechtigt ist, auf dieser Domain zu laufen
Jeder dieser Fälle lässt das Token zur Tür hinaus gehen. Das Token ist ein Bearer-Credential - wer es hat, kann es verwenden.
Die Alternative ist ein HTTP-only, Secure, SameSite=Strict Cookie. Es ist immer noch anfällig für CSRF, aber CSRF wird mit einem separaten Token oder dem Double-Submit-Cookie-Muster verteidigt. XSS-Exfiltration via JavaScript ist strukturell unmöglich gegen ein Cookie, das JavaScript nicht lesen kann.
Wenn man ein System erbt, das JWTs in localStorage speichert und der Wechsel zu Cookies ein vollständiges Auth-Refactoring erfordert, ist die sofortige Abschwächung eine strikte Content Security Policy, die Inline-Skripts und externe Skriptquellen ablehnt, die man nicht kontrolliert. CSP macht XSS nicht sicher - es erhöht die Angriffsschwelle.
Fehler 6: Sensible Daten in den Payload legen
Der JWT-Payload ist Base64, nicht verschlüsselt. Jeder, der das Token empfängt, kann es dekodieren.
Dinge, die nicht in einem JWT sein sollten:
- Interne Benutzer-IDs, die man anderswo nicht exposiert
- PII über das hinaus, was der Nutzer bereits über sich selbst sieht
- Rollen, die auf interne Infrastruktur abbilden (statt auf benutzerseitige Berechtigungen)
- Alles, das, wenn es von einem Dritten gelesen wird, der das Token irgendwie erhält, peinlich wäre oder dem Nutzer schaden würde
Ein JWT-Payload sollte das Minimum sein, das notwendig ist, um die Anfrage zu routen: Subjekt, Aussteller, Ablaufzeit, eine Handvoll grober Scopes. Die Details leben in der eigenen Datenbank, bei jeder Anfrage nach der Nutzer-ID geladen.
Fehler 7: iss und aud nicht validieren
Die iss (Aussteller)- und aud (Zielgruppe)-Claims sind aus einem Grund da. Wenn man mehrere Dienste hat, die alle JWTs verwenden - beispielsweise eine API und eine Admin-Konsole - und sie verwenden verschiedene Signierungsschlüssel, ist das gut. Wenn sie einen Schlüssel teilen, ist das Validieren von aud das, was verhindert, dass ein für die Admin-Konsole ausgestelltes Token von der API akzeptiert wird.
jwt.verify(token, secret, {
algorithms: ['HS256'],
issuer: 'https://auth.example.com/',
audience: 'https://api.example.com/',
});
Ohne diese Prüfungen ist ein Token, das für irgendeinen Dienst in der eigenen Flotte gültig ist, für alle gültig.
Fehler 8: Fehlende Uhrentoleranz-Behandlung
Wenn man exp validiert, vergleicht man es mit “jetzt” auf dem Verifizierer. Wenn der Aussteller und der Verifizierer auf verschiedenen Maschinen mit desynchronisierten Uhren sind, lehnt man gelegentlich Tokens ab, die der Aussteller gerade geprägt hat.
Die meisten Bibliotheken haben eine clockTolerance-Option (oft standardmäßig 30 Sekunden). Diese nicht deaktivieren. Sicherstellen, dass die Server NTP ausführen.
Eine minimale Checkliste
Bevor ein JWT-authentifiziertes System ausgeliefert wird:
- Explizite Algorithmusliste bei jedem
verify-Aufruf. Niemals auf Standards verlassen. - Geheimnis-Entropie >= 256 Bit für HS256; RS/ES-Schlüssel aus einem ordentlichen Schlüsselpaar-Generator.
- Kurzes
expbei jedem ausgestellten Token. Niemals ohne eins minten. - Speicherung in einem HttpOnly-Cookie, es sei denn, man hat einen konkreten Grund für das Gegenteil.
- Minimaler Payload. Nutzer-ID, Aussteller, Zielgruppe, Ablaufzeit, Scopes. Nichts anderes.
- Aussteller- und Zielgruppenvalidierung bei jeder Verifikation erzwungen.
- Rotationsplan - wie widerruft man ein kompromittiertes Token? Wie rollt man den Signierungsschlüssel?
Die Rotationsfrage ist es wert, innezuhalten. JWTs sind “zustandslos” in dem Sinne, dass ein Verifizierer nicht für jede Anfrage eine Datenbank prüfen muss - aber diese Eigenschaft bedeutet, dass sie nicht vor ihrem Ablauf widerrufen werden können. Jedes Widerrufsschema (Positivliste gültiger Token, Negativliste widerrufener) beinhaltet Zustand, was einen Teil des Zwecks durchkreuzt. Kurze Ablaufzeit + Refresh-Tokens ist der Standard-Kompromiss: das kurzlebige Zugangstokens kann nicht widerrufen werden, aber sein Gültigkeitsfenster ist kurz, und das langlebigere Refresh-Token ist ein datenbankgestütztes Credential, das widerrufen werden kann.
Für lokales Debugging
Wenn man ein Token inspizieren muss - prüfen, welche Claims drin sind, wann es abläuft, welcher Algorithmus verwendet wurde - es in den JWT Decoder einfügen. Er läuft im Browser, sodass das Token nirgendwohin hochgeladen wird. Zum Generieren einer Testsignatur verarbeitet der HMAC Generator die HS256/HS384/HS512-Familie. Alles Ernstere sollte im Code, unter Tests, geschehen.
JWTs sind nicht kompliziert, haben aber ein hohes Verhältnis von “Fallstricken pro Feature.” Die gängigen Fehler zu kennen, ist das meiste davon, was ein Auth-System, das funktioniert, von einem, das darauf wartet, durchbrochen zu werden, unterscheidet.
In diesem Artikel erwähnte Tools
- JWT Decoder - Decode and inspect JWT tokens — header, payload and claims.
- Hash Generator - Generate SHA-1, SHA-256, SHA-384 and SHA-512 hashes from text.
- HMAC Generator - Generate HMAC signatures with SHA-1, SHA-256, SHA-384 and SHA-512.