Implementing OAuth 2.0 Authorization in Mobile App
OAuth 2.0 in mobile context — not the same as OAuth 2.0 on web. Authorization Code Flow with PKCE (Proof Key for Code Exchange) — only correct option for native apps. Implicit Flow officially deprecated in RFC 9700. Client Credentials doesn't fit — no user context. If someone proposes Implicit Flow for mobile in 2024 — red flag.
Why PKCE is mandatory
Native app cannot securely store client_secret. APK/IPA decompiled in minutes — any secret in binary considered compromised by definition. PKCE solves this without client_secret: generate code_verifier (random string 43–128 characters), hash SHA-256 → code_challenge, send challenge on authorization request. On code-to-token exchange send original verifier — server checks match. Intercept code and use without verifier impossible.
code_verifier generate cryptographically:
// iOS
var buffer = [UInt8](repeating: 0, count: 32)
_ = SecRandomCopyBytes(kSecRandomDefault, buffer.count, &buffer)
let verifier = Data(buffer).base64URLEncodedString()
// Android
val bytes = ByteArray(32)
SecureRandom().nextBytes(bytes)
val verifier = Base64.encodeToString(bytes, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
Redirect URI and Custom URL Schemes
Second painful moment — redirect interception. Custom URL Scheme (myapp://callback) can be intercepted by another app on device with same scheme. Correct variant — HTTPS App Links (Android) and Universal Links (iOS). Authorization server redirects to https://yourapp.com/callback, system checks assetlinks.json / apple-app-site-association and passes URL directly to your app.
Universal Links setup requires:
- AASA file on domain at
https://yourapp.com/.well-known/apple-app-site-association -
Associated Domainscapability in Xcode with entryapplinks:yourapp.com - Handle URL in
scene(_:continue:)orapplication(_:continue:restorationHandler:)
On Android — Digital Asset Links file and intent-filter with autoVerify="true".
Libraries
Don't write OAuth 2.0 + PKCE from scratch. Use:
- iOS: AppAuth-iOS (openid.net, current version 1.7+)
- Android: AppAuth-Android
- React Native: react-native-app-auth
- Flutter: flutter_appauth
AppAuth handles PKCE generation, browser opening (via ASWebAuthenticationSession on iOS, Custom Tabs on Android), redirect handling, and code-to-token exchange.
Token storage
Access token — in memory (@State, ViewModel). Not in UserDefaults, not in SharedPreferences. Refresh token — in Keychain (iOS) or EncryptedSharedPreferences/Android Keystore (Android). Access token lives 15–60 minutes, refresh — days or weeks. Different sensitivity level — different storage level.
Refresh token interception from SharedPreferences on rooted Android device — real attack vector. EncryptedSharedPreferences from androidx.security:security-crypto closes this scenario on devices with Android Keystore (API 23+).
Working with multiple identity providers
Typical case: login via Google, Apple, corporate SAML IdP. Architecturally — single OAuthService with protocol/interface, different providers as concrete implementations. Don't mix tokens from different providers — each stored under its own key in Keychain/Keystore.
"Sign in with Apple" mandatory for App Store if Google or Facebook login exists — guideline 4.8. Violation → rejection on review.
Process and timeframe
Audit existing auth (if any) → choose/configure Authorization Server → implement PKCE flow via AppAuth → setup Universal/App Links → token storage → refresh flow → test on real devices → integration tests with Authorization Server.
Timeframe: 5–10 business days for one provider. Each additional provider — plus 2–3 days. Server-side Authorization Server configuration not included in this estimate.







