Chapter 9: SPA and Mobile Patterns — Auth in Hostile Environments
This is Part 9 of a chapter-by-chapter walkthrough of my book OpenID: Modern Identity for Developers and Architects. In the previous chapter we hardened backend APIs. Chapter 9 takes us into the opposite world: the client environments where the code runs under an adversary's nose.
9.1 — The Browser Is Hostile by Design
Everything your JavaScript can read, any script that runs in the same page can read. An XSS bug anywhere on your site — a third-party analytics pixel, a user-generated markdown post — can exfiltrate whatever's in localStorage or sessionStorage. That includes access tokens. It includes refresh tokens. It includes anything you put there "just for convenience."
The defensive stack is multiple layers: strict HTML escaping, a Content Security Policy, HttpOnly session cookies (invisible to JS), Secure and SameSite attributes, and a real CSRF defense where cookies do carry auth.
localStorage, the correct next question is "what's our XSS story?" There isn't a perfect one; that's why the modern recommendation is to keep tokens out of the browser entirely.9.2 — PKCE for SPAs and the BFF Pattern
PKCE (Chapter 4) makes the Authorization Code Flow safe for clients that can't hold a secret. That's what lets a SPA complete the OIDC handshake directly, code_verifier and all. It works, it's supported everywhere, and it removes the need to ship a client_secret to the browser.
Even so, the current recommendation for SPAs with any meaningful sensitivity is the Backend-for-Frontend (BFF) pattern. The SPA talks to a small server component that does the OIDC dance, holds the tokens server-side, and issues the browser an HttpOnly session cookie. The SPA never touches an access token. An XSS bug becomes a much smaller problem.
9.3 — Native Apps: Your Code Is the Attacker's Code
Mobile apps ship as binaries attackers can reverse. Secrets embedded in your app are secrets you've published. The right pattern is Authorization Code + PKCE (never Implicit), launched into the system browser (not an in-app WebView), returning via a claimed-HTTPS redirect or a custom URI scheme.
The in-app WebView trap deserves its own warning: a WebView your app controls is a WebView that can read everything the user types. System browsers (SFSafariViewController on iOS, Chrome Custom Tabs on Android, or AppAuth-style components) isolate the login from your app's memory space. Use them.
Storage on-device: refresh tokens in Keychain (iOS) or EncryptedSharedPreferences/Keystore (Android), access tokens in memory only. Anything you put on disk without hardware-backed encryption is one stolen device away from a bad day.
9.4 — Refresh Token Rotation (And Reuse Detection)
Refresh tokens are the most valuable credentials your client holds. They live for days or weeks. A stolen refresh token is the difference between a scare and a breach.
Rotation: every time you use a refresh token, you get a new one; the old one is invalidated. Simple and effective.
Reuse detection: if the provider ever sees the same refresh token used twice, it assumes theft and kills the entire token chain. The legitimate user gets re-prompted. The attacker is locked out. This is the pattern you want your provider (and your client library) to implement.
Chapter 9 walks through the client-side state handling this requires — persisting the new token before acting on the new access token, because getting that order wrong can log real users out of their real sessions during normal use.
What Chapter 9 Sets Up
After Chapter 9 you should be able to pick the right pattern for each client class: BFF for SPAs with real sensitivity; PKCE-in-browser when you're happy with the XSS exposure; Authorization Code + PKCE via the system browser for native apps; refresh token rotation with reuse detection everywhere. And you should know exactly which tokens live in which storage and why.
Next up — Chapter 10: Single Sign-On at Scale. We shift to the enterprise side of identity: corporate IdPs, the CIAM and SSO vendor ecosystem (Okta, Auth0, Entra ID), multi-tenant design, account linking, and external user access. If Chapters 7–9 were about the client, Chapter 10 is about running identity for an organization.
Sho Shimoda
I share and organize what I’ve learned and experienced.カテゴリー
タグ
検索ログ
Development & Technical Consulting
Working on a new product or exploring a technical idea? We help teams with system design, architecture reviews, requirements definition, proof-of-concept development, and full implementation. Whether you need a quick technical assessment or end-to-end support, feel free to reach out.
Contact Us