Storage Options
Storage Options
localStorage
// Easy but vulnerable to XSS
localStorage.setItem('token', jwtToken);
const token = localStorage.getItem('token');
// Can be accessed by any JavaScript
// document.cookie won't show it
// But XSS can steal it
sessionStorage
// Similar to localStorage but per-tab
sessionStorage.setItem('token', jwtToken);
// Cleared when tab closes
// Still vulnerable to XSS
Cookies (HttpOnly)
// Most secure option
res.cookie('session', token, {
httpOnly: true, // No JavaScript access
secure: true, // HTTPS only
sameSite: 'strict',
});
// Automatically sent with requests
// Not accessible via JavaScript
Comparison
| Storage | XSS Risk | CSRF Risk | Access |
|---|---|---|---|
| localStorage | High | Low | JavaScript |
| sessionStorage | High | Low | JavaScript |
| Cookie (HttpOnly) | Low | High* | HTTP only |
| In-memory | Low | Low | JavaScript |
*Requires CSRF protection
In-Memory Storage
// Most secure, but lost on refresh
let token = null;
function setToken(newToken) {
token = newToken;
}
function getToken() {
return token;
}
// Or use React state
const [token, setToken] = useState(null);
JWT Security
JWT Security
JWT Structure
Header.Payload.Signature
Header: { "alg": "HS256", "typ": "JWT" }
Payload: { "sub": 123, "name": "John", "exp": 1234567890 }
Signature: HMAC-SHA256(base64(header) + "." + base64(payload), secret)
JWT Storage
// Option 1: HttpOnly cookie (recommended)
res.cookie('jwt', token, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 15 * 60 * 1000, // 15 minutes
});
// Option 2: In-memory (for SPAs)
let accessToken = null;
async function login() {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
});
const { token } = await response.json();
accessToken = token; // Store in memory
}
// Option 3: Short-lived in localStorage (less secure)
localStorage.setItem('token', token);
// But requires refresh token mechanism
Token Refresh
// Refresh token in HttpOnly cookie
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
});
// Access token in memory
let accessToken = null;
async function refreshAccessToken() {
const response = await fetch('/api/refresh', {
method: 'POST',
credentials: 'include', // Send cookies
});
const { token } = await response.json();
accessToken = token;
}
// Auto-refresh before expiration
setInterval(refreshAccessToken, 14 * 60 * 1000); // Every 14 minutes
JWT Best Practices
- Never store secrets in JWT
- Use short expiration for access tokens (15 min)
- Use refresh tokens for long-lived sessions
- Store tokens securely (HttpOnly cookie or memory)
- Validate tokens server-side
- Use strong secrets
- Implement token revocation
- Use HTTPS only
Token Security
Token Security
Preventing Token Theft
// 1. Use HttpOnly cookies
res.cookie('token', jwt, { httpOnly: true });
// 2. Set short expiration
const token = jwt.sign(payload, secret, { expiresIn: '15m' });
// 3. Use refresh tokens
const refreshToken = jwt.sign(payload, secret, { expiresIn: '7d' });
// 4. Implement token revocation
const revokedTokens = new Set();
function isTokenRevoked(token) {
return revokedTokens.has(token);
}
// 5. Use token binding
const token = jwt.sign(
{ ...payload, sessionId: req.sessionId },
secret
);
Token in Transit
// Always use HTTPS
app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https' && process.env.NODE_ENV === 'production') {
return res.redirect(`https://${req.header('host')}${req.url}`);
}
next();
});
// Use Authorization header for APIs
fetch('/api/data', {
headers: {
'Authorization': `Bearer ${token}`,
},
});
// Or use cookies (automatically sent)
fetch('/api/data', {
credentials: 'include',
});
Token Validation
// Server-side validation
function verifyToken(token) {
try {
const decoded = jwt.verify(token, secret);
// Check if token is revoked
if (isTokenRevoked(token)) {
throw new Error('Token revoked');
}
// Check expiration
if (decoded.exp < Date.now() / 1000) {
throw new Error('Token expired');
}
return decoded;
} catch (error) {
throw new Error('Invalid token');
}
}
// Middleware
function authMiddleware(req, res, next) {
const token = req.cookies.token || req.header('Authorization')?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token' });
}
try {
req.user = verifyToken(token);
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
}
Common Mistakes
// Bad: Storing token in localStorage (XSS vulnerable)
localStorage.setItem('token', jwt);
// Bad: Storing secrets in JWT
const token = jwt.sign({ secret: 'mysecret' }, key);
// Bad: Long expiration without refresh
const token = jwt.sign(payload, secret, { expiresIn: '30d' });
// Good: HttpOnly cookie with short expiration
res.cookie('token', jwt, { httpOnly: true, maxAge: 900000 });
// Good: Refresh token mechanism
const accessToken = jwt.sign(payload, secret, { expiresIn: '15m' });
const refreshToken = jwt.sign(payload, secret, { expiresIn: '7d' });
Testing Token Security
describe('Token Security', () => {
it('should not expose token in response body', async () => {
const response = await request(app)
.post('/login')
.send({ email: 'test@example.com', password: 'password' });
expect(response.body.token).toBeUndefined();
});
it('should set HttpOnly cookie', async () => {
const response = await request(app)
.post('/login')
.send({ email: 'test@example.com', password: 'password' });
const cookies = response.headers['set-cookie'];
expect(cookies).toBeDefined();
expect(cookies[0]).toContain('HttpOnly');
});
});
Practice Problems
Create a reusable React component implementing Token Storage. Include proper state management and accessibility.
Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where neededWrite unit and integration tests for Token Storage using React Testing Library.
Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility testsOptimize Token Storage for performance. Consider memoization, code splitting, and bundle size.
Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysisQuiz
1. What is the most secure way to store JWT?
2. Why use short-lived access tokens?
3. What is the primary purpose of Token Storage?
4. What is a common mistake when implementing Token Storage?
Flashcards
Question
What is JWT?
Click to reveal answer
Answer
JSON Web Token - a compact, URL-safe token format for secure information exchange.
Question
What is the safest token storage?
Click to reveal answer
Answer
HttpOnly cookies - not accessible via JavaScript, preventing XSS theft.
Question
Why use refresh tokens?
Click to reveal answer
Answer
To obtain new access tokens without requiring re-authentication.
Question
What is token revocation?
Click to reveal answer
Answer
Invalidating a token before its expiration, used for logout or security.
Question
What is Token Storage?
Click to reveal answer
Answer
Token Storage is a key concept in frontend development.
Revision Notes
Key Takeaways
- 1.HttpOnly cookies are the most secure token storage
- 2.Use short-lived access tokens with refresh tokens
- 3.Never store tokens in localStorage
- 4.Always use HTTPS for token transmission
- 5.Implement token revocation for security
Interview Tips
- •Explain token storage security trade-offs
- •Discuss JWT structure and security
- •Know refresh token patterns
Cheat Sheet
Token Storage Cheat Sheet
Storage Options
- localStorage: XSS vulnerable
- sessionStorage: XSS vulnerable
- HttpOnly cookie: Most secure
- In-memory: Secure but lost on refresh
JWT Structure
Header.Payload.Signature
Best Practices
- Use HttpOnly cookies
- Short access token (15 min)
- Long refresh token (7 days)
- Always use HTTPS
- Validate server-side
- Implement revocation
Refresh Flow
- Access token in memory/HttpOnly
- Refresh token in HttpOnly cookie
- Auto-refresh before expiry
- Re-authenticate on refresh failure