RoastMyOpsec

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.'

StoreJS can read?Sent automatically?Takeaway
HttpOnly cookieNoYes, by the browserDefault for session IDs
localStorage JWTYesNo — you attach itXSS reads the vault
Memory onlyYes, until reloadNoBetter than disk; still XSS-visible
document.cookie without HttpOnlyYesYesWorst 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 now

FAQ

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.

Sources

Related guides