Comparisons

Pairs that get conflated in real conversations, and in real pull requests. Neither column wins — what decides is the requirement. Each record leads with the confusion, because the confusion is the reason the record exists.

Cookies vs Script-readable token storage

What people get wrong about this pair

The argument is usually reduced to "localStorage is vulnerable to XSS" versus "cookies are vulnerable to CSRF", as if you were picking which vulnerability to have. What is actually being traded is *who attaches the credential*. A cookie is attached by the browser, automatically, on requests to its domain — which is convenient, and is precisely why cross-site requests carry it and CSRF exists. A script-readable token is attached by your code, so nothing is sent unless you send it, and CSRF largely disappears — but the token now lives somewhere any script running on your origin can read, which means an XSS becomes credential theft rather than session abuse. It is worth being exact about the XSS case: if an attacker is executing script on your origin, a HttpOnly cookie is not safe either, because they can make authenticated requests as the user from inside the page. What HttpOnly buys is that the credential cannot be *exfiltrated* and reused elsewhere, later, without the page. That is a real difference, and it is smaller than it is usually claimed to be.

A cookie the script cannot read (HttpOnly, Secure, SameSite)
Use it when

First-party web apps talking to their own origin or a sibling one, where you want the credential outside JavaScript's reach and you are willing to handle CSRF.

A token in memory, web storage or IndexedDB, attached by your code
Use it when

Clients that must attach a credential explicitly — cross-origin APIs, multiple backends, or flows where automatic attachment is exactly what you do not want.

DimensionA cookie the script cannot read (HttpOnly, Secure, SameSite)A token in memory, web storage or IndexedDB, attached by your code
Attached byThe browser, automaticallyYour code, explicitly
Readable by scriptNo, when HttpOnlyYes, by any script on the origin
CSRF exposureYes — mitigated by SameSite plus a tokenLargely none, because nothing is sent automatically
XSS exposureAttacker can act as the user in-page, but cannot take the valueAttacker can take the value and use it anywhere
Cross-origin useNeeds credentialed CORS and an exact originStraightforward — it is just a header
Survives a reloadYes, until expiryOnly if persisted, which is the risky part
RevocationServer clears or rotates the sessionNeeds short expiry plus refresh, or a denylist
Sent on every request to the domainYes, including ones that did not need itOnly where you attach it