Client-side encryption for localStorage - Framework-agnostic with React and Angular support
npm install @bantis/local-cipher



Client-side encryption for localStorage using AES-256-GCM
Protect sensitive data in browser storage from XSS attacks, local file access, and casual inspection. Drop-in replacement for localStorage with automatic encryption/decryption.
localStorage stores data in plain text. Anyone with access to DevTools, browser files, or malicious scripts can read:
- Authentication tokens
- User credentials
- API keys
- Personal information
Transparent AES-256-GCM encryption with browser fingerprinting. Data is encrypted before storage and decrypted on retrieval. Keys are derived from browser characteristics, making data unreadable outside the original browser context.
``bash`
npm install @bantis/local-cipher
`typescript
import { SecureStorage } from '@bantis/local-cipher';
const storage = SecureStorage.getInstance();
// Store encrypted
await storage.setItem('token', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...');
// Retrieve decrypted
const token = await storage.getItem('token');
// Works like localStorage
await storage.removeItem('token');
storage.clear();
`
Before:
``
localStorage: { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }
After:
``
localStorage: { "__enc_a7f5d8e2": "Qm9keUVuY3J5cHRlZERhdGE..." }
- ✅ AES-256-GCM encryption with authentication
- ✅ PBKDF2 key derivation (100k+ iterations)
- ✅ Browser fingerprinting for unique keys per device
- ✅ Key obfuscation - even key names are encrypted
- ✅ TTL/Expiration - auto-delete expired data
- ✅ Event system - monitor storage operations
- ✅ Compression - automatic gzip for large values
- ✅ Namespaces - organize data in isolated spaces
- ✅ Integrity checks - SHA-256 checksums
- ✅ TypeScript - full type definitions
- ✅ Framework support - React hooks, Angular service
`typescript
// Store JWT with 1-hour expiration
await storage.setItemWithExpiry('accessToken', jwt, {
expiresIn: 3600000
});
// Auto-cleanup expired tokens
storage.on('expired', ({ key }) => {
console.log(Token ${key} expired, redirecting to login);`
window.location.href = '/login';
});
`typescript
const userStorage = storage.namespace('user');
await userStorage.setItem('theme', 'dark');
await userStorage.setItem('language', 'en');
// Isolated from other namespaces
const appStorage = storage.namespace('app');
`
`typescript
// Store with compression for large data
const storage = SecureStorage.getInstance({
storage: { compression: true, compressionThreshold: 512 }
});
await storage.setItem('userData', JSON.stringify(largeObject));
`
`tsx
import { useSecureStorage, useSecureStorageEvents } from '@bantis/local-cipher';
function App() {
const [token, setToken, loading] = useSecureStorage('token', '');
useSecureStorageEvents('expired', () => {
// Handle expiration
});
if (loading) return
🚀 Quick Start
$3
`javascript
import { SecureStorage } from '@bantis/local-cipher';const storage = SecureStorage.getInstance();
// Store encrypted
await storage.setItem('accessToken', 'mi-token-secreto');
// Retrieve decrypted
const token = await storage.getItem('accessToken');
// With expiration (1 hour)
await storage.setItemWithExpiry('session', sessionData, { expiresIn: 3600000 });
// Remove
await storage.removeItem('accessToken');
`Before:
`
localStorage: { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }
`After:
`
localStorage: { "__enc_a7f5d8e2": "Qm9keUVuY3J5cHRlZERhdGE..." }
`$3
`jsx
import { useSecureStorage } from '@bantis/local-cipher/react';function App() {
const [token, setToken, loading] = useSecureStorage('accessToken', '');
if (loading) return
Loading...; return (
Token: {token}
);
}
`$3
`typescript
import { SecureStorageService } from '@bantis/local-cipher/angular';@Component({
selector: 'app-root',
template:
})
export class AppComponent {
token$ = this.storage.getItem('accessToken'); constructor(private storage: SecureStorageService) {}
saveToken(token: string) {
this.storage.setItem('accessToken', token).subscribe();
}
}
`
Angular Integration
`typescript
import { SecureStorageService } from '@bantis/local-cipher';@Component({...})
export class AppComponent {
token$ = this.storage.getItem('token');
constructor(private storage: SecureStorageService) {
this.storage.events$.subscribe(event => {
console.log('Storage event:', event);
});
}
}
`Configuration
`typescript
const storage = SecureStorage.getInstance({
encryption: {
iterations: 150000, // PBKDF2 iterations
keyLength: 256, // 128, 192, or 256 bits
saltLength: 16, // Salt size in bytes
ivLength: 12, // IV size in bytes
},
storage: {
compression: true, // Enable gzip compression
compressionThreshold: 1024, // Compress if > 1KB
autoCleanup: true, // Auto-delete expired items
cleanupInterval: 60000 // Cleanup every 60s
},
debug: {
enabled: false, // Enable debug logging
logLevel: 'info' // silent, error, warn, info, debug, verbose
}
});
`Security
$3
✅ XSS attacks - Encrypted data is useless without the browser-specific key
✅ Local file access - Malware reading browser files gets encrypted data
✅ Casual inspection - DevTools shows encrypted values
✅ Data tampering - Integrity checks detect modifications
$3
❌ Server-side attacks - Encryption is client-side only
❌ Man-in-the-Middle - Use HTTPS for data in transit
❌ Memory dumps - Keys exist in memory during runtime
❌ Compromised browser - If the browser is compromised, all bets are off
❌ Physical access during active session - Data is decrypted when accessed
$3
1. Use HTTPS - Always transmit data over secure connections
2. Short TTLs - Set expiration on sensitive data
3. Clear on logout - Call
storage.clear() when user logs out
4. Monitor events - Track suspicious activity via event listeners
5. Rotate keys - Periodically call storage.rotateKeys()
6. Don't store passwords - Never store plaintext passwords, even encryptedBrowser Support
Requires Web Crypto API:
- Chrome 37+
- Firefox 34+
- Safari 11+
- Edge 12+
- Opera 24+
Fallback: Gracefully degrades to unencrypted localStorage in unsupported browsers.
API Reference
$3
`typescript
setItem(key: string, value: string): Promise
getItem(key: string): Promise
removeItem(key: string): Promise
hasItem(key: string): Promise
clear(): void
`$3
`typescript
setItemWithExpiry(key: string, value: string, options: {
expiresIn?: number; // milliseconds from now
expiresAt?: Date; // absolute date
}): PromisecleanExpired(): Promise // Returns count of deleted items
`$3
`typescript
on(event: StorageEventType, listener: EventListener): void
off(event: StorageEventType, listener: EventListener): void
once(event: StorageEventType, listener: EventListener): void// Event types: 'encrypted', 'decrypted', 'deleted', 'cleared',
// 'expired', 'error', 'keyRotated', 'compressed'
`$3
`typescript
namespace(name: string): NamespacedStorageconst userStorage = storage.namespace('user');
await userStorage.setItem('profile', data);
await userStorage.clearNamespace();
`$3
`typescript
rotateKeys(): Promise
exportEncryptedData(): Promise
importEncryptedData(backup: EncryptedBackup): Promise
`FAQ
Q: Is this secure enough for passwords?
A: No. Never store passwords in localStorage, even encrypted. Use secure, httpOnly cookies or sessionStorage with server-side session management.
Q: Can data be decrypted on another device?
A: No. Keys are derived from browser fingerprinting. Data encrypted on Chrome/Windows cannot be decrypted on Firefox/Mac.
Q: What happens if Web Crypto API is unavailable?
A: The library falls back to unencrypted localStorage with a console warning. Check
EncryptionHelper.isSupported() to detect support.Q: Does this protect against XSS?
A: Partially. It makes stolen data harder to use, but XSS can still intercept data when it's decrypted in memory. Use CSP headers and sanitize inputs.
Q: How is this different from sessionStorage?
A: sessionStorage is cleared on tab close. This provides persistent, encrypted storage across sessions.
Q: Can I use this in Node.js?
A: No. This library requires browser APIs (Web Crypto, localStorage). For Node.js, use native
crypto module.Q: What's the performance impact?
A: Encryption adds ~2-5ms per operation. Compression adds ~5-10ms for large values. Negligible for most use cases.
Migration from v1
v1 data is automatically migrated to v2 format on first read. No action required.
`typescript
// v1 and v2 are API-compatible
const storage = SecureStorage.getInstance(); // Works with both
`Migration from v2.0.x to v2.1.0
> [!WARNING]
> Breaking Change: Framework-specific imports required
$3
Before (v2.0.x):
`typescript
import { useSecureStorage } from '@bantis/local-cipher';
`After (v2.1.0):
`typescript
import { useSecureStorage } from '@bantis/local-cipher/react';
`$3
Before (v2.0.x):
`typescript
import { SecureStorageService } from '@bantis/local-cipher';
`After (v2.1.0):
`typescript
import { SecureStorageService } from '@bantis/local-cipher/angular';
`$3
No changes required:
`typescript
import { SecureStorage } from '@bantis/local-cipher'; // Still works
`$3
v2.0.x bundled all framework code together, causing dependency conflicts:
- React projects needed Angular dependencies (
@angular/core, rxjs`)v2.1.0 separates frameworks into independent bundles:
- ✅ Core: 42KB (no framework dependencies)
- ✅ React: 49KB (core + hooks)
- ✅ Angular: 55KB (core + service)