JWT vs. Session-Cookies: Wie sie sich unterscheiden und wann Sie was einsetzen
Ein Session-Cookie ist ein Schlüssel zu einem Zustand, den der Server speichert; ein JWT trägt signierten Zustand zum Server. Aus diesem einen Unterschied ergeben sich alle Abwägungen.
Der Vergleich wird meist als JWT gegen Cookies formuliert, doch das sind gar nicht dieselben Dinge. Ein Cookie ist ein Weg, Daten mit jeder Anfrage mitzuschicken. Ein JWT ist ein Datenformat. Die eigentliche Wahl besteht zwischen einer serverseitigen Session, bei der der Browser eine zufällige ID hält und der Server nachschlägt, zu wem sie gehört, und einem in sich geschlossenen Token, bei dem der Client signierte Claims hält, die der Server ohne Nachschlagen prüfen kann. Ist das klar, beantworten sich die Fragen zu Widerruf, Speicherort und CSRF fast von selbst.
So funktioniert eine serverseitige Session
Nach einer erfolgreichen Anmeldung legt der Server einen Session-Datensatz an (Benutzer-ID, Rollen, Ablaufzeit), im Arbeitsspeicher, in einer Datenbank oder in einem Store wie Redis, und schickt dem Browser eine nicht erratbare ID in einem Cookie. Der Browser hängt das Cookie an jede weitere Anfrage an diese Website an, und der Server lädt bei jeder Anfrage den Datensatz. Die ID selbst bedeutet nichts; 32 zufällige Bytes ergeben codiert 43 Zeichen.
Set-Cookie: __Host-session=p-sLYX7J3ziMKxq-dadVMuQJNSVBHM5hrw7MJPihGQ0; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=86400
Das Präfix __Host- sorgt dafür, dass Browser das Cookie ablehnen, sofern es nicht Secure ist, Path=/ hat und keine Domain setzt. So kann eine Subdomain es nicht überschreiben.
So funktioniert ein JWT
Ein JSON Web Token (RFC 7519) besteht aus drei Base64URL-codierten Teilen, die durch Punkte verbunden sind: einem Header, der den Algorithmus nennt, einem Payload mit Claims wie sub und exp und einer Signatur über die ersten beiden Teile. Der Server prüft Signatur und Ablaufzeit mit einem Schlüssel und vertraut den Claims, ohne eine Datenbank zu fragen. Übertragen wird das Token meist im Header Authorization: Bearer.
Der Payload ist nur codiert, nicht verschlüsselt. Jeder, der das Token besitzt, kann ihn lesen, wie Sie selbst sehen, wenn Sie eines in den JWT Decoder einfügen. Schreiben Sie deshalb nie Geheimnisse oder personenbezogene Daten in ein JWT, die Sie dem Nutzer nicht zeigen würden. Verschlüsselte Tokens gibt es (JWE, RFC 7516), doch die meisten JWTs im Einsatz sind nur signiert. Warum die Teile - und _ statt + und / verwenden, erklärt der Guide Base64 vs. Base64URL.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzg0MTIiLCJpYXQiOjE3NjAwMDAwMDAsImV4cCI6MTc2MDAwMDkwMH0.08BFm85KQIp34VSetcibqwBVLpHKR3IZhI_IPNeTif4
// Reading the payload needs no key at all (browser):
const part = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
const bytes = Uint8Array.from(atob(part), c => c.charCodeAt(0));
JSON.parse(new TextDecoder().decode(bytes));
// { sub: 'user_8412', iat: 1760000000, exp: 1760000900 }
Die Abwägungen im Vergleich
| Serverseitige Session | JWT | |
|---|---|---|
| Der Client hält | Eine zufällige, bedeutungslose ID | Signierte, lesbare Claims |
| Der Server braucht | Einen Session-Store, den alle Server teilen | Nur den Schlüssel zum Prüfen der Signaturen |
| Einen Nutzer überall abmelden | Session löschen: sofort wirksam | Vor exp ohne zusätzlichen Zustand nicht möglich |
| Änderung von Rolle oder Berechtigung | Gilt ab der nächsten Anfrage | Gilt, sobald ein neues Token ausgestellt wird |
| Typischer Transportweg | Cookie, automatisch mitgeschickt | Header Authorization oder ein Cookie |
| Größe pro Anfrage | ID mit 43 Zeichen in unserem Beispiel | 152 bis 540 Zeichen in unseren Beispielen |
Widerruf, Ablauf und Refresh-Tokens
Zustandslosigkeit ist das wichtigste Verkaufsargument von JWTs und zugleich ihre größte Schwäche. Ein gültiges Token bleibt gültig, bis es abläuft, selbst wenn sich der Nutzer abmeldet, sein Passwort ändert oder gesperrt wird. Die übliche Antwort darauf: Access-Tokens kurzlebig halten, oft Minuten statt Stunden, und sie mit einem langlebigeren Refresh-Token kombinieren, das der Client gegen neue Access-Tokens eintauscht. Das Refresh-Token wird gegen serverseitigen Zustand geprüft, sein Widerruf verhindert also, dass neue Access-Tokens ausgestellt werden.
Die OAuth 2.0 Security Best Current Practice (RFC 9700) beschreibt die Rotation von Refresh-Tokens: Jede Erneuerung liefert ein neues Refresh-Token und macht das alte ungültig, sodass sich ein gestohlenes Token erkennen lässt, sobald es erneut verwendet wird. Müssen auch Access-Tokens sofort widerrufbar sein, landen Sie bei einer Sperrliste von Token-IDs (jti) oder einer Token-Version pro Nutzer, und das ist wieder serverseitiger Zustand. Spätestens dann lohnt sich die Frage, ob eine einfache Session nicht simpler wäre.
Wo das Token hingehört: Cookies, localStorage und XSS
Alles in localStorage oder sessionStorage kann jedes Skript lesen, das auf Ihrer Seite läuft. Ein einziger Cross-Site-Scripting-Fehler (XSS) oder ein einziges kompromittiertes Drittanbieter-Skript genügt, um das Token zu kopieren und es von überall aus zu benutzen, bis es abläuft.
Ein Cookie mit dem Attribut HttpOnly kann JavaScript überhaupt nicht lesen, XSS kann es also nicht stehlen. XSS kann aber weiterhin Anfragen aus der geöffneten Seite des Opfers senden, und der Browser hängt das Cookie an. HttpOnly begrenzt den Schaden also, statt ihn zu verhindern. XSS zu verhindern bleibt in jedem Fall unverzichtbar; HttpOnly-Cookies machen einen erfolgreichen Angriff für den Angreifer lediglich weniger wertvoll.
CSRF und SameSite
Die Bequemlichkeit des Cookies ist zugleich sein Risiko: Der Browser hängt es automatisch an, auch an Anfragen, die eine andere Website auslöst. Genau das ist Cross-Site Request Forgery (CSRF). Tokens, die im Header Authorization gesendet werden, sind für klassisches CSRF nicht anfällig, weil der Browser ein Bearer-Token nicht von sich aus anhängt; das muss Ihr eigener Code tun.
SameSite=Strict: Das Cookie wird nie bei Anfragen gesendet, die von einer anderen Website ausgehen, auch nicht, wenn jemand einem Link auf Ihre Website folgt.SameSite=Lax: Gesendet, wenn der Nutzer über einen normalen Link auf Ihre Website navigiert (ein GET auf oberster Ebene), aber nicht bei websiteübergreifenden Formular-POSTs, fetch-Aufrufen oder eingebetteten Ressourcen. Chrome wendet Lax auf Cookies an, die das Attribut nicht setzen; setzen Sie es trotzdem ausdrücklich, statt sich darauf zu verlassen.SameSite=None: Wird überall gesendet und muss mitSecurekombiniert werden (sonst lehnt Chrome das Cookie ab). Nur nötig für echte websiteübergreifende Nutzung wie eingebettete Widgets.
„Same Site“ bedeutet dieselbe registrierbare Domain, app.example.com und forum.example.com gelten also als dieselbe Website. Vor einer kompromittierten Subdomain schützt SameSite Sie nicht. Setzen Sie für zustandsändernde Anfragen zusätzlich ein CSRF-Token ein oder prüfen Sie den Header Origin.
Tokengröße
Eine Session-ID bleibt winzig, ganz gleich, wie viel Sie über den Nutzer wissen. Ein JWT wächst mit jedem Claim und mit dem Signaturalgorithmus. Die Tokens, die wir für diesen Guide erzeugt haben, hatten 152 Zeichen bei HS256 mit drei Claims, 241 Zeichen nach dem Hinzufügen von Name, E-Mail-Adresse und Rolle und 540 Zeichen für dieselben Claims, signiert mit RS256 und einem 2048-Bit-Schlüssel. RFC 6265 verlangt von Browsern nur die Unterstützung von Cookies mit mindestens 4.096 Bytes, einschließlich Name und Attributen. Ein mit Berechtigungen vollgepacktes JWT kann einem Cookie also entwachsen. Und diese Größe wird mit jeder einzelnen Anfrage übertragen.
Fehler, die die Sicherheit von JWTs aushebeln
- Dem Header
algvertrauen. Legen Sie fest, welche Algorithmen Ihr Server akzeptiert. Wernoneakzeptiert oder ein Token von RS256 auf HS256 wechseln lässt, sodass der öffentliche Schlüssel als HMAC-Geheimnis dient, ermöglicht Angreifern gefälschte Tokens. - Dekodieren statt verifizieren. Den Payload zu lesen beweist nichts; prüfen Sie zuerst die Signatur mit einer gepflegten Bibliothek.
- Claim-Prüfungen auslassen. Validieren Sie
exp,nbf,issundaud, damit ein Token, das für einen Dienst ausgestellt wurde, nicht von einem anderen akzeptiert wird.
RFC 8725, die JWT Best Current Practices, behandelt diese und weitere Punkte.
Wann was passt
| Situation | Meist die bessere Wahl |
|---|---|
| Serverseitig gerenderte Web-App auf einer Domain | Session-Cookie |
| Single-Page-App, die von derselben Website ausgeliefert wird wie ihre API | Session-Cookie oder ein Backend-for-Frontend, das die Tokens auf dem Server hält |
| Native Mobile-App | Kurzlebiges Access-Token plus Refresh-Token im sicheren Speicher der Plattform |
| Microservices, die Anfragen ohne Aufruf eines Auth-Dienstes prüfen | JWT-Access-Tokens, signiert mit einem asymmetrischen Schlüssel wie RS256 oder ES256 |
| Zugriff Dritter auf Ihre API | OAuth-2.0-Access-Tokens, die oft JWTs sind |
Die Mischform: ein JWT in einem HttpOnly-Cookie
Viele Apps legen das JWT selbst in ein Cookie mit HttpOnly; Secure; SameSite. Skripte können es nicht lesen, und Server können es trotzdem ohne Session-Abfrage prüfen. Die Abwägungen kommen allerdings mit: Das Cookie wird automatisch gesendet, CSRF-Schutz ist also wieder nötig; das Cookie-Budget von 4 KB gilt; und der Widerruf ist genauso schwierig wie bei jedem anderen JWT. Für eine Browser-App mit zustandslosen APIs dahinter ist das ein solides Design, sofern Sie es beim CSRF-Schutz wie ein Cookie und beim Widerruf wie ein JWT behandeln.