Most token theft happens in browsers; the client architecture decides how exposed tokens are.
Confidential clients (server-side apps) can keep a secret. Public clients (SPAs, mobile and desktop apps) cannot: anything shipped to a device can be extracted. Both use the authorization code flow with PKCE, but token handling differs.
Native apps (RFC 8252): open the login page in the system browser or an in-app browser tab (ASWebAuthenticationSession, Custom Tabs), never an embedded WebView that could read the password. Receive the redirect via claimed HTTPS links (Universal/App Links) or a private-use URI scheme, and store refresh tokens in the Keychain or Keystore.
Single-page apps are the hardest case. Any token readable by JavaScript can be stolen by an XSS bug or a compromised dependency. Options, from most to least secure:
- Backend for frontend (BFF): a small server-side component performs the OAuth flow as a confidential client, keeps tokens server-side, and gives the browser an
HttpOnly,Secure,SameSitesession cookie. API calls go through the BFF. This is the current recommendation for sensitive apps. - Token-mediating backend: the backend gets tokens and hands short-lived access tokens to the SPA.
- Browser-only: tokens in memory (not
localStorage), refresh token rotation, strict CSP, and DPoP if available.
The implicit grant (tokens in the URL fragment) is deprecated; do not use it.
Example
Browser BFF (same site) Auth server / API
------- --------------- -----------------
GET /login -------> build authorize URL + PKCE
<------- 302 to auth server
login + consent ----------------------------------------> /authorize
callback ?code= -------> POST /token (client secret + /token
code_verifier) ------->
<------- Set-Cookie: sid=...; HttpOnly;
Secure; SameSite=Strict
fetch /api/orders -------> look up tokens for sid,
(cookie only) add Authorization: Bearer ----> API
<------- JSON <----
Tokens never reach JavaScript in the browser.Remember
- Public clients cannot keep secrets; use code flow + PKCE
- Native apps: system browser, never embedded WebViews
- SPAs: a BFF keeps tokens server-side behind an HttpOnly cookie
- The implicit grant is deprecated
Watch out for
- Storing refresh tokens in localStorage, where any XSS can read them.