Mobile API Best Practices · Mobile Auth, Security & Flaky Networks
Mobile Authentication: OAuth, PKCE & Refresh Rotation
Authorization code + PKCE via the system browser; rotating refresh tokens.
Native apps are public clients — they can't keep a client secret. The standard (RFC 8252, OAuth 2.1) is authorization code flow with PKCE in the system browser (ASWebAuthenticationSession / Custom Tabs), never an embedded WebView. Issue short-lived access tokens (5–15 min) and rotating refresh tokens (each use returns a new one; reuse of an old one revokes the family). Store tokens in Keychain / Android Keystore-backed storage. For first-party apps with your own login UI, an equivalent design is a first-party session token endpoint — but keep the same rotation and storage rules.
Mobile login options
| Criterion | Embedded WebView login | System browser + PKCE | Native first-party login → session token |
|---|---|---|---|
| Credential exposure to app | App can read password | None | App handles password |
| SSO / passkeys / MFA | Poor | Best (shared browser state) | Must build |
| Standards | Discouraged (RFC 8252) | RFC 8252 / OAuth 2.1 | Custom |
| UX | In-app | Browser sheet | Fully native |
| Use when | Never for third-party IdPs | Default | First-party apps with strong security review |
val request = AuthorizationRequest.Builder(serviceConfig, clientId, ResponseTypeValues.CODE, redirectUri)
.setScopes("openid", "profile", "offline_access")
.build() // AppAuth generates PKCE verifier + S256 challenge
val intent = authService.getAuthorizationRequestIntent(request) // Custom Tabs
launcher.launch(intent)
async def refresh(old_token: str) -> TokenPair:
rec = await tokens.find(hash_token(old_token))
if rec is None:
raise Unauthorized()
if rec.used_at is not None: # reuse detected → likely theft
await tokens.revoke_family(rec.family_id)
raise Unauthorized("refresh_token_reused")
await tokens.mark_used(rec.id)
return await issue_pair(rec.user_id, family_id=rec.family_id) # new access + new refresh
Common pitfalls
- Client secrets embedded in the app.
- Long-lived access tokens with no rotation.
- Tokens in SharedPreferences/UserDefaults in plain text.
Key points
- Auth code + PKCE in the system browser
- Short access tokens, rotating refresh tokens
- Keychain/Keystore storage
Further reading: RFC 8252: OAuth 2.0 for Native Apps
Practice questions
Why can't a mobile app keep a client secret?
Answer
The binary is distributed to users and can be decompiled; anything embedded is effectively public, so native apps are public clients and use PKCE instead.
What does refresh token rotation with reuse detection protect against?
Answer
If a stolen refresh token is used after the legitimate client already rotated it (or vice versa), the server detects reuse and revokes the whole token family.
Open in the app to mark lessons complete, take notes and track your progress.