diagram.mmd — sequence
Mobile App Authentication sequence diagram

Mobile app authentication describes the OAuth 2.0 Authorization Code flow with PKCE (Proof Key for Code Exchange) — the recommended pattern for authenticating users in native mobile applications without exposing a client secret.

Unlike server-side web apps, native mobile apps cannot safely store a client secret in their binary. PKCE solves this by having the app generate a cryptographically random code_verifier before the auth request. It then derives a code_challenge by hashing the verifier with SHA-256. Only the challenge — not the verifier — is sent to the authorization server, so intercepting the challenge is not sufficient to complete the exchange.

The flow begins when the user taps "Sign In." The app opens a system browser (ASWebAuthenticationSession on iOS, Custom Tabs on Android) and navigates to the authorization endpoint, passing the code_challenge, client_id, redirect_uri, and requested scopes. The user authenticates in the browser — entering credentials or approving a social login — and the server redirects back to the app via a registered URI scheme or universal link, delivering a short-lived authorization_code.

The app immediately exchanges this code at the token endpoint, sending the original code_verifier. The server hashes it, compares it against the stored code_challenge, and if they match, returns an access_token and refresh_token. The access token is used to call protected API endpoints; the refresh token is stored securely in the device keychain (iOS) or Keystore (Android) and used to obtain new access tokens silently when the current one expires.

For related patterns, see JWT Authentication Flow for token structure details and Mobile API Sync for how authenticated requests flow to the backend.

Free online editor
Edit this diagram in Graphlet
Fork, modify, and export to SVG or PNG. No sign-up required.
Open in Graphlet →

Frequently asked questions

OAuth2 PKCE (Proof Key for Code Exchange) is the recommended authorisation flow for native mobile apps. Because a mobile binary cannot safely store a client secret, PKCE replaces the secret with a per-request cryptographic code verifier that only the originating app can produce.
The app generates a random `code_verifier`, hashes it to a `code_challenge`, and sends only the challenge to the authorisation server. The user authenticates in a system browser, the server returns an authorisation code to the app's redirect URI, and the app exchanges the code for tokens by sending the original verifier — proving it initiated the request.
Always prefer PKCE for native apps. The implicit flow returns tokens directly in the redirect URI fragment, which can be intercepted from browser history or other apps. PKCE never exposes tokens in the redirect and is mandated by OAuth 2.1 for all public clients.
Storing the refresh token in insecure app storage (SharedPreferences or UserDefaults in plaintext) instead of the device Keychain/Keystore is the most critical error. Other mistakes include not validating the `state` parameter against CSRF, reusing the same `code_verifier` across sessions, and forgetting to handle the case where the user denies the authorisation prompt.
A client secret flow requires the app to embed a shared secret in its binary, which can be extracted by a determined attacker. PKCE generates a fresh cryptographic proof for every authorisation request, so there is nothing static to steal — making it the correct choice for any client that cannot keep a secret confidential.
mermaid
sequenceDiagram participant App as Mobile App participant Browser as System Browser participant AuthServer as Auth Server participant API as Resource API App-->>App: Generate code_verifier (random 32 bytes) App-->>App: code_challenge = BASE64URL(SHA256(code_verifier)) App->>Browser: Open authorization URL with code_challenge Browser->>AuthServer: GET /authorize?code_challenge=CODE_CHALLENGE&client_id=CLIENT_ID AuthServer-->>Browser: Show login / consent screen Browser-->>Browser: User enters credentials Browser->>AuthServer: Submit credentials AuthServer-->>App: Redirect to app://callback?code=AUTH_CODE App->>AuthServer: POST /token with AUTH_CODE + code_verifier AuthServer-->>AuthServer: SHA256(code_verifier) == stored code_challenge? AuthServer-->>App: access_token + refresh_token App-->>App: Store refresh_token in device Keychain/Keystore App->>API: GET /api/resource with Authorization: Bearer access_token API-->>App: 200 OK with resource data
Copied to clipboard