How do you store auth tokens securely?
By FrontendPro Editorial Team Updated 9/26/2026
Answer
The short answer: in an HttpOnly cookie set by the server, not in localStorage.
Why not localStorage
localStorage and sessionStorage are readable by any JavaScript running on the page. One cross-site scripting hole — in your code, or in any dependency, or in a third-party analytics snippet — and the attacker reads the token and has the session. You will see advice recommending localStorage for tokens; it is common and it is wrong.
An HttpOnly cookie cannot be read by JavaScript at all, so XSS cannot steal it:
Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=1800
HttpOnly— invisible todocument.cookie.Secure— only sent over HTTPS.SameSite=Lax— not attached to cross-site POSTs, which blocks the basic CSRF attack.
Cookies are sent automatically, which is the trade: you gain XSS resistance and take on CSRF, so pair them with anti-CSRF tokens for state-changing requests.
Keep tokens short-lived
Use a short-lived access token (minutes) plus a refresh token stored the same way, rotated on each use. Then a leaked token is useful only briefly, and you have a revocation point — a JWT that cannot be revoked and lasts a week is the worst of both worlds.
Authorisation is a server concern
Hiding a button is user experience, not security. Every endpoint must check permissions itself, because the client can call it directly:
if (!user || !user.roles.includes("admin")) {
return res.status(403).json({ error: "Forbidden" });
}
Never trust a role sent from the client, and never decide access from an unverified JWT payload — verify the signature server-side first.
When you have no choice
In a native or cross-origin SPA where cookies are impractical, keep the access token in memory only, never in storage that survives a reload, and accept that XSS remains your critical risk.
