My SPA stores the JWT in localStorage and attaches it as a Bearer token. A colleague says that is insecure. What is the safer approach and what are the trade-offs?
#javascript#security#jwt#cookies
DEV3 Developer Forum
Every post is reviewed first. Usually reviewed within 24 hours.
My SPA stores the JWT in localStorage and attaches it as a Bearer token. A colleague says that is insecure. What is the safer approach and what are the trade-offs?
#javascript#security#jwt#cookies
The risk with localStorage is XSS: any injected script (a compromised npm package, an unsanitised comment, a third-party widget) can read it and send it to an attacker. The token then works from anywhere until it expires.
The server sets the session in a cookie that JavaScript cannot read:
Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=3600
HttpOnly: not readable by scripts, so XSS cannot steal it (it can still make requests as the user while the page is open).Secure: HTTPS only.SameSite=Lax (or Strict): blocks most cross-site request forgery. For sensitive state-changing endpoints also use CSRF tokens or check the Origin header.Client code then just calls fetch('/api/me', { credentials: 'include' }) if the API is on another origin (with proper CORS and SameSite=None; Secure where necessary).
| localStorage | HttpOnly cookie | |
|---|---|---|
| XSS steals token | yes | no |
| CSRF risk | no (manual header) | yes, mitigate with SameSite |
| Works with mobile/other clients | easy | needs cookie handling |
| SSR can read it | no | yes |
innerHTML).Inspect token contents with the JWT decoder, and remember a JWT payload is only encoded, not encrypted.
Pitfall: storing a token in a normal (non-HttpOnly) cookie has the same XSS exposure as localStorage.