Cookies vs. localStorage für JWT-Authentifizierung
Sie haben Ihren Authentifizierungsflow implementiert, JWTs funktionieren – und jetzt stehen Sie vor der Frage, mit der jeder Frontend-Entwickler früher oder später konfrontiert wird: Wo soll ich dieses Token eigentlich speichern? Die Antwort ist wichtiger, als die meisten Tutorials vermuten lassen, und der gängige Rat – „Verwenden Sie einfach HttpOnly-Cookies” – übergeht echte Abwägungen, die Sie kennen sollten.
Im Folgenden finden Sie eine klare Gegenüberstellung beider Optionen, was jede davon tatsächlich schützt und wie moderne Anwendungen damit in der Praxis umgehen.
Wichtigste Erkenntnisse
localStorageist für jedes JavaScript, das auf Ihrer Seite ausgeführt wird, vollständig zugänglich und damit anfällig für XSS-basierten Token-Diebstahl.HttpOnly-Cookies blockieren den JavaScript-Zugriff vollständig, führen jedoch ein CSRF-Risiko ein, das durch die AttributeSameSiteundSecuregemindert wird.- Das moderne Muster speichert kurzlebige Access-Tokens im Arbeitsspeicher und Refresh-Tokens in
HttpOnly-,Secure- undSameSite-Cookies. - OWASP und die OAuth-Richtlinien für browserbasierte Apps raten davon ab, langlebige Tokens in
localStoragezu speichern. - Die richtige Wahl hängt von Ihrem Bedrohungsmodell, Ihrer Backend-Kontrolle und davon ab, ob Ihre API
Authorization-Header erfordert.
Was bei der JWT-Speicherung wirklich auf dem Spiel steht
Der Speicherort eines JWTs bestimmt, welche Angriffsvektoren für Ihre Anwendung relevant sind. Die zwei primären Bedrohungen sind:
- XSS (Cross-Site Scripting): Schadhafter JavaScript-Code, der im Kontext Ihrer Anwendung ausgeführt wird.
- CSRF (Cross-Site Request Forgery): Der Browser eines Benutzers wird dazu verleitet, unbeabsichtigte authentifizierte Anfragen zu stellen.
Keine der beiden Speicheroptionen eliminiert beide Risiken gleichzeitig. Das Ziel ist zu verstehen, welches Risiko Sie akzeptieren und wie Sie es mindern können.
localStorage: Praktisch, aber per JavaScript zugänglich
Ein JWT in localStorage zu speichern ist unkompliziert. Sie schreiben es, lesen es aus und hängen es manuell an Authorization: Bearer-Header an. Das funktioniert problemlos mit APIs, die dieses Header-Format erwarten.
Das Problem besteht darin, dass localStorage für jedes JavaScript, das auf Ihrer Seite ausgeführt wird, vollständig zugänglich ist. Wenn ein Angreifer erfolgreich ein Script einschleust – über eine Schwachstelle in einer Abhängigkeit, ein kompromittiertes CDN oder einen XSS-Fehler in Ihrem eigenen Code – kann er den Token direkt auslesen und exfiltrieren. OWASP rät aus diesem Grund ausdrücklich davon ab, Session-Identifier in localStorage zu speichern.
Das ist keine rein theoretische Gefahr. Moderne Webanwendungen binden Dutzende von Drittanbieter-Scripts ein, und jedes davon stellt eine potenzielle Angriffsfläche dar.
HttpOnly-Cookies: Bessere XSS-Resistenz, neue Überlegungen
Ein HttpOnly-Cookie kann von JavaScript überhaupt nicht ausgelesen werden. Selbst wenn ein Angreifer Code auf Ihrer Seite ausführt, kann er den Token-Wert nicht extrahieren. Das ist eine bedeutsame Verbesserung.
Allerdings bringen Cookies eine CSRF-Angriffsfläche mit sich. Browser hängen Cookies automatisch an passende Anfragen an – einschließlich solcher, die von schadhaften Drittanbieter-Seiten ausgelöst werden.
Drei Cookie-Attribute wirken zusammen, um diese Lücke zu schließen:
HttpOnly– blockiert den JavaScript-Zugriff vollständig.Secure– überträgt den Cookie ausschließlich über HTTPS.SameSite– steuert, wann Cookies bei Cross-Site-Anfragen gesendet werden.
Bei SameSite setzen moderne Browser standardmäßig Lax, wenn das Attribut nicht gesetzt ist. Dieser Wert blockiert Cookies bei Cross-Site-Subanfragen (z. B. POST-Anfragen von einem anderen Ursprung), erlaubt sie jedoch bei Top-Level-Navigationen. Strict ist restriktiver und verhindert, dass der Cookie bei jeglichen Cross-Site-Anfragen gesendet wird – einschließlich Top-Level-Navigationen. Setzen Sie dieses Attribut immer explizit, anstatt sich auf Browser-Standardwerte zu verlassen. Die Browser-Unterstützung für SameSite ist bei modernen Browsern ausgezeichnet und kann auf Can I Use überprüft werden.
Mit korrekt konfiguriertem SameSite=Strict oder Lax ist das CSRF-Risiko für die meisten Same-Site-Authentifizierungsszenarien erheblich reduziert. Für sicherheitskritische, zustandsverändernde Endpunkte empfiehlt sich zusätzlich ein Anti-CSRF-Token für Defense-in-Depth.
Discover how at OpenReplay.com.
Das Muster, das die meisten modernen Anwendungen verwenden
Viele Produktionsanwendungen teilen das Problem auf:
- Kurzlebige Access-Tokens, gespeichert im JavaScript-Arbeitsspeicher (eine Variable auf Modulebene oder React-State).
- Refresh-Tokens, gespeichert in einem
HttpOnly-,Secure- undSameSite-Cookie.
Der Access-Token verschwindet beim Schließen des Tabs oder beim Neuladen der Seite, aber ein stiller Aufruf Ihres /refresh-Endpunkts ruft mithilfe des Cookies einen neuen ab. Der Access-Token gelangt nie in einen persistenten Speicher, und der Refresh-Token ist für JavaScript nie auslesbar.
Dieser Ansatz entspricht der aktuellen Empfehlung für browserbasierte Apps, die OAuth 2.0 mit PKCE (Authorization Code Flow mit PKCE) verwenden – so wie es die Richtlinien für OAuth 2.0 für browserbasierte Apps empfehlen. Wenn Sie mit OpenID Connect (OIDC) arbeiten, gilt dasselbe Muster: Halten Sie ID-Tokens und Refresh-Tokens aus localStorage heraus.
Sicherheits-Audit-Checkliste
Überprüfen Sie vor dem Deployment:
HttpOnly-Flag ist für jeden Cookie gesetzt, der Tokens enthält.Secure-Flag ist aktiviert (HTTPS wird erzwungen).SameSiteist explizit aufStrictoderLaxgesetzt.- Access-Tokens sind kurzlebig – typischerweise in Minuten statt Stunden gemessen.
- Content-Security-Policy-Header sind konfiguriert.
- Keine langlebigen JWTs in
localStorage.
Den richtigen Ansatz für Ihre Anwendung wählen
Es gibt keine universelle Antwort. Wenn Sie Ihr Backend kontrollieren und Ihre Anwendung von derselben Domain ausliefern, sind HttpOnly-Cookies mit korrekter SameSite-Konfiguration der robustere Standard. Wenn Sie eine Drittanbieter-API integrieren, die Authorization-Header erfordert und Sie serverseitig keine Cookies setzen können, ist die In-Memory-Speicherung mit kurzer Ablaufzeit ein vertretbarer Fallback – speichern Sie jedoch niemals langlebige Tokens in localStorage.
Fazit
Langlebige JWTs in localStorage sind das, wovon aktuelle Sicherheitsrichtlinien konsequent abraten. HttpOnly-Cookies mit den Attributen Secure und SameSite bieten den stärksten Standard für die meisten Same-Domain-Szenarien, während die In-Memory-Speicherung in Kombination mit einem Refresh-Token-Cookie die komplexeren Fälle abdeckt. Sobald Sie das Bedrohungsmodell verstehen – XSS auf der einen Seite, CSRF auf der anderen – wird die richtige Wahl für Ihre Anwendung zu einer nachvollziehbaren Abwägung statt einem Ratespiel.
Häufig gestellte Fragen
sessionStorage weist dieselbe Schwachstelle wie localStorage auf: Jedes JavaScript, das auf der Seite ausgeführt wird, kann darauf zugreifen. Der einzige Unterschied besteht darin, dass sessionStorage beim Schließen des Tabs geleert wird. Das reduziert das Expositionsfenster, schützt jedoch nicht vor XSS. Behandeln Sie sessionStorage bei der Token-Speicherung mit derselben Vorsicht wie localStorage, und vermeiden Sie es, langlebige Tokens dort abzulegen.
SameSite=Strict verhindert, dass Cookies bei Cross-Site-Anfragen gesendet werden, und blockiert damit die meisten CSRF-Angriffsmuster. Für hochwertige, zustandsverändernde Endpunkte bietet ein zusätzliches Anti-CSRF-Token jedoch Defense-in-Depth. Da SameSite vom Browser durchgesetzt wird, könnten ältere Clients oder ungewöhnliche Randfälle es möglicherweise nicht berücksichtigen. Ein Double-Submit-Token-Muster bleibt daher eine sinnvolle Sicherheitsmaßnahme.
Ein gängiger Bereich liegt zwischen 5 und 15 Minuten. Kurz genug, damit ein gestohlener Token nur begrenzten Nutzen hat, aber lang genug, um Ihren Refresh-Endpunkt nicht zu überlasten. Kombinieren Sie dies mit einem länger gültigen Refresh-Token (Stunden bis Tage) in einem HttpOnly-Cookie. Wenn Ihre Anwendung sensible Vorgänge wie Zahlungen verarbeitet, tendieren Sie zu kürzeren Ablaufzeiten und fordern Sie für kritische Aktionen eine erneute Authentifizierung.
Speichern Sie den Access-Token im JavaScript-Arbeitsspeicher – als Modulvariable, React-State oder Closure – statt in localStorage. Halten Sie ihn kurzlebig und aktualisieren Sie ihn wenn möglich über einen Backend-Endpunkt. Falls Sie etwas über Seitenneuladen hinaus persistieren müssen, leiten Sie den Refresh-Flow über Ihr eigenes Backend, das die langlebigen Zugangsdaten serverseitig hält, und legen Sie niemals langlebige Tokens im Client-Speicher ab.
Gain control over your UX
See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.