Hör auf, JWT zu verwenden
(gist.github.com/samsch)- JWT eignet sich nicht dafür, Nutzer eingeloggt zu halten; für diesen Zweck sind normale Cookie-Sessions besser geeignet
- Die JWT-Spezifikation geht von kurzlebigen Tokens mit einer Laufzeit von etwa 5 Minuten oder weniger aus, während Sessions eine längere Lebensdauer benötigen
- Sichere zustandslose Authentifizierung ist schwer zu erreichen, und um Tokens sicher zu handhaben, ist letztlich doch irgendeine Form von Zustandspeicher nötig
- Ein JWT, das nur ein einfaches Session-Token enthält, ist im Vergleich zu normalen Session-Cookies ineffizienter und weniger flexibel, und Authentifizierungsdaten sollten nicht in localStorage oder
sessionStoragegespeichert werden - Wenn kurzlebige signierte Tokens benötigt werden, ist PASETO, das für Sicherheit entworfen wurde, die bessere Wahl, sollte aber nicht für Sessions verwendet werden
Kurzfassung
- JWT sollte nicht verwendet werden, um Nutzer eingeloggt zu halten; für diesen Zweck sind normale Cookie-Sessions das bessere Werkzeug
- JWT wurde für diesen Zweck nicht entworfen und ist dafür nicht sicher, während reguläre Cookie-Sessions besser zur Aufrechterhaltung von Login-Sessions geeignet sind
- Als verwandtes Thema gilt außerdem: Authentifizierungsnachweise einschließlich JWT-Tokens sollten nicht in localStorage oder
sessionStoragegespeichert werden - Es gibt Präsentationen zum Thema JWT, aber andere Themen wie CSRF-Schutz werden meist nur kurz behandelt und sollten daher separat aus anderen Quellen gelernt werden
- Auch die am Ende des Videos genannten „gültigen“ JWT-Anwendungsfälle lassen sich leicht mit besseren und sichereren Werkzeugen abdecken, konkret mit PASETO
Warum JWT vermieden werden sollte
- Die JWT-Spezifikation wurde nur für sehr kurzlebige Tokens mit einer Laufzeit von etwa 5 Minuten oder weniger entworfen, während Sessions länger leben müssen
- Zustandslose Authentifizierung auf sichere Weise ist nicht möglich; um Tokens sicher zu verarbeiten, ist zwingend irgendein Zustand erforderlich
- Wenn ohnehin ein Datenspeicher benötigt wird, ist es besser, alle Daten zu speichern, statt nur einen Teil des Token-Zustands zu verwalten
- Näher behandelt wird das unter http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/
- Es gibt tatsächlich Anwendungen, die JWT auf diese Weise einsetzen, aber solche Anwendungen sind fehlerhaft, daher sollte derselbe Fehler nicht wiederholt werden
- Ein JWT, das nur ein einfaches Session-Token speichert, ist im Vergleich zu normalen Session-Cookies ineffizienter und weniger flexibel und bringt keine zusätzlichen Vorteile
- Der JWT-Standard selbst genießt unter Sicherheitsexperten kein Vertrauen und sollte daher generell für Sicherheits- und Authentifizierungszwecke ausgeschlossen werden
- Die ursprüngliche Spezifikation ermöglichte das Erstellen gefälschter Tokens, und es könnten weitere Fehler enthalten sein
- Die Probleme der JWT-Familie werden tiefergehend in JWT: The JSON Web Token standard is bad and everyone should avoid it behandelt
Einwände
- Der Einwand „Auch Google verwendet JWT“ bezieht sich nicht auf Browser-Nutzersessions
- Google verwendet für Browser-Nutzersessions kein JWT, sondern normale Cookie-Sessions
- JWT wird nur als Übertragungsmechanismus für Single Sign On verwendet, um die Login-Session eines Servers oder Hosts an einen anderen Server oder Host weiterzugeben
- Diese Nutzungsweise fällt in den Bereich sinnvoller JWT-Anwendungsfälle
- Google verfügt über die Ressourcen und Sicherheitsexperten, um eine sicherere JWT-Implementierung zu entwickeln und zu pflegen
- Googles JWT ist faktisch nicht dasselbe wie JWT anderswo
- Der Einwand „Zustandslos ist besser“ passt nicht zu den Anforderungen sicherer Authentifizierung
- Ohne enorme Ressourcen lässt sich echte zustandslose Authentifizierung nicht sicher betreiben
- Siehe dazu auch Stateless is a lie
- Das Problem „Ich weiß nicht, wie man Sessions einrichtet“ lässt sich in den meisten Fällen durch die Dokumentation und Implementierungen von Webserver-Frameworks lösen
- Session-Technik ist nicht besonders neu, deshalb sieht man seltener Artikel, die Sessions erklären
- Mit der Dokumentation der Session-Implementierung sollte sich der Einrichtungsprozess nachvollziehen lassen
- Fast jedes Webserver-Framework enthält eine Session-Implementierung, und selbst wenn sie nicht standardmäßig aktiviert ist, lässt sie sich normalerweise leicht aktivieren
- Express und andere Node.js-Frameworks sind wegen ihrer hohen Modularität und ihres Single-Purpose-Charakters teilweise eine Ausnahme
- In Express reicht es, die Middleware
express-sessionund einen zum Speicher passenden Store-Connector zu verwenden - Empfohlen wird die Verwendung von
connect-session-knexmit Postgres, MySQL oder möglichst SQLite
Kurzlebige Tokens
- Wenn für einen Zweck kurzlebige signierte Tokens benötigt werden, gibt es mit PASETO eine bessere Spezifikation, die mit Blick auf Sicherheit entwickelt wurde
- Auch mit PASETO sollte man es nicht für Sessions verwenden
Wie Sessions funktionieren
- Wer mehr darüber lernen möchte, wie Sessions funktionieren, sollte sich joepie91s gist ansehen
2 Kommentare
JWT dient dazu, Tokens zu verschlüsseln und DB-Abfragen zu reduzieren; es ist kein Konzept, das im Gegensatz zur Cookie-Authentifizierung steht. Wenn man JWT in einem sicheren Cookie speichert, ist das Risiko eines Diebstahls dasselbe wie bei der Legacy-Cookie-Authentifizierung.
Das Verwalten einer Ablauf-Liste, um JWTs ablaufen zu lassen, bietet aus Performance-Sicht Vorteile. Es gibt einen Kostenunterschied zwischen dem Abfragen nur der Ablauf-Informationen in Redis und dem Ausführen von DB-Abfragen über alle Benutzer.
Zehntausende indexbasierte Abfragen über 100.000 Mitgliedszeilen (Legacy-Cookie-Methode)
vs
Zehntausende Abfragen von 50 Einträgen in der Ablauf-Liste in Redis (sofortiges JWT-Expiry)
JWT hat durchaus Vorteile. In kleineren Umgebungen fällt der Unterschied nur weniger stark ins Gewicht.
Hacker-News-Kommentare
Es fehlt ein wichtiger Hinweis: Es geht um browserbasierte Benutzersitzungen
Für die Kommunikation zwischen Services gibt es viele Fälle, in denen sich JWT gut einsetzen lässt
Nebenbei: Ich habe einen Teil des verlinkten Artikels gelesen, darunter z. B. https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba.... Wenn JWT wirklich ein so furchtbar unsicherer Standard ist, dann veröffentlicht doch bitte eine Methode, um AWS STS
AssumeRoleWithWebIdentityzu hacken – oder veröffentlicht sie nicht und startet stattdessen auf den produktiven AWS-Konten von Fortune-500-Unternehmen einfach Crypto-Miner. Wenn JWT wirklich so unsicher ist, sagt bitte Bescheid, wenn ihr Erfolg habt /sarkasmusDer Signatur-/Verschlüsselungsteil von JWT ist komplex, und auch gängige JWT-Bibliotheken sind erst inzwischen größtenteils zur Vernunft gekommen; früher war das nicht so. Viele Bibliotheken akzeptierten den
"none"-Algorithmus [1], und in manchen Fällen wurde ein öffentlicher Schlüssel wie ein Shared Secret verwendet, sodass Angreifer Tokens fälschen konnten [2]. Das ist genau das Resultat der Komplexität, die der verlinkte Artikel kritisiertJWT kann außerdem Funktionen, die man bei Benutzersitzungen möchte, teils gar nicht leisten. Ohne irgendwo eine Sperrliste zu führen, lässt es sich nicht ungültig machen. Wenn man aber bei jeder Anfrage eine Kennung gegen eine Sperrliste prüfen muss, kann man genauso gut eine undurchsichtige Session-ID verwenden und bei jeder Anfrage nachschlagen. Natürlich kann man kurzlebige Tokens verwenden und ständig erneuern, aber in gewöhnlichen Anwendungen, die ohnehin State halten müssen, gibt es wenig Grund dafür
Allerdings stimme ich voll zu, dass signierte Tokens in verteilten Systemen oder bei Kommunikation zwischen Maschinen nützlich sein können. Man sollte die beiden Fälle nicht verwechseln
[1] https://nvd.nist.gov/vuln/detail/cve-2022-23540
[2] https://nvd.nist.gov/vuln/detail/CVE-2024-54150
Inzwischen haben die wichtigen Bibliotheken in mehreren Sprachen vernünftigere Defaults, daher würde ich sagen, dass es heute in der Praxis ziemlich sicher geworden ist
Wenn „sicher, sofern man es richtig anfasst und benutzt“ automatisch gutes Design bedeuten würde, dann würde dasselbe auch für X.509 gelten
In vielen Fällen gibt es bessere Alternativen. Standard-Session-Tokens oder API-Keys werden auf den meisten großen Websites breit eingesetzt und passen für die meisten Anwendungsfälle fast perfekt
Das soll nicht heißen, dass solche Standards keinerlei Wert haben. Das Beste daran ist wohl, dass es ein grundlegender Standard ist, um Dinge ohne ASN.1-Encoding hin und her zu schicken, und die ASN.1-Werkzeuge wirken extrem fragil und fehleranfällig
Ich weiß zum Beispiel nicht, wie man SAML konkret ausnutzt, aber ich weiß, dass es ein schrecklicher Standard ist, weil es den gesamten XML-Parser zur Angriffsfläche macht. Ich bin kein Sicherheitsforscher und weiß nicht, wie man Schwachstellen in XML-Parsern findet, aber ich kann trotzdem erkennen, dass eine große Angriffsfläche schlecht ist
JWT soll unsicher sein, selbst wenn man ein vertrauenswürdiges RSA-/Public-Key-basiertes Signaturverfahren verwendet? Also auch ohne Shared Secret?
Auch die Behauptung, JWT lebe zu lange, wirkt seltsam. Man kann die Lebensdauer von JWT begrenzen und ein Erneuerungsmodell gegenüber der Autorisierungsinstanz haben. Selbst bei Cookie-basierten Sessions wird am Ende doch irgendwo etwas gespeichert. Ein JWT kann man nur 5–15 Minuten gültig machen, und 15 Minuten liegen in der Größenordnung der Cache-Dauer vieler Autorisierungssysteme, einschließlich Entra. Auch 5-Minuten-Tokens können mit einem Erneuerungssystem im Browser gut funktionieren
Außerdem bevorzuge ich es, Identität/Authentifizierung von Anwendungs- und API-Services zu trennen. Man kann den Kontext externalisieren, und JWT pro Anfrage zu verarbeiten ist oft leichter zu handhaben als gemeinsam genutzte Cache-/State-Systeme, die sporadisch ausfallen können. Bei signierten Tokens kann man die Signatur gegenüber einer bekannten Instanz verifizieren
Davon abgesehen ist die Signatur kryptografisch gültig. Man kann einfach alle JWT jedes Mal mit kurzer Lebensdauer verifizieren
Zur Einordnung: OIDC-Tokens sind alle JWT
Wenn man Sessions mit JWT-Sperrlisten vergleicht, gibt es auch eine Logik zugunsten von JWT-Sperrlisten. JWT haben eine begrenzte Ablaufzeit, also muss man eine Sperrliste nur für Tokens pflegen, die noch nicht abgelaufen sind
Wahrscheinlich ist nur ein kleiner Teil der noch im Umlauf befindlichen gültigen JWT gesperrt, also fragt man pro Request nur einen sehr kleinen Datensatz ab
Bei Sessions ist die Liste gültiger Sessions wahrscheinlich um mehrere Größenordnungen größer als eine Sperrliste, daher sind die Lookup- und Speicherkosten durch Statefulness höher
Außerdem heißt es im Artikel, JWT seien zustandslos, aber meist sind sie das nicht. In der Regel validiert man nicht nur das JWT, sondern lädt bei jeder Anfrage auch das passende Identitätsobjekt, also Benutzerdetails, um zu prüfen, ob der Benutzer noch aktiv ist und die betreffende Aktion noch ausführen darf. Man kann pro Benutzer eine Sperrliste oder einen Wert wie
minimum_issued_atverwenden, um dasiat-Feld des JWT zu validieren. Damit ist auch das Muster „auf allen Geräten abmelden“ möglich: Diese Aktion setzt einfachminimum_issued_atdes Benutzers auf$NOW, und alle vorherigen Tokens werden damit ungültig. Eine Abfrage einer individuellen Sperrliste ist nicht nötigselectmit Index in der Datenbank und gibt 0–1 Zeilen zurück. In den meisten Fällen ist das kein ProblemDieser Artikel verlinkt für den Großteil des „Warum“ auf andere Blogbeiträge, und diese Blogbeiträge scheinen sich meist darüber zu beschweren, dass man „einzelne JWT-Token nicht ungültig machen kann“
Als ich das jeweils implementiert habe, war die übliche Vorgabe, irgendwo auf ungültig gemachte Nonces zu prüfen, und damit wäre auch das zweite Argument in diesem Beitrag gelöst
Die Aussage „Die JWT-Spezifikation selbst wird von Sicherheitsexperten nicht vertraut“ scheint mehr Belege zu brauchen als einen einzelnen Blogpost. Und dieser Beitrag scheint größtenteils schlechte Implementierungen zu kritisieren, aber Probleme durch schlechte Implementierungen gibt es bei jedem Standard
Insgesamt weiß ich nicht, was ich erwartet habe, als ich auf einen beliebigen gist-Link geklickt habe
Außerdem kann man im Browser durchaus JWTs mit kürzerer Lebensdauer verwenden und den Agenten sie selbst erneuern lassen. Mit Azure Entra oder diversen anderen Anbietern funktioniert das in der Praxis genau so. Man kann JWTs relativ kurz halten, etwa 5–15 Minuten, und zusätzlich prüfen, ob die
jtiverworfen wurdeJWTs sind sehr nützlich, um die Autorisierungsinstanz vom Anwendungs-/API-System zu trennen und wiederzuverwenden. Man verlagert die Angriffsfläche, aber auf eine vertrauenswürdige Weise. Weltweit wird an vielen Stellen Public-Key-Kryptografie verwendet, einschließlich SSH. Gemeinsame Geheimnisse oder langlebige Token würde ich nicht verwenden, aber kurzlebige, mit Public Keys signierte Token aus einer verifizierten und bekannten Quelle sind im Allgemeinen in Ordnung
Eher sind in der Praxis oft API-Schlüssel das eigentliche Problem. Ich musste das gerade erst implementieren; in meinem Fall habe ich API-Schlüssel ebenfalls wie Bearer-Token aussehen lassen, mit einem kurzen
sak.-Präfix, gefolgt von einem Identitätsteil (base64url-UUID-Bytes) und danach einem Geheimwert (base64url-Bytes). In der Datenbank speichere ich die UUID und einen salt+hash auf Passphrasen-Niveau, der aus dem Geheimwert erzeugt wird. Daher muss ein erzeugter API-Schlüssel als Geheimnis behandelt werden, und in der Datenbank wird er nur in eine Richtung gespeichert, sodass eine Datenbankkompromittierung nicht automatisch zu einer Kompromittierung der Authentifizierung führtTrotzdem ist ein API-Key-Leak sehr viel wahrscheinlicher als ein Problem mit einer gut implementierten JWT-Lösung
Ich bin zufällig auf diesen Artikel gestoßen und fand es interessant, dass das Thema gerade wieder aktuell ist, weil ich früher viel dazu gearbeitet habe. Dann habe ich geklickt und festgestellt, dass der Autor auf einen Teil meines Materials verlinkt hatte. Da kamen wirklich alte Erinnerungen hoch
Wie auch immer, viel klügere Leute als ich haben dieses Thema über Jahre hinweg breit behandelt, aber ich denke auch 2026 noch, dass JWT für Web-Authentifizierung das falsche Werkzeug ist. Für Service-zu-Service-Zwecke ist es in Ordnung, aber wenn man die Wahl hat, sollte man einfach PASETO verwenden. Das löst viele Probleme
https://www.paseto.io/
Ich bin gerade dabei, RabbitMQ für Benachrichtigungs-Push auf einer Website anzubinden. Ich verwende dabei JWT-Authentifizierung, um zu steuern, was der Client wo lesen darf, mit kurzer Lebensdauer und regelmäßiger Token-Erneuerung
Ich kenne nicht viele andere Setups, die in ihrer Einfachheit daran herankommen. Man fügt einfach einen Endpoint hinzu, der für gültige Sitzungen JWT-Token ausgibt, und schon ist es erledigt; auch benutzerspezifische Berechtigungen sind möglich
Einer der verlinkten Artikel, die erklären sollen, warum man JWT nicht verwenden sollte, ist selbst in wohlwollender Lesart merkwürdig
https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba...
Zusammengefasst lautet die Aussage: „Einige Bibliotheken hatten Bugs“, und dann wird empfohlen, libsodium dazuzunehmen und es selbst zu machen. Das ist ein derart absurder Ratschlag, dass man ihn kaum ernst nehmen kann. Jede Software hat Bugs. Bei Heartbleed stand das ganze Internet in Flammen, und trotzdem verwenden wir weiterhin TLS und OpenSSL
Die Aussage „Die JWT-Spezifikation wurde speziell nur für Token mit sehr kurzer Lebensdauer entworfen, ungefähr 5 Minuten oder weniger“ höre ich zum ersten Mal, und ich kann auch keine Belege dafür finden. In RFC 7519 steht so etwas nicht
Üblicherweise verwende ich JWT als eine Art Authentifizierungs-Cache. Man erhält ein Authentifizierungs-Token von einem Authentifizierungsdienst, und dieses Token erteilt dann Berechtigungen gegenüber anderen Diensten
Das hat mehrere Vorteile, aber der Kern ist, dass nachgelagerte Dienste weder mit der Authentifizierungsdatenbank interagieren müssen noch die Berechtigung haben müssen, selbst Token auszustellen. Vorausgesetzt, man verwendet RS256 und nicht HMAC. Wenn also ein nachgelagerter Dienst kompromittiert wird, ist das nicht annähernd so verheerend wie die Kompromittierung eines Dienstes mit Zugriff auf die Authentifizierungsdatenbank
Wenn sich sensible Daten im Token befinden, muss man JWE verwenden, aber das ist nicht besonders gut, weil man dann bei jeder Nutzung einen internen Dienst mit dem privaten Schlüssel zum Entschlüsseln des Tokens anfragen muss
Eine Struktur, die ich häufig verwende, ist
{"id": (uuid), "scopes": ["scope:read/write"]}Auch für SPAs ist das ziemlich gut. Der statische Site-Server kann nämlich JWE mit einem Public Key prüfen, bevor er Ressourcen ausliefert. Meine Methode ist, statische Sites in der Form
/(scope)/pathzu kompilieren, sodass der statische Dienst Seiten, auf die kein Zugriff bestehen darf, gar nicht erst ausliefert. Das ist sehr nützlich, wenn man dem Benutzer keine Funktionen zeigen will, die nur das Backend hat, wie etwa Admin-Panels, oder keine angreifbaren internen Service-Pfade offenlegen möchteDie JWT-Lebensdauer für den „Backend-Zugriff“ beträgt etwa 5 Minuten, und Dinge wie
/mewerden inlocalStoragezwischengespeichert, es sei denn,/refreshweist explizit an, denlocalStorage-Cache zu verwerfen. Der Request-Handler der SPA-Anwendung erkennt dann „Erneuerung erforderlich“ und erneuert das TokenIch denke, ein Großteil dieser Verantwortung liegt bei node/next- und Python-Bibliotheken. Das Backend schreibe ich in stark typisierten Sprachen, und das Frontend besteht immer aus vorab kompilierten statischen Seiten. Mein aktuelles Frontend-Setup nutzt VITE, mit einer vorgerenderten Landingpage und der Anwendung als normaler SPA
Selbst unter Berücksichtigung all dessen stimme ich diesem gesamten gist entschieden nicht zu. JWT kann so sicher gemacht werden, wie man es braucht
JWT ist in Ordnung, und der Titel wirkt etwas reißerisch
Stattdessen gäbe es gute Themen, über die man sprechen könnte: wann man verschlüsselte Werte (symmetrisch oder asymmetrisch), zufällige aber geheime Werte oder signierte Werte verwendet (lesbar, aber nicht manipulierbar), wo man solche Werte speichert (Arbeitsspeicher,
localStorage, Cookies), wie man verhindert, dass sie ewig gültig bleiben, und ob man sie schon vor ihrem natürlichen Ablaufzeitpunkt widerrufen können muss