Insecure Data Storage in Browser
Insecure Data Storage in Browser
Client-side storage mechanisms like localStorage, sessionStorage, and IndexedDB are often misused to store sensitive data. JavaScript running in the browser has full access to these storage mechanisms, making them inappropriate for sensitive information like tokens, passwords, or personal data. Understanding the security implications of different storage methods is crucial for protecting user data.
// Storage security best practices
class SecureStorage {
constructor(encryptionKey) {
this.encryptionKey = encryptionKey;
}
// NEVER store sensitive data in plain text
insecureExample() {
// DON'T DO THIS
localStorage.setItem('authToken', 'secret-token-123');
localStorage.setItem('userPassword', 'plain-text-password');
localStorage.setItem('creditCard', '1234-5678-9012-3456');
}
// Better approach: Use session storage for temporary data
storeTemporaryData(key, value) {
// Session storage is cleared when tab closes
// Still accessible to XSS, but reduced exposure window
sessionStorage.setItem(key, JSON.stringify({
value: value,
timestamp: Date.now(),
expires: Date.now() + (15 * 60 * 1000) // 15 minutes
}));
}
getTemporaryData(key) {
const stored = sessionStorage.getItem(key);
if (!stored) return null;
const data = JSON.parse(stored);
// Check expiration
if (Date.now() > data.expires) {
sessionStorage.removeItem(key);
return null;
}
return data.value;
}
// For sensitive data, use encryption (still vulnerable to XSS)
async storeEncrypted(key, value) {
try {
// Use Web Crypto API for encryption
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(value));
// Generate IV for each encryption
const iv = crypto.getRandomValues(new Uint8Array(12));
// Import key
const cryptoKey = await crypto.subtle.importKey(
'raw',
this.encryptionKey,
{ name: 'AES-GCM' },
false,
['encrypt', 'decrypt']
);
// Encrypt
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: iv },
cryptoKey,
data
);
// Store encrypted data with IV
const stored = {
iv: Array.from(iv),
data: Array.from(new Uint8Array(encrypted))
};
localStorage.setItem(key, JSON.stringify(stored));
} catch (error) {
console.error('Encryption failed:', error);
throw error;
}
}
// Secure cookie handling
setSecureCookie(name, value, days = 7) {
const expires = new Date();
expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
// Important security flags
document.cookie = `${name}=${value}; ` +
`expires=${expires.toUTCString()}; ` +
'path=/; ' +
'Secure; ' + // HTTPS only
'SameSite=Strict; ' + // CSRF protection
'HttpOnly'; // Not accessible via JavaScript (server-set only)
}
}
// Best practice: Use memory storage for truly sensitive data
class MemoryStorage {
constructor() {
// Private field - harder to access
this.#storage = new Map();
// Clear on page unload
window.addEventListener('beforeunload', () => {
this.clear();
});
}
#storage;
set(key, value) {
// Store with expiration
this.#storage.set(key, {
value,
expires: Date.now() + (5 * 60 * 1000) // 5 minutes
});
}
get(key) {
const item = this.#storage.get(key);
if (!item) return null;
if (Date.now() > item.expires) {
this.#storage.delete(key);
return null;
}
return item.value;
}
clear() {
// Overwrite values before clearing
for (let [key, item] of this.#storage) {
item.value = null;
}
this.#storage.clear();
}
}