100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Token Storage Best Practices

Where and how a client stores access and refresh tokens is often the deciding factor in whether an OAuth 2.0 integration is actually secure in practice.

Tokens & ScopesIntermediate9 min readJul 10, 2026
Analogies

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.

http
// 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

Was this page helpful?

Topics covered

#Programming#OAuth20StudyNotes#TokenStorageBestPractices#Token#Storage#Location#Matters#StudyNotes#SkillVeris#ExamPrep