Where to store JWTs in the browser
localStorage vs httpOnly cookie, XSS risk, CSRF risk, token storage tradeoffs, Authorization header, Bearer token pattern
The Storage Dilemma
Where you store a JWT in the browser has major security implications. There is no perfect option โ only tradeoffs.
localStorage
Pros: easy to access, no CSRF risk, works with the Authorization header pattern. Cons: any JavaScript running on the page can read it. If your site has an XSS vulnerability, an attacker can steal all JWTs stored in localStorage. This is a significant risk for apps that load third-party scripts.
HttpOnly Cookie
Pros: JavaScript cannot read it at all, so XSS attacks cannot steal the token directly. Cons: the browser automatically sends cookies with requests, which means CSRF attacks become possible again. Mitigate with SameSite=Strict or a CSRF token.
The Practical Recommendation
For most apps: store the JWT in an httpOnly cookie. This is the more defense-in-depth approach. XSS is extremely common; CSRF is more controllable with SameSite. When using the Authorization header (common in mobile apps and pure APIs), localStorage is acceptable if you have a strict Content Security Policy to reduce XSS exposure.
// Sending token in Authorization header
fetch('/api/profile', {
headers: {
'Authorization': `Bearer ${token}`
}
});
