PKCE for Native Mobile Applications

Implementing OAuth 2.0 Authorization Code + PKCE in iOS and Android native applications with custom URL schemes and universal links.

PKCE

Detailed Explanation

PKCE for Mobile Apps

Mobile applications are public clients — they cannot securely store a client_secret because the app binary can be decompiled. PKCE is essential for securing the Authorization Code flow on iOS, Android, and other native platforms.

Mobile-Specific Considerations

Custom URL Schemes vs. Universal Links

Mobile apps register a redirect URI to receive the authorization callback:

Method Example Security
Custom URL scheme myapp://callback Low — any app can register the same scheme
Universal Links (iOS) https://app.example.com/callback High — verified domain ownership
App Links (Android) https://app.example.com/callback High — verified domain ownership

Always prefer universal/app links over custom URL schemes. Custom schemes are vulnerable to interception by malicious apps that register the same scheme.

Flow Steps for Mobile

  1. Generate PKCE pair in the app before starting the flow
  2. Open system browser (ASWebAuthenticationSession on iOS, Custom Tabs on Android) — do not use embedded WebViews
  3. User authenticates in the browser
  4. Receive callback via universal link / custom URL scheme
  5. Exchange code + code_verifier for tokens
  6. Store tokens securely (Keychain on iOS, Keystore on Android)

iOS Example (Swift)

import CryptoKit

let verifier = Data(count: 32).map { _ in
    UInt8.random(in: 0...255)
}
let codeVerifier = Data(verifier).base64URLEncodedString()
let challenge = SHA256.hash(data: Data(codeVerifier.utf8))
let codeChallenge = Data(challenge).base64URLEncodedString()

Android Example (Kotlin)

val verifier = ByteArray(32).also { SecureRandom().nextBytes(it) }
val codeVerifier = Base64.encodeToString(verifier, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
val digest = MessageDigest.getInstance("SHA-256").digest(codeVerifier.toByteArray())
val codeChallenge = Base64.encodeToString(digest, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)

Security Best Practices

  • Use the system browser, never an embedded WebView (which can intercept credentials)
  • Prefer universal/app links over custom URL schemes
  • Store tokens in the platform's secure storage (Keychain/Keystore)
  • Implement certificate pinning for the token endpoint
  • Use a new PKCE pair for every authorization request

Use Case

An iOS or Android app that authenticates users via an identity provider (e.g., Google Sign-In, Azure AD B2C). The app generates a PKCE pair, opens the system browser for login, receives the authorization code via a universal link, and exchanges it for tokens.

Try It — OAuth 2.0 Flow Visualizer

Open full tool