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();
    }
}