Technical guide

Make custom mobile SSO workflows work in mobile apps

Build custom mobile SSO on ASWebAuthenticationSession and Custom Tabs with verified App Links, PKCE and audience-checked tokens, then test renewal and logout.

  • System browser, always

    Sign-in runs in ASWebAuthenticationSession on iOS and Custom Tabs on Android, so the identity provider's session and protections apply.

  • Callbacks you own

    Return to the app through a verified link or a strictly matched redirect, protected by PKCE and state.

  • Test the whole session

    Renewal, revocation, logout and account switching break more often than the first login.

A custom mobile SSO workflow works when the customisation stays in your app and the sign-in itself stays standard. Run the identity provider’s page in the system browser: ASWebAuthenticationSession on iOS, Custom Tabs on Android. Return to the app through a callback you own, ideally a verified HTTPS link, and protect the exchange with PKCE and state. Validate the ID token, send audience-restricted access tokens to your APIs, and test renewal, revocation, logout and account switching as carefully as the first login. Verify Android and iOS separately. This is a vendor-neutral guide for native apps.

For the product context, see mobile SSO integration.

Why the system browser

RFC 8252 requires native apps to use an external user agent for OAuth, not an embedded web view. The system browser keeps the user’s password out of your process, lets the provider apply its own protections and shares the provider’s session, which is what makes single sign-on work across apps.

Custom screens are fine before and after sign-in: choosing an organisation, picking an account, or resuming a payment after authentication. Do not collect the identity provider’s password in a native form or a web view.

iOS: ASWebAuthenticationSession

  • Create the session with the authorisation URL and a callback, and set a presentationContextProvider.
  • Keep prefersEphemeralWebBrowserSession false when you want SSO. Setting it to true stops cookie sharing with Safari, so the user signs in every time. That suits shared devices or a deliberate “sign in as someone else”.
  • Use a custom URL scheme callback, or from iOS 17.4 the ASWebAuthenticationSession.Callback API, which can match an HTTPS host and path.
  • Treat a canceledLogin error as a normal user choice, not a failure to retry silently.
  • Open the authorisation URL with CustomTabsIntent from the AndroidX Browser library. Custom Tabs run in the user’s browser and share its session.
  • Receive the redirect through an Android App Link: an HTTPS URL with an intent filter marked android:autoVerify="true" and a Digital Asset Links file on your domain. A verified link cannot be claimed by another app, unlike a custom scheme.
  • If the device has no browser that supports Custom Tabs, fall back to the default browser, not a web view.
  • Handle the redirect in a single activity and resume the pending transaction, even if the original activity was destroyed while the browser was in front.

The sign-in sequence

  1. Register each platform’s redirect URI with the provider. RFC 9700 requires exact string matching of redirect URIs, apart from the port of a loopback redirect.
  2. Generate a PKCE code verifier and an S256 challenge (RFC 7636), plus a random state and an OpenID Connect nonce. Store them with the pending transaction and the action the user was trying to do.
  3. Open the system browser with the authorisation request. Add login_hint, prompt or max_age when the workflow needs a specific account or a fresh login.
  4. On the callback, reject it unless state matches a pending transaction that has not completed. A callback must never complete a different action.
  5. Exchange the code with the PKCE verifier. Validate the ID token: issuer, audience, expiry and nonce.
  6. Store tokens in the Keychain on iOS, or encrypted with an Android Keystore key on Android. Keep them out of logs, URLs and crash reports.
  7. Call your APIs with an access token issued for that API. The resource server checks the audience and the scopes for each request.

An ID token says who signed in. It is not permission to call your API. A token issued for another service must be refused by yours.

Ownership of decisions

OwnerDecidesEvidence it works
Identity teamIssuer, client registration, sign-in policy, step-up rulesInvalid redirects and clients are refused
Mobile teamTransactions, callbacks, token storage, lifecyclePer-platform login, renewal and switch tests
API teamAudience, scopes, per-operation authorisationDenied requests for each sensitive operation
Security operationsSignal interpretation and escalationReviewed response record and recovery path

Otukan protects the authentication flow inside the app against hooking and runtime manipulation, and provides runtime threat signals your response policy can use. It does not replace the identity provider. Decide whether a signal affects the current action, the local session or a server-side step-up, and confirm each signal’s Android and iOS scope separately.

Test the full session

  • Login: success, cancellation, denied consent, an unknown state and a replayed callback.
  • Renewal: expired access tokens, parallel requests during refresh and a lost refresh response. RFC 9700 requires refresh tokens for public clients to be sender-constrained or rotated; test that a used refresh token is refused.
  • Logout: clear local tokens, then end the provider session with OpenID Connect RP-initiated logout where the provider supports it. Decide whether “sign out” means this app or every app using the provider session.
  • Revocation: revoke the grant at the provider (RFC 7009 where supported) and confirm that renewal and API calls fail.
  • Account switching: switch accounts, confirm cached tokens and data from the first account are gone, and confirm the new identity before resuming any sensitive action.
  • Interruption: kill the app while the browser is open, return, and confirm the pending transaction resumes or fails cleanly.

Repeat the critical journey after a fresh install and an update, on both platforms. Shared Flutter or React Native code does not remove either platform’s tests.

  • Redirect URIs match exactly, and unverified links cannot capture the callback.
  • PKCE, state and nonce are checked on every sign-in.
  • APIs refuse tokens with the wrong audience.
  • Used refresh tokens are refused.
  • Logout and revocation have defined, tested scopes.
  • Test evidence uses test accounts and non-secret references, never raw tokens.

The wider integration boundary is covered on integration overview.

Further reading

Common questions

  • Can I show the identity provider's sign-in page in a web view?

    No. RFC 8252 requires native apps to use an external user agent for OAuth, not an embedded web view. Use ASWebAuthenticationSession on iOS and Custom Tabs on Android. The system browser keeps the user's password out of your process and shares the provider's session, which makes single sign-on work across apps.

  • Why does prefersEphemeralWebBrowserSession affect SSO on iOS?

    Setting prefersEphemeralWebBrowserSession to true stops cookie sharing with Safari, so the user signs in every time. Keep it false when you want single sign-on. True suits shared devices or a deliberate “sign in as someone else”, and a canceledLogin error should be treated as a normal user choice.

  • Why use a verified App Link for the Android callback?

    Because a verified link is tied to your domain, unlike a custom scheme that another app can claim. Use an HTTPS URL with an intent filter marked android:autoVerify="true" and a Digital Asset Links file on your domain. If no browser supports Custom Tabs, fall back to the default browser, not a web view.

  • Is a valid ID token enough to call my API?

    No. An ID token says who signed in; it is not permission to call your API. Call your APIs with an access token issued for that API, and have the resource server check the audience and scopes for each request. A token issued for another service must be refused by yours.

  • Does Otukan replace the identity provider?

    No. Otukan protects the authentication flow inside the app against hooking and runtime manipulation, and provides runtime threat signals your response policy can use. You decide whether a signal affects the current action, the local session or a server-side step-up, and confirm each signal's Android and iOS scope separately.

Review your mobile identity boundaries

Tell us your platforms, framework, identity provider setup and the custom workflow you need. We do not ask for binaries, signing keys, credentials or production data.

Or request a technical demo