Introduction
JSON Web Tokens (JWT) are widely used for authentication in modern web applications. They help verify user identity without storing session data on the server. But a common question among developers is: “Where should I store my JWT token — in localStorage or in cookies?”
This guide will explain both approaches in simple words, discuss their security implications, and help you choose the best method for your Next.js, React, Angular, or Node.js app. You’ll also see examples and best practices for keeping JWTs safe from attacks.
What is a JWT Token?
A JWT (JSON Web Token) is a compact, secure way to represent user data between a client and server. After a user logs in, the server generates a token that contains encoded information like:
{
"userId": "12345",
"email": "[email protected]",
"role": "admin"
}
This token is signed using a secret key, ensuring its authenticity. The client stores this token and includes it with every request to prove the user’s identity.
Example of a JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NSIsImVtYWlsIjoiZXhhbXBsZUBkb21haW4uY29tIn0.uKkhH8QNUZKJfRHF1a_jkXfVZhEN9eCMo64hO_xXYYA
Now, let’s explore where and how to store it securely.
Option 1. Storing JWT in localStorage
What is localStorage?
localStorage is a built-in browser storage system that stores key-value pairs on the client side. Data saved here persists even after refreshing or closing the browser.
Example
// Store token
localStorage.setItem('token', jwtToken);
// Retrieve token
const token = localStorage.getItem('token');
✅ Advantages of Using localStorage
Easy to Use – Simple JavaScript API for storing and retrieving tokens.
Persistent Storage – Tokens stay in the browser even after the page reloads or browser restarts.
Framework Independent – Works with any front-end framework like React, Angular, or Vue.
⚠️ Disadvantages of localStorage
Vulnerable to XSS (Cross-Site Scripting) – If an attacker injects JavaScript into your website, they can easily read the JWT from localStorage.
No Built-In Expiry – You must manually handle token expiration.
Not Automatically Sent to Server – You need to manually attach the token in the request headers:
axios.get('/api/profile', {
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`
}
});
🛡️ Security Tips for localStorage
Use strong Content Security Policy (CSP) to prevent XSS.
Avoid storing sensitive data (like passwords or secrets) in the token.
Always sanitize user input to block malicious scripts.
Consider rotating JWTs regularly and using short expiration times.
Option 2. Storing JWT in Cookies
What Are Cookies?
Cookies are small pieces of data that a server sends to a user's browser. They are automatically included with every HTTP request made to the server.
Example of setting a cookie:
res.cookie('token', jwtToken, {
httpOnly: true,
secure: true,
sameSite: 'Strict',
maxAge: 3600000 // 1 hour
});
✅ Advantages of Using Cookies
More Secure (when HttpOnly) – Cookies can be marked HttpOnly, preventing JavaScript access and reducing XSS risk.
Automatic Transmission – The browser automatically sends cookies with every request to the same domain.
Built-In Expiry – You can control how long a cookie stays valid.
⚠️ Disadvantages of Cookies
Vulnerable to CSRF (Cross-Site Request Forgery) – Since cookies are automatically sent with requests, an attacker can trick users into making unintended requests.
Limited Storage – Cookies can only store up to 4KB of data.
CORS Configuration Required – When using cookies across different domains, you need to configure CORS properly.
Example of enabling credentials in Express
app.use(cors({
origin: 'https://yourdomain.com',
credentials: true
}));
And in frontend (React/Next.js)
axios.get('https://yourdomain.com/api/profile', { withCredentials: true });
🛡️ Security Tips for Cookies
Always use HttpOnly and Secure flags.
Set SameSite to Strict or Lax to prevent CSRF attacks.
Use CSRF tokens if handling sensitive actions.
Use HTTPS for all API calls.
localStorage vs Cookies: Comparison Table
| Feature | localStorage | Cookies |
|---|
| Ease of Use | Easy to implement | Requires configuration |
| Persistence | Persists after refresh | Persists based on expiry |
| Automatic Transmission | ❌ No | ✅ Yes |
| XSS Protection | ❌ Vulnerable | ✅ Safer (with HttpOnly) |
| CSRF Protection | ✅ Safe | ❌ Needs protection |
| Storage Limit | 5-10 MB | 4 KB |
| Access from JS | ✅ Yes | ❌ No (if HttpOnly) |
Best Practice: Use Cookies with HttpOnly + CSRF Tokens
If you’re building a secure web application (like banking, e-commerce, or admin dashboards), the safest approach is to store JWT in HttpOnly cookies. This prevents JavaScript from accessing the token, reducing XSS risks.
To protect against CSRF attacks, you can use a CSRF token that’s sent as a header with every request.
Example
// Backend sets CSRF token
res.cookie('csrfToken', generateToken(), { httpOnly: false, secure: true });
// Frontend sends CSRF token in request header
axios.post('/api/update', data, {
headers: { 'X-CSRF-Token': csrfToken }
});
This ensures that both XSS and CSRF are handled safely.
When to Use localStorage
Use localStorage if:
You’re building a public or low-risk application.
Your app doesn’t perform highly sensitive actions (like payments).
You can manage security (CSP + short-lived tokens).
Example
localStorage.setItem('token', jwtToken);
axios.get('/api/profile', {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
});
When to Use Cookies
Use cookies if
You’re handling user authentication or sensitive data.
You need automatic token transmission with each request.
You can implement CSRF protection.
Example
res.cookie('token', jwtToken, {
httpOnly: true,
secure: true,
sameSite: 'Strict'
});
Summary
Choosing between localStorage and cookies for storing JWT depends on your application’s security needs.
For maximum security in 2025 and beyond, use HttpOnly cookies with CSRF protection. This method ensures your tokens are stored safely and transmitted securely.