The differences side by side
| Aspect | localStorage | HttpOnly cookie |
|---|---|---|
| Readable by JavaScript | Yes, by any script running on the origin, including injected and third-party scripts. | No. MDN says the HttpOnly attribute forbids JavaScript from accessing the cookie, for example through document.cookie. |
| Effect of an XSS bug | The attacker can copy the token and use it from their own machine until it expires. | The attacker cannot read the token. They can still make requests from the victim's browser while the page is open. |
| Sent automatically | No. Your code attaches it, usually in an Authorization header. | Yes, the browser attaches it to matching requests. This is what makes CSRF possible. |
| CSRF | Not exposed in the usual header-based design. | Exposed. Use SameSite and CSRF tokens. OWASP treats SameSite as defence in depth, not a full replacement. |
| Who sets it | Client-side code. | The server, through a Set-Cookie response header. |
| Lifetime | MDN says localStorage data has no expiration time and persists across browser sessions. | Controlled with Max-Age or Expires, and cleared by the server on logout. |
| Cross-origin APIs | Simple, because you attach the header yourself. | Needs CORS with credentials and the right SameSite setting, or a same-origin proxy route. |
What OWASP says
The OWASP HTML5 Security Cheat Sheet says not to store session identifiers in local storage because the data is always accessible by JavaScript, and that cookies can mitigate this risk using the httpOnly flag. It adds that a single cross-site scripting flaw can be used to steal all the data in these objects.
The OWASP Session Management Cheat Sheet gives the same advice for authentication tokens, JWTs and refresh tokens, and points to HttpOnly cookies or a backend-for-frontend pattern.
What localStorage leaves to you
- Preventing every XSS bug, in your code and in every third-party script you load, because any one of them can read the token.
- Short token lifetimes, so a stolen token is useful for less time.
- A strict Content-Security-Policy as a second layer.
- Clearing the token on logout, and revoking it on the server, since removing it from the browser does not invalidate a copy.
A reasonable default
Set the cookie from your server after login. The __Host- prefix requires Secure, Path=/ and no Domain attribute, which ties the cookie to the exact host.
Set-Cookie: __Host-session=VALUE; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=3600What to check whichever you pick
Open your own app, sign in, and run the first two lines in the browser console. Then check the Cookies panel in developer tools.
- No long-lived token or refresh token sits in localStorage or sessionStorage.
- Session cookies show HttpOnly, Secure and a SameSite value in developer tools.
- State-changing requests are rejected when sent from another origin without a CSRF token.
- After logout, the old token or cookie value no longer works against the API.
- A Content-Security-Policy is in place or in report-only mode.
- User-supplied HTML is sanitised before it is rendered.
Object.keys(localStorage).filter((k) => /token|auth|session|jwt/i.test(k));
document.cookie;
// A session cookie that appears in document.cookie is not HttpOnly.Frequently asked questions
Is it safe to store a JWT in localStorage?
OWASP advises against it, because any script running on the page can read localStorage, so a single XSS flaw lets an attacker copy the token. If you must, keep lifetimes short and treat XSS prevention as critical.
Are HttpOnly cookies safe from XSS?
They stop injected JavaScript from reading the cookie. They do not stop XSS itself: a script can still send requests as the user while the page is open. HttpOnly reduces the damage and does not remove the need to fix XSS.
Do HttpOnly cookies prevent CSRF?
No. HttpOnly is about JavaScript access. CSRF is about the browser attaching cookies to requests from other sites. Use SameSite and CSRF tokens for that.
Is sessionStorage safer than localStorage for tokens?
It is cleared when the tab closes, which shortens exposure. It is still readable by any script on the page, and OWASP's session guidance lists sessionStorage alongside localStorage as a place not to keep tokens.