Client-side Encryption — The Security Boundary
One sentence: framework's client-side encryption is obfuscation, not a security barrier. Real security comes from HTTPS + backend auth.
Where encryption is used
framework encrypts in 4 places (all AES-CBC + random IV):
| Where | Entry point | Encrypted content |
|---|---|---|
| Cookie | useCookie('xxx') (utils/cookie.ts) | tokenInfo / deviceId / channel_code / share_code |
| localStorage | useLocal('xxx', init) | AccountInfo (username / password / "remember me") and any business data |
| sessionStorage | useSession('xxx', init) | Business data |
| API requests | request plugin auto-encrypts POST body | Via aes-dynamic (HmacSHA256 per-request key derivation) |
Why it's "not real security"
Encryption keys live in runtimeConfig.public.cacheAesKey / runtimeConfig.public.apiKey:
// nuxt.config.ts — project constants as real values (ship with the code; a same-named NUXT_PUBLIC_* env can still override at runtime)
runtimeConfig: {
public: {
cacheAesKey: 'your-16-byte-key', // 16 / 24 / 32 bytes
apiKey: 'your-backend-key', // request-encryption key agreed with the backend
}
}
Anything under runtimeConfig.public.* is serialized into __NUXT__.config.public and injected into the client HTML. Any user can read it with one line of JS in DevTools:
// Browser console
window.__NUXT__.config.public.cacheAesKey // key in plain text
window.__NUXT__.config.public.apiKey
Once the key is out, cookies / localStorage / request bodies can all be decrypted — the encryption is effectively useless against a determined attacker. This is an inherent limitation of any pure-frontend encryption, not a framework design flaw: the client must hold the key to decrypt, and any key shipped to the client can be reverse-engineered.
What actually provides security
| What you want to prevent | The real defense |
|---|---|
| MITM eavesdropping on traffic | HTTPS / TLS (not framework encryption) |
| Backend rejecting illegal requests | Backend auth: token validation, request signing |
| DB leak leaving plaintext readable | Field-level encryption with keys in HSM/KMS, never on the frontend |
| User tampering with local data | Validate again on backend — frontend encryption only adds seconds of friction |
Then what is framework encryption good for
Some limited but real value:
- Prevents DevTools Cookies / Local Storage panel from showing sensitive fields at a glance. After "Remember me", the stored password is ciphertext rather than plain JSON — a casual onlooker can't grab it instantly.
- Prevents intermediate log/proxy/CDN layers from accidentally recording plaintext when cookies travel with every request.
- Stops low-effort scrapers that grep for keywords in raw response bodies.
Design guidance
Treat framework encryption as obfuscation. Never as a security barrier.
- ✅ Use
useCookie/useLocal/useSessionfor auth state, user preferences, UI caches — obfuscation gives enough marginal benefit. - ✅ Use framework request encryption — keeps logs clean and stops low-effort scrapers.
- ❌ Don't use "we encrypt it" as justification to store more sensitive fields. Passwords must rely on HTTPS + backend validation + user opt-in ("Remember me"), as layered defenses.
- ❌ Don't design "encrypt sensitive business data on the frontend before putting it into the DB" — that's a backend job; the key shouldn't be on the frontend.
Planned evolution
- AES-CBC → AES-GCM (shelved — see the closing comment on framework#2): the backend framework must stay compatible with legacy projects and cannot change its ECB encryption, and the Web Crypto API has no native ECB support, so the migration is shelved as a whole; to be re-evaluated when the backend framework ships a major upgrade.
- Client-side key negotiation (not planned): technically possible to use ECDH to negotiate a session key at login time, but the engineering cost vastly outweighs the benefit (HTTPS already covers 99% of transport security).
Related
- Auto-login + Token Renewal Flow — full lifecycle of auth state persistence
- request module — how request encryption works