Storage and browser security
Choose the right browser storage
Choose cookies, localStorage, sessionStorage, or IndexedDB according to transport, lifetime, size, and access patterns.
We now have four ways to store data in the browser. Pick the one that fits the job, not the one with the shortest API.
Four questions decide it. Does the server need the value on every request? How long should it live? How big is it? And does reading it need to be synchronous?
| Storage | Useful when | Main cost or constraint |
|---|---|---|
| Cookies | The server must receive a small value with matching HTTP requests | Sent repeatedly; strict size limits; needs security attributes |
localStorage | A small string preference should survive browser restarts | Synchronous access can block the main thread |
sessionStorage | A small string value belongs to one tab session | Disappears with that session and is not shared like persistent storage |
| IndexedDB | The page needs asynchronous access to larger structured data | More code and a transactional API |
Some concrete examples. A color theme fits in localStorage. A half-finished form in a checkout flow fits in sessionStorage, because a second tab should not inherit it. An offline product catalog belongs in IndexedDB. A server-managed session uses a cookie with Secure, HttpOnly, and an explicit SameSite, not a token that every script on the page can read.
Storage is not a database
All four are scoped and constrained by the browser. Quotas, eviction, private browsing, and user settings mean “persistent” is a hope, not a guarantee.
So keep the source of truth on the server. Treat anything in the browser as a cache you can rebuild, and handle a failed write instead of assuming it worked.
localStorage accepts only strings, so objects go through JSON. Notice the fallback for a missing key:
localStorage.setItem('preferences', JSON.stringify({ theme: 'dark' }))
const preferences = JSON.parse(
localStorage.getItem('preferences') ?? '{}'
)
Without the ?? '{}', a first visit would call JSON.parse(null). That returns null, and the next line that reads preferences.theme throws.
Don’t store secrets
An API that persists data is not an API that protects it. Any script running on the origin can read JavaScript-accessible storage, including a script injected through an XSS bug. Session IDs and tokens belong in an HttpOnly cookie.
Audit a real page
Open the Application panel on a site you work on and go through every cookie, localStorage key, and IndexedDB database. For each one, write down who owns it, how long it should live, how big it can plausibly get, and what clears it.
If you can’t answer one of those four, the storage decision isn’t finished. I’ve found forgotten keys this way on every project I’ve audited.
Lesson completed