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.

Direkt ausprobieren: JWT-Decoder

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 SessionJWT
Der Client hältEine zufällige, bedeutungslose IDSignierte, lesbare Claims
Der Server brauchtEinen Session-Store, den alle Server teilenNur den Schlüssel zum Prüfen der Signaturen
Einen Nutzer überall abmeldenSession löschen: sofort wirksamVor exp ohne zusätzlichen Zustand nicht möglich
Änderung von Rolle oder BerechtigungGilt ab der nächsten AnfrageGilt, sobald ein neues Token ausgestellt wird
Typischer TransportwegCookie, automatisch mitgeschicktHeader Authorization oder ein Cookie
Größe pro AnfrageID mit 43 Zeichen in unserem Beispiel152 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 mit Secure kombiniert 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 alg vertrauen. Legen Sie fest, welche Algorithmen Ihr Server akzeptiert. Wer none akzeptiert 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, iss und aud, 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

SituationMeist die bessere Wahl
Serverseitig gerenderte Web-App auf einer DomainSession-Cookie
Single-Page-App, die von derselben Website ausgeliefert wird wie ihre APISession-Cookie oder ein Backend-for-Frontend, das die Tokens auf dem Server hält
Native Mobile-AppKurzlebiges Access-Token plus Refresh-Token im sicheren Speicher der Plattform
Microservices, die Anfragen ohne Aufruf eines Auth-Dienstes prüfenJWT-Access-Tokens, signiert mit einem asymmetrischen Schlüssel wie RS256 oder ES256
Zugriff Dritter auf Ihre APIOAuth-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.

Häufige Fragen

Ist JWT sicherer als Session-Cookies?

Nein, keines von beiden ist von Natur aus sicherer. Sessions lassen sich leichter widerrufen und verraten dem Client nichts. JWTs sparen das Nachschlagen der Session, lassen sich aber vor ihrem Ablauf nur schwer widerrufen. Wo die Zugangsdaten gespeichert werden und wie mit CSRF und XSS umgegangen wird, zählt mehr als das Format.

Sollte ich ein JWT in localStorage speichern?

Das ist einfach, macht das Token aber für jedes Skript auf der Seite zugänglich, sodass ein einziger XSS-Fehler es preisgeben kann. Ein HttpOnly- und Secure-Cookie mit ausdrücklich gesetztem SameSite-Attribut hält es von JavaScript fern, erfordert dafür aber CSRF-Schutz.

Kann man ein JWT ungültig machen?

Nicht von sich aus. Ein signiertes Token bleibt bis zu seiner exp-Zeit gültig. Um den Zugriff früher zu beenden, halten Sie Access-Tokens kurzlebig, widerrufen das Refresh-Token oder prüfen Token-IDs gegen eine Sperrliste, was serverseitigen Zustand hinzufügt.

Sind die Daten in einem JWT verschlüsselt?

Normalerweise nicht. Header und Payload sind Base64URL-codiertes JSON, das jeder decodieren kann; nur die Signatur verhindert Änderungen. Verwenden Sie JWE, wenn der Inhalt vertraulich sein muss, oder halten Sie sensible Daten aus dem Token heraus.
Direkt ausprobieren: JWT-Decoder