Why Storage Location Matters
OAuth 2.0's protocol design can be flawless and still be undermined entirely by where a client keeps its tokens after receiving them. Every storage location a browser or device offers, localStorage, sessionStorage, cookies, in-memory JavaScript variables, or a mobile app's keychain, has a different threat model: some are readable by any script running on the page, some persist across browser restarts, some are sent automatically on every request, and some are wiped the instant a tab closes. Choosing the right one means matching the token's sensitivity and lifetime to a storage mechanism whose exposure window and threat model are appropriate, rather than defaulting to whatever is easiest to code against.
Cricket analogy: It's like deciding whether to keep the Ranji Trophy in a glass display case in the pavilion lobby versus a bank vault: a short-lived practice medal can sit in the open, but a trophy this valuable needs storage matched to its actual worth and exposure risk.
Browser-Based Apps: The XSS Problem
For single-page applications, the dominant threat is cross-site scripting: if an attacker can inject any JavaScript into the page, that script can read anything stored in localStorage, sessionStorage, or a non-HttpOnly cookie, and exfiltrate it to an external server. This is why the strongest recommendation for browser-based apps is to keep tokens out of JavaScript-accessible storage entirely, using HttpOnly, Secure, SameSite=Lax or Strict cookies set by a backend, so the browser sends the cookie automatically with requests but no script running on the page, malicious or otherwise, can ever read its value. A Content Security Policy that restricts script sources further reduces the odds of XSS succeeding in the first place, but should be treated as defense in depth, not a substitute for keeping tokens out of readable storage.
Cricket analogy: It's like leaving the team's dressing room door unlocked because you trust everyone in the building: one compromised badge (an XSS injection) and anyone can walk in and read the entire strategy whiteboard (localStorage) left in plain sight.
// Backend-for-Frontend sets an HttpOnly cookie the browser
// automatically sends, but page JavaScript cannot read.
HTTP/1.1 200 OK
Set-Cookie: session=eyJhbGciOiJIUzI1NiJ9...; \
HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900
// Bad: token stored where any injected script can read it
// localStorage.setItem('access_token', token); // AVOID
// Good: browser never sees the raw token; the BFF holds it
// server-side and attaches it to upstream API calls itself.Native and Mobile Apps
Native mobile apps have a different, generally stronger, threat model than browsers because there's no page-level scripting environment for XSS to exploit, but they introduce device-level risks instead: a jailbroken or rooted device, a stolen unlocked phone, or a poorly sandboxed app that writes tokens to shared storage. The correct approach is to use the platform's dedicated secure storage, iOS Keychain or Android Keystore, which are backed by hardware-level encryption and access control rather than plain files, and to never write tokens to SharedPreferences, plist files, or app-readable SQLite databases in cleartext. For highly sensitive apps, biometric-gated access to the keychain entry adds another layer, requiring Face ID or a fingerprint before the token can even be retrieved for use.
Cricket analogy: It's like a player keeping match fees in a locker with a combination lock built into the stadium wall (Keychain) instead of a loose envelope in their kit bag (plaintext file) that anyone rifling through the bag could grab.
Sender-constraining mechanisms like DPoP bind a token to a specific client-held key pair, so even if a token is somehow exfiltrated from storage, it cannot be replayed from a different device without also possessing the private key — a meaningful extra layer beyond storage hygiene alone.
- Storage choice should match a token's sensitivity and lifetime; not every credential deserves the same level of protection, but tokens should never default to convenient-but-exposed storage.
- For browser-based SPAs, the dominant threat is XSS: any script injected into the page can read localStorage, sessionStorage, and non-HttpOnly cookies.
- HttpOnly, Secure, SameSite cookies set by a backend keep the raw token out of JavaScript's reach entirely, which is the strongest practical mitigation for browser apps.
- A Backend-for-Frontend pattern lets the browser hold only a session cookie while the server-side component holds and manages the real OAuth tokens.
- Native apps should use hardware-backed secure storage — iOS Keychain or Android Keystore — never plaintext files, SharedPreferences, or app-readable databases.
- Biometric-gated access to secure storage adds a meaningful extra layer for high-sensitivity native apps.
- Sender-constraining mechanisms like DPoP reduce the impact of token exfiltration by requiring possession of a private key the storage breach alone wouldn't reveal.
Practice what you learned
1. Why is localStorage considered risky for storing OAuth tokens in a single-page application?
2. What makes an HttpOnly cookie more resistant to XSS-based token theft than localStorage?
3. What is the recommended storage location for OAuth tokens in a native mobile app?
4. What does a Backend-for-Frontend (BFF) pattern change about where OAuth tokens live for a browser-based app?
5. What additional protection does a sender-constraining mechanism like DPoP provide beyond good storage hygiene?
Was this page helpful?
You May Also Like
Refresh Tokens
Refresh tokens let a client obtain new access tokens without forcing the resource owner to re-authenticate, and how they're issued and rotated is central to OAuth 2.0 security.
Access Tokens and JWTs
Access tokens are the credentials a client presents to a resource server, and JWTs are the most common self-contained format for encoding them.
Token Introspection and Revocation
Introspection lets a resource server ask the authorization server whether a token is still valid, and revocation lets a client or user proactively kill a token before it expires.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics