Native Apps as Public Clients
Like SPAs, native mobile apps cannot keep a client_secret confidential — the compiled binary can be decompiled and any embedded secret extracted — so mobile apps are also public OAuth clients. RFC 8252, OAuth 2.0 for Native Apps, mandates that the authorization request must open in an external user-agent, meaning the platform's system browser or an in-app browser tab like Android's Custom Tabs or iOS's ASWebAuthenticationSession, rather than an embedded WebView the host app fully controls.
Cricket analogy: It's like decompiling a fielding app's APK being similar to a rival team getting hold of a printed strategy sheet left in a dugout — since the secret is embedded in something physically handed out, it can't stay confidential.
PKCE and Redirect URIs on Mobile
After completing authorization in the external browser, the operating system must route the redirect back into the correct app; this is done either via a custom URI scheme like com.example.app://callback, which is easy to register but can be claimed by multiple apps on some platforms, or preferably via claimed HTTPS redirects — Android App Links or iOS Universal Links — which are cryptographically tied to a specific app through a domain-hosted association file. PKCE is mandatory in this flow, since it's what actually binds the authorization code to the specific app instance that initiated the request, closing the gap left by an unclaimed custom scheme.
Cricket analogy: A custom URI scheme is like a generic team jersey number that two different clubs could both claim, while an HTTPS App Link is like a jersey verified against the official league registry — only PKCE's proof-of-possession check confirms which specific player actually started the play.
// AppAuth-Android: configuring an authorization request with PKCE
val serviceConfig = AuthorizationServiceConfiguration(
Uri.parse("https://idp.example.com/authorize"),
Uri.parse("https://idp.example.com/token")
)
val authRequest = AuthorizationRequest.Builder(
serviceConfig,
"mobile-client-id",
ResponseTypeValues.CODE,
Uri.parse("com.example.app:/oauth2redirect")
)
.setScope("openid profile email offline_access")
.setCodeVerifier(CodeVerifierUtil.generateRandomCodeVerifier())
// AppAuth derives and attaches the code_challenge automatically
.build()
val authService = AuthorizationService(context)
val authIntent = authService.getAuthorizationRequestIntent(authRequest)
startActivityForResult(authIntent, RC_AUTH)Secure Token Storage on Device
Once tokens are obtained, they must be stored using the platform's hardware-backed secure storage: the Android Keystore system, which can wrap keys in a Trusted Execution Environment or Secure Element, or the iOS Keychain, which encrypts entries and can be gated behind biometric authentication. Storing a refresh_token in plain SharedPreferences on Android or an unencrypted plist on iOS leaves it readable by any process with root or jailbreak access, or recoverable from an unencrypted device backup.
Cricket analogy: It's like storing the team's match strategy in a locked safe at the team hotel rather than in a notebook left in a shared team bus — the Keystore/Keychain is the safe, SharedPreferences is the notebook anyone could flip through.
AppAuth (Google's open-source library for Android and iOS) implements RFC 8252's recommendations out of the box — external user-agent usage, PKCE, and safe redirect handling — so most teams should adopt it rather than reimplementing the native OAuth flow from scratch.
Handling Redirect Back to App and Token Refresh
After the user authenticates in the system browser, the OS matches the redirect URI to the registered app and routes control back to it with the authorization code, which the app then exchanges at the token endpoint along with its code_verifier. From then on, the app uses the refresh_token grant to silently obtain new access tokens in the background, avoiding repeated browser redirects and keeping the user logged in across app launches until the refresh token itself expires or is revoked.
Cricket analogy: It's like a runner completing a quick single and returning to the same end without needing to restart the over — the app returns from the browser and resumes its session using the refresh token instead of restarting the whole login.
Never use an embedded WebView for the login screen. WebViews don't share cookies or session state with the system browser, forcing users to log in repeatedly even if they're already authenticated elsewhere, and a malicious host app could inject JavaScript into the WebView to capture credentials directly — a practice RFC 8252 explicitly prohibits and that major app stores increasingly reject during review.
- Mobile apps are public OAuth clients, just like SPAs, since compiled binaries can be decompiled to extract embedded secrets.
- RFC 8252 mandates using the system browser or an in-app browser tab, never an embedded WebView, for the login screen.
- PKCE is mandatory on mobile to bind the authorization code to the specific app instance that started the flow.
- Claimed HTTPS redirects (App Links / Universal Links) are safer than custom URI schemes, which can be claimed by other apps.
- Refresh tokens and other secrets must be stored in hardware-backed secure storage: Android Keystore or iOS Keychain.
- AppAuth is a widely used open-source library implementing RFC 8252's native app best practices out of the box.
- Embedded WebViews for login break session sharing and are explicitly disallowed by RFC 8252 and app store policy.
Practice what you learned
1. Why are native mobile apps treated as public OAuth clients?
2. What does RFC 8252 require for the login screen in a native app?
3. Why is PKCE mandatory for the mobile OAuth flow?
4. What is a security advantage of App Links / Universal Links over a custom URI scheme for redirects?
5. Where should a mobile app store its refresh token?
Was this page helpful?
You May Also Like
OAuth for Single-Page Apps
Learn why browser-based SPAs must be treated as public OAuth clients, how PKCE secures the Authorization Code flow, and why the BFF pattern beats storing tokens in the browser.
OAuth with OpenID Connect
Understand how OpenID Connect layers standardized authentication — ID tokens, claims, and the UserInfo endpoint — on top of OAuth 2.0's authorization framework.
Implementing OAuth in a Web App
A practical guide to wiring the Authorization Code flow into a server-rendered web application, from callback handling to secure token storage and refresh rotation.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics