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

```js
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

- **do not put a token in a url.** it lands in server logs, in the referrer
  header and in the browser history.
- **do not decode a jwt on the client and trust it.** the signature is checked
  by the server. client side decoding is for showing a name, never for deciding
  what someone can do.
- **do not use a long-lived access token because refresh is annoying.** the
  expiry is the entire mitigation. removing it removes the plan.
