When your Android app needs to access a user's data on a third-party server (like their Google Drive files or their profile on your company's backend), you need a secure way to get permission. The industry standard for this is OAuth2. However, the original OAuth2 specification had a major security vulnerability when used on public clients like mobile apps.
This guide will explain that vulnerability and walk you through the modern, secure solution: the Authorization Code Flow with PKCE (Proof Key for Code Exchange). We'll implement the full flow in Kotlin using the excellent AppAuth library.
The Problem: Why Traditional OAuth2 is Insecure on Mobile
In the standard web server flow, the server can prove its identity by sending its `client_secret` when exchanging an authorization code for an access token. But if a mobile app does this, a malicious app on the same device could intercept the authorization code and use it (along with the public `client_id`) to get an access token for your user's account, because there's no secret to stop it.
The Solution: PKCE (Proof Key for Code Exchange)
PKCE (pronounced "pixie") solves this problem by adding a dynamic, request-specific secret. It works like this:
- Your app generates a random secret string called the Code Verifier.
- It then creates a hash of that verifier, called the Code Challenge.
- When it sends the user to the login page, it includes the `code_challenge`.
- After the user logs in, the server gives your app an `authorization_code`.
- When your app exchanges the `authorization_code` for an access token, it also sends the original, un-hashed `code_verifier`.
- The server hashes the `code_verifier` it just received and checks if it matches the `code_challenge` from the first step. If they match, it knows the request is coming from the legitimate app and issues the token.
A malicious app might intercept the `authorization_code`, but it won't have the original `code_verifier`, so the final step will fail. It's a brilliant and simple way to secure the flow without a static, long-lived secret.
Implementation with AppAuth
The AppAuth library is the official recommendation from the OAuth working group for implementing this flow on Android. It handles the Custom Tabs integration, the token exchange, and the PKCE challenge generation for you.
Step 1: Add Dependencies and Configure the Redirect
// In app/build.gradle.kts
implementation("net.openid:appauth:0.11.1") // Use latest
// In app/build.gradle.kts (inside defaultConfig)
manifestPlaceholders = [
"appAuthRedirectScheme": "com.androshelf.app"
]
You also need to add an intent filter to your `AndroidManifest.xml` to handle the redirect back to your app after the user logs in.
Step 2: Configure and Launch the Authorization Request
You'll need to get your OAuth2 endpoints and client ID from your provider (e.g., Auth0, Google, Okta).
val serviceConfig = AuthorizationServiceConfiguration(
Uri.parse("https://your-auth-provider.com/authorize"), // Authorization endpoint
Uri.parse("https://your-auth-provider.com/token") // Token endpoint
)
val authRequestBuilder = AuthorizationRequest.Builder(
serviceConfig,
"YOUR_CLIENT_ID",
ResponseTypeValues.CODE,
Uri.parse("com.androshelf.app:/oauth2redirect") // Must match your manifest placeholder
)
val authRequest = authRequestBuilder
.setScope("openid profile email")
.build()
val authService = AuthorizationService(this)
val authIntent = authService.getAuthorizationRequestIntent(authRequest)
startActivityForResult(authIntent, AUTH_REQUEST_CODE)
Step 3: Handle the Response and Exchange the Token
In onActivityResult
, you'll receive the response and use it to perform the token exchange. AppAuth automatically handles sending the code verifier.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == AUTH_REQUEST_CODE) {
val resp = AuthorizationResponse.fromIntent(data!!)
val ex = AuthorizationException.fromIntent(data)
if (resp != null) {
// Authorization was successful, exchange the code for a token
val authService = AuthorizationService(this)
authService.performTokenRequest(resp.createTokenExchangeRequest()) { tokenResponse, exception ->
if (tokenResponse != null) {
// You have the access token! Securely store it.
val accessToken = tokenResponse.accessToken
val refreshToken = tokenResponse.refreshToken
} else {
// Handle error
}
}
}
}
}
With the access token in hand, you can now use it in an OkHttp interceptor to make authenticated calls to your API with Retrofit. By using OAuth2 with PKCE, you've implemented the modern, secure standard for API authentication on mobile, protecting your users and your platform.