Cookies vs localStorage vs sessionStorage

Three browser storage mechanisms. Different lifetimes, different security models, different use cases.

1 credit

Comparison

6 items
Max size
Cookies: ~4KB each / localStorage: ~5-10MB / sessionStorage: ~5-10MB
Sent with requests
Cookies: yes (automatic) / Storage: no (manually attach)
Lifetime
Cookies: until expiry / local: until cleared / session: until tab closes
Accessible to JS
Cookies: only if not HttpOnly / Storage: always
Cross-tab
Cookies: shared per origin / local: shared / session: tab-isolated
XSS read risk
Cookies (HttpOnly): no / Storage: yes — any injected script can read

Use each for

  • **HttpOnly, Secure cookies** — session/auth tokens. Server sets them, JS can't read them, immune to XSS theft.
  • **localStorage** — user preferences, cached non-sensitive data, feature flags. Never tokens.
  • **sessionStorage** — per-tab scratch data (multi-step form draft, pagination cursor).
  • **IndexedDB** — if you need structured storage or >10MB. Async API, SQL-like queries.

Cookie attributes

6 items
HttpOnly
JS can't access via document.cookie — blocks XSS token theft. ALWAYS set for auth cookies.
Secure
Only sent over HTTPS. ALWAYS set in production.
SameSite
`Lax` (default, safe), `Strict` (no cross-site at all), `None` (needs Secure + usually CSRF token)
Domain
`.example.com` = send to all subdomains. Explicit `app.example.com` = that exact host.
Path
Limit which URLs include the cookie. Usually `/`.
Max-Age / Expires
How long before the cookie dies. Omit = session cookie (dies when browser closes).

Anti-patterns

  • Storing JWTs in localStorage — any XSS = account takeover. Use HttpOnly cookies.
  • Storing PII (emails, phone) in localStorage — persists forever, even on shared machines.
  • Relying on sessionStorage for auth — tab refresh = logged out. Use cookies.
  • Setting cookies for every request — SameSite=Strict + fetch with `credentials: "omit"` = cookie absent anyway.

Further reading