Safety
localStorage vs HttpOnly Cookies for Session Tokens
August 28, 2024
localStorage vs HttpOnly cookies for session tokens is not a framework fashion contest. localStorage (and document.cookie without HttpOnly) is readable to any script that runs on the page — including XSS and a sloppy third-party tag. HttpOnly cookies keep the browser from handing the session to JavaScript. SPAs can still use cookie-based sessions with SameSite, CSRF defenses, and a boring API. Access tokens in localStorage are convenient and a classic OPSEC miss. RoastMyOpsec grades Set-Cookie flags on the landing response and scans public JS for secret-like strings. It does not log into your app.
The practical difference
localStorage is a JavaScript disk. XSS becomes session theft with one document.read. An HttpOnly cookie is a credential the page script cannot copy, though XSS can still drive the victim's browser (CSRF-shaped actions) — which is why you still want CSP and CSRF controls.
sessionStorage is the same class of problem, just shorter-lived. IndexedDB is still JS-visible.
How to choose based on the app
Cookie-session apps: Secure, HttpOnly, SameSite=Lax or Strict, plus CSRF tokens or Origin checks. Token apps that 'must' use Authorization headers can keep short-lived access tokens in memory and refresh via an HttpOnly cookie — not a JWT in localStorage 'because Redux wanted it.'
| Store | JS can read? | Sent automatically? | Takeaway |
|---|---|---|---|
| HttpOnly cookie | No | Yes, by the browser | Default for session IDs |
| localStorage JWT | Yes | No — you attach it | XSS reads the vault |
| Memory only | Yes, until reload | No | Better than disk; still XSS-visible |
| document.cookie without HttpOnly | Yes | Yes | Worst of both |
When localStorage still shows up
Tutorials put JWTs in localStorage because it is easy to demo. Production inherits the demo. Pair this with the public-JS-secrets guide: a token in localStorage is not 'hidden' any more than an API key in main.js.
If you already shipped it, rotate, move the session to HttpOnly, and keep CSP tight so the next XSS is less of a full account dump.
Common mistakes
The first mistake is 'we have CSP so localStorage is fine.'
The second mistake is HttpOnly on the cookie and a duplicate JWT in localStorage for the SPA.
The third mistake is localStorage for the access token and no refresh rotation.
Free audit the URL you own
RoastMyOpsec is a defensive public-surface roast: headers, cookies, sensitive paths, and more — no exploit payloads. Start with the free audit, then open the vault if the blurred findings look expensive.
Free audit nowFAQ
- Is putting a JWT in localStorage a vulnerability?
- It is a high-cost design. Any XSS can steal it. Prefer HttpOnly cookies or in-memory tokens with a cookie refresh.
- Do HttpOnly cookies break my SPA?
- No. The SPA calls same-origin APIs; the browser attaches the cookie. You add CSRF controls instead of reading the token in JS.
- Will a URL scan see localStorage?
- No. localStorage is in the browser. RoastMyOpsec sees Set-Cookie on the response and secret-like strings in public JS — not your users' disks.