TL;DR
localStorage is accessible to any JavaScript on your page, including XSS payloads. Never store auth tokens, secrets, or sensitive PII there. Use it only for non-sensitive preferences. For auth, use HttpOnly cookies. These prompts help you audit and fix storage issues.
Storage Security Audit
Audit my codebase for insecure localStorage/sessionStorage usage.
Search for:
- localStorage.setItem
- localStorage.getItem
- sessionStorage.setItem
- sessionStorage.getItem
- window.localStorage
- window.sessionStorage
For each usage, identify:
- What data is being stored
- Is it sensitive? (tokens, PII, secrets)
- Can it be accessed by XSS?
- Is there a safer alternative?
Flag as dangerous:
- Auth tokens (JWT, session IDs)
- API keys
- User PII (email, phone, address)
- Payment information
- Passwords or password hints
OK to store:
- UI preferences (theme, language)
- Non-sensitive cached data
- Draft content (with user consent)
Move Tokens to HttpOnly Cookies
Move auth tokens from localStorage to HttpOnly cookies.
Current (insecure):
// Storing JWT in localStorage
localStorage.setItem('token', jwt);
// Sending in header
fetch('/api', { headers: { Authorization: Bearer ${localStorage.getItem('token')} }});
New approach (secure): // Server sets HttpOnly cookie on login Set-Cookie: token=jwt; HttpOnly; Secure; SameSite=Lax; Path=/
// Client just makes requests, cookie auto-sent fetch('/api', { credentials: 'include' });
// Server reads from cookie instead of header const token = req.cookies.token;
Migration steps:
- Update login endpoint to set HttpOnly cookie
- Update API to read from cookie
- Remove localStorage token code
- Add CSRF protection (needed with cookies)
- Update logout to clear cookie server-side
XSS = localStorage compromise: If your site has any XSS vulnerability, attackers can read everything in localStorage. HttpOnly cookies are immune to XSS JavaScript access.
Safe Storage Patterns
Implement a safe wrapper for localStorage.
Safe storage wrapper: const storage = { // Never store these types FORBIDDEN_KEYS: 'token', 'jwt', 'apiKey', 'password', 'secret',
set(key, value) {
if (this.FORBIDDEN_KEYS.some(k => key.toLowerCase().includes(k))) {
console.error(Security: Cannot store '${key}' in localStorage);
return;
}
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (e) {
// Storage full or disabled
console.warn('localStorage unavailable');
}
},
get(key) { try { const item = localStorage.getItem(key); return item ? JSON.parse(item) : null; } catch { return null; } },
remove(key) { localStorage.removeItem(key); } };
Use this wrapper instead of direct localStorage access.
Token Storage Alternatives
Compare secure options for storing auth tokens.
Options ranked by security:
- HttpOnly Cookie (Best)
- Can't be accessed by JavaScript
- Automatically sent with requests
- Needs CSRF protection
- Works well for SSR and traditional apps
- Memory (Good for SPAs)
- Store token in JavaScript variable/state
- Lost on page refresh (may want this)
- Pair with silent refresh for persistence
- Immune to XSS persistence (token lost if page closes)
- SessionStorage (Better than localStorage)
- Cleared when tab closes
- Still vulnerable to XSS
- Better for short-lived tokens
- localStorage (Avoid for tokens)
- Persists forever
- Fully exposed to XSS
- Only use for non-sensitive data
Recommended pattern:
- HttpOnly cookie for refresh token
- Memory for access token
- Silent refresh to get new access token
Pro tip: Use the BFF (Backend for Frontend) pattern. Your frontend talks to your backend, which handles all token storage and third-party API calls. Tokens never touch the browser.
Can I encrypt data in localStorage?
You can, but the encryption key must be in JavaScript, so an XSS attacker can just decrypt it too. Encryption in localStorage is security theater. Use HttpOnly cookies for sensitive data.
What about IndexedDB?
IndexedDB has the same security model as localStorage - accessible to any JavaScript. It's just for larger amounts of data. Same rules apply: no sensitive data.