Implementing OpenID Connect Authorization in Mobile App
OpenID Connect (OIDC) — OAuth 2.0 plus standardized way to get user information. OAuth 2.0 solves authorization task (resource access), OIDC adds authentication (who is this user). Difference is fundamental: OAuth 2.0 access token doesn't guarantee user data in predictable format; OIDC ID token — JWT with fixed set of claims (sub, iss, aud, exp, iat minimum).
ID Token: what to do correctly
Main error — trust ID token without signature verification. Seen projects where mobile app parses JWT base64-decoding and reads sub claim — without signature check, without iss and aud check. This is equivalent to trusting any JWT from anyone.
Correct flow:
- Get ID token from Authorization Server.
- Download JSON Web Key Set (JWKS) from
jwks_uriin discovery document (.well-known/openid-configuration). - Verify ID token signature with public key from JWKS.
- Check
iss== expected issuer,audcontains ourclient_id,expnot expired,noncematches (replay-attack protection).
In practice AppAuth with JWTDecode (iOS) or nimbus-jose-jwt (Android) does all this. Custom JWKS verification implementation — source of vulnerabilities.
Nonce
Generate nonce cryptographically before authorization request start, save in memory, send in request parameters. After getting ID token — check that nonce in token matches. If server returned token without nonce or with different — reject authentication. This protects from CSRF and replay attacks at mobile level.
Discovery Document and auto-configuration
OIDC providers publish metadata at .well-known/openid-configuration. AppAuth can load them automatically — no need hardcode endpoints:
// iOS — autodiscovery
OIDAuthorizationService.discoverConfiguration(forIssuer: issuerURL) { config, error in
guard let config else { return }
// config contains authorizationEndpoint, tokenEndpoint, jwksURL, etc.
}
// Android
AuthorizationServiceConfiguration.fetchFromIssuer(issuerUri) { config, error ->
// use config for building AuthorizationRequest
}
Cache discovery document reasonably (hour or two), don't download on every operation.
UserInfo endpoint
After getting access token can request userinfo_endpoint for additional claims (email, name, picture, phone_number). Claims in ID token intentionally minimal — OIDC Core doesn't guarantee their presence without explicit request via scope (profile, email, phone, address).
Important: userinfo endpoint protected by access token. If access token expired — refresh via refresh token before userinfo request. Do this transparently via interceptor/middleware in HTTP client.
Logout: often forgotten
OIDC defines three logout variants:
-
RP-Initiated Logout (rp = Relying Party, your app): redirect to
end_session_endpoint. - Front-Channel Logout: server pings known clients via iframe (not applicable for native apps).
-
Back-Channel Logout: server sends POST request to registered
backchannel_logout_uriof your server.
For mobile apps only RP-Initiated works. Open end_session_endpoint in browser (ASWebAuthenticationSession/Custom Tabs), send id_token_hint and post_logout_redirect_uri. Without id_token_hint some providers (Keycloak, Auth0) won't finish server session — user exits app but SSO session in browser stays active.
Integration with corporate IdP
Keycloak, Azure AD, Okta, Ping Identity — all support OIDC. Main differences: claims format (Azure AD uses oid instead of sub as stable user identifier), additional non-standard claims (roles, groups), refresh token rotation specifics.
Azure AD B2C adds User Flows — each flow has own issuer, breaks standard JWKS cache. Either dynamically resolve issuer from ID token, or configure static list of trusted issuers.
Timeframe
One OIDC provider with standard configuration — 5–8 business days (including tests and redirect setup). Corporate IdP with non-standard claims and B2C user flows — 10–15 days with coordination with IdP team.







