token storage

there is no safe place to keep a token in the browser. the question is not where you put it, it is how much an attacker gets when they reach it.

every javascript-readable store is the same store

localStorage, sessionStorage and a plain variable differ in lifetime, not in exposure. any script running on your origin can read all three, and that includes the analytics tag, the chat widget and the dependency that got compromised last night.

localStorage.getItem('token');   // any script on the page
sessionStorage.getItem('token'); // same, but dies with the tab
tokenInMemory;                   // same, but dies on refresh

so choosing between them is choosing how long the window stays open, not whether it exists. localStorage is the worst of the three only because the window never closes.

httpOnly cookies move the risk, they do not remove it

a cookie marked HttpOnly is invisible to document.cookie, so a script cannot read it and cannot ship it to another host. that is a real win: the token stops being stealable.

but the browser still attaches it to every request to your api. an attacker with script execution does not need to read the token, they just call your endpoints from your page and the cookie rides along. HttpOnly stops exfiltration, not abuse.

Set-Cookie: refresh=...; HttpOnly; Secure; SameSite=Lax; Path=/auth

Secure keeps it off plain http. SameSite is what blocks another site from triggering those authenticated requests. Path narrows where it is sent at all.

the shape that holds up

a short-lived access token in memory, and a long-lived refresh token in an HttpOnly cookie.

the access token dies on refresh and expires in minutes, so stealing it buys very little. the refresh token survives, but a script cannot read it. neither half is safe on its own, and together they turn a full account takeover into a few minutes of access.

if your api is on another origin, this needs credentials: 'include' and a cors policy that names your site explicitly. a wildcard origin will not send cookies, which is the browser saving you from yourself.

the part people skip

none of the above matters if you have xss. a script on your page can call your api as the user no matter where the token lives, and storage strategy only changes how long that lasts.

the work that actually moves the needle is upstream: escaping output, a content security policy that blocks inline scripts, and knowing what your third party tags are allowed to do. token storage is damage control, not defense.

what not to do