Encrypted Cache
@mongez/cache — Encrypted Storage
How to use
Install
yarn add @mongez/cache @mongez/encryption@mongez/encryption is a peer dependency needed only for the encrypted drivers. You may supply your own encrypt/decrypt pair if you prefer.
Full setup with @mongez/encryption
import { encrypt, decrypt, setEncryptionConfigurations,} from "@mongez/encryption";
import { EncryptedLocalStorageDriver, setCacheConfigurations,} from "@mongez/cache";
// 1. Configure the encryption key once at boot.setEncryptionConfigurations({ key: "your-app-secret" });
// 2. Pass the driver and the encrypt/decrypt pair to the cache.setCacheConfigurations({ driver: new EncryptedLocalStorageDriver(), encryption: { encrypt, decrypt, },});From this point the cache API is identical to the plain drivers:
import cache from "@mongez/cache";
cache.set("token", "abc123");// localStorage: { "token": "U2FsdGVkX18..." } ← ciphertext
cache.get("token"); // "abc123" ← decrypted transparentlyEncrypted sessionStorage variant
Replace the driver; everything else is identical:
import { EncryptedSessionStorageDriver, setCacheConfigurations } from "@mongez/cache";import { encrypt, decrypt, setEncryptionConfigurations } from "@mongez/encryption";
setEncryptionConfigurations({ key: "your-app-secret" });
setCacheConfigurations({ driver: new EncryptedSessionStorageDriver(), encryption: { encrypt, decrypt },});Bringing your own encrypt/decrypt pair
Any object with encrypt(value: any): any and decrypt(value: any): any is valid. The pair is called on every set and get:
import { EncryptedLocalStorageDriver, setCacheConfigurations } from "@mongez/cache";import CryptoJS from "crypto-js";
const SECRET = "app-secret";
setCacheConfigurations({ driver: new EncryptedLocalStorageDriver(), encryption: { encrypt: (value) => CryptoJS.AES.encrypt(JSON.stringify(value), SECRET).toString(), decrypt: (value) => { const bytes = CryptoJS.AES.decrypt(value, SECRET); return JSON.parse(bytes.toString(CryptoJS.enc.Utf8)); }, },});TTL with encrypted drivers
The encrypted driver wraps values in the same {data, expiresAt} envelope as the plain drivers before encrypting. TTL works identically:
cache.set("token", "abc123", 60 * 15); // expires in 15 minutes
// On disk: encrypted({ data: "abc123", expiresAt: <timestamp> })// On get after expiry: entry is removed, defaultValue is returned.Set a global default in setCacheConfigurations:
setCacheConfigurations({ driver: new EncryptedLocalStorageDriver(), encryption: { encrypt, decrypt }, expiresAfter: 60 * 60, // 1-hour default});Key prefixing
Works the same as plain drivers:
setCacheConfigurations({ driver: new EncryptedLocalStorageDriver(), encryption: { encrypt, decrypt }, prefix: "secure-",});
cache.set("user", { id: 1 });// localStorage key: "secure-user", value: ciphertextcache.get("user"); // { id: 1 }Rotating the encryption key
Because the encrypt/decrypt pair is read from configuration on every call, you can rotate the key without rebuilding driver instances:
import { setEncryptionConfigurations } from "@mongez/encryption";import { setCacheConfigurations, getCacheConfigurations } from "@mongez/cache";
// Rotate the @mongez/encryption key:setEncryptionConfigurations({ key: "new-secret" });
// Re-apply config to pick up the new functions:const existing = getCacheConfigurations();setCacheConfigurations({ ...existing, encryption: { encrypt, decrypt }, // encrypt/decrypt close over the new key} as any);Note: existing ciphertext written with the old key cannot be decrypted after rotation. Clear the cache or migrate entries before rotating in production.
Wiring to @mongez/atom with encrypted storage
Tokens and PII stored through @mongez/atom persist automatically using encrypted localStorage when you use the encrypted driver in the shared cache adapter:
import cache from "@mongez/cache";
export const secureAdapter = { get: (key: string) => cache.get(key), set: (key: string, value: unknown) => { cache.set(key, value); }, remove: (key: string) => { cache.remove(key); },};import { createAtom } from "@mongez/atom";import { secureAdapter } from "./adapters/cacheAdapter";
const authAtom = createAtom({ key: "auth.token", default: null, persist: secureAdapter,});Because the adapter delegates to the cache which uses EncryptedLocalStorageDriver, all atom values are transparently encrypted at rest.
Key details / Pitfalls
encryptionkey insetCacheConfigurationsis mandatory. Without it,getCacheConfig("encryption")returnsundefinedand the driver will throw a runtime error onsetorget. Always pass{ encrypt, decrypt }.- The
encryptionpair is called with the raw object before JSON-stringification. The driver passes{ data: value, expiresAt }(the envelope object) directly toencrypt. Yourencryptfunction receives a plain object, not a string. Ensure your encryptor handles that (e.g.JSON.stringifyinternally before encrypting). clear()wipes the entirelocalStorage. It is not scoped to encrypted-only keys. Useremove(key)for targeted deletion.- Legacy ciphertext (pre-envelope format) is handled gracefully. If a stored ciphertext decrypts to something without
data/expiresAtkeys (i.e. written before the envelope was introduced), the driver returns the decrypted value as-is with no expiry check. This prevents data loss during upgrades. - Decryption failure returns
defaultValue. If the ciphertext is corrupted or the key has been rotated,getreturnsnull(or the supplied default) rather than throwing. - Do not mix plain and encrypted drivers on the same key. Reading a plaintext envelope with an encrypted driver (or vice versa) will return
null/ default, not an error. Clear stale entries after switching drivers. EncryptedSessionStorageDriverhas tab-lifetime persistence. UseEncryptedLocalStorageDriverwhen you need values to survive tab close and reopen.