VibeSecurity

Comparison

localStorage vs HttpOnly cookies for auth tokens

After a user signs in, the browser has to keep a token somewhere. Generated apps often put it in localStorage because that is the shortest code. This page explains what that choice exposes, using OWASP and MDN as the reference.

By the VibeSecurity team4 min read

The differences side by side

AspectlocalStorageHttpOnly cookie
Readable by JavaScriptYes, 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 bugThe 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 automaticallyNo. Your code attaches it, usually in an Authorization header.Yes, the browser attaches it to matching requests. This is what makes CSRF possible.
CSRFNot 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 itClient-side code.The server, through a Set-Cookie response header.
LifetimeMDN 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 APIsSimple, 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.

What HttpOnly cookies leave to you

  • CSRF defences for every state-changing request.
  • Setting Secure and SameSite as well. HttpOnly alone does not stop the cookie travelling over plain HTTP or with cross-site requests.
  • Still fixing XSS. An injected script cannot read the cookie, and it can still act as the user while the page is open.
  • A server-side component to set and clear the cookie, which a pure client-side app may not have yet.

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 header
Set-Cookie: __Host-session=VALUE; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=3600

What 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.
Browser console on your own app
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.

Sources

  1. 1.OWASP HTML5 Security Cheat Sheet
  2. 2.OWASP Session Management Cheat Sheet
  3. 3.OWASP Cross-Site Request Forgery Prevention Cheat Sheet
  4. 4.OWASP Cross Site Scripting Prevention Cheat Sheet
  5. 5.MDN: Set-Cookie header
  6. 6.MDN: Using HTTP cookies
  7. 7.MDN: Window.localStorage