The Three Storage Mechanisms
**Cookies** — key-value pairs sent with every HTTP request to the matching domain. Set by the server via Set-Cookie header or by JavaScript via document.cookie.
**localStorage** — key-value pairs stored permanently in the browser until explicitly deleted. Accessible only via JavaScript. Never sent to the server automatically.
**sessionStorage** — identical to localStorage, but scoped to the browser tab and cleared when the tab closes.
Persistence
| Cleared when tab closes | No (unless session cookie) | No | Yes |
|---|---|---|---|
| Cleared when browser closes | No (unless session cookie) | No | Yes |
| Cleared when user clears storage | Yes | Yes | Yes |
| Has expiration | Yes (`Expires`/`Max-Age`) | No | No |
| Max size | ~4 KB | ~5–10 MB | ~5–10 MB |
Security Properties
**Cookies can be httpOnly** — this flag prevents JavaScript from accessing the cookie. A httpOnly cookie can be set and read only by the server. This is critical for session tokens: if your auth cookie is httpOnly, a successful XSS attack cannot steal it, because JavaScript can't read it.
**Cookies can be Secure** — only transmitted over HTTPS connections.
**Cookies can be SameSite** — controls whether the cookie is sent with cross-site requests. SameSite=Strict only sends the cookie for same-origin requests (prevents CSRF). SameSite=Lax allows top-level navigation GET requests.
**localStorage and sessionStorage have no equivalent protections** — they're fully accessible to any JavaScript running on the page. An XSS vulnerability on any part of your site can read everything in localStorage.
The Auth Token Problem
Storing session tokens or JWTs in localStorage is a common pattern (you've probably seen tutorials that do this). It's convenient because localStorage is easy to access in JavaScript, and you can inspect it in DevTools.
The problem: any XSS attack — even from a third-party ad script or a compromised npm dependency — can read everything in localStorage. The attack doesn't need to find an XSS hole in your code specifically. Any script running on your page can access it.
**The secure approach:** Store session tokens in httpOnly; Secure; SameSite=Lax cookies. They're never accessible to JavaScript. The trade-off: you need to handle CSRF protection (usually a secondary token in a non-httpOnly cookie).
What Each Storage Is Good For
- Session authentication tokens (always use
httpOnly; Secure) - Cross-request state that the server needs to read
- Tracking and analytics (requires user consent in GDPR jurisdictions)
- User preferences (theme, language, UI state)
- Draft content (unsaved form data)
- Application-level state that doesn't involve authentication
- Caching non-sensitive data
- Tab-specific state (multi-step forms, wizard progress)
- Data you explicitly want to clear when the user closes the tab
- Temporary data for a single workflow
Privacy Implications
All three storage mechanisms are covered by GDPR and similar regulations when used for tracking. Functional cookies (session management, preferences) generally don't require consent. Analytics and advertising cookies require explicit opt-in in the EU.
localStorage used for tracking is subject to the same consent requirements as cookies. "We don't use cookies" doesn't mean "we don't track you" if localStorage is being used instead.
The rule: any persistent identifier stored in the browser that's used to track a user across time or across pages requires disclosure and, in many jurisdictions, consent.