Mobile SDKs
Native helpers for Android, iOS, Flutter and React Native. The system browser, PKCE, verified HTTPS callbacks, and no client secret in the app.
The packages
The mobile SDK repository holds five packages. Every adapter uses the hosted authorization experience, the authorization code flow with PKCE S256, state validation, and the operating system's browser authentication surface.
| Package | Runtime | Browser surface |
|---|---|---|
packages/android-kotlin | Android / Kotlin | Android Custom Tabs |
packages/ios-swift | iOS / Swift | ASWebAuthenticationSession |
packages/flutter | Flutter on Android and iOS | flutter_appauth |
packages/react-native | React Native | react-native-app-auth |
packages/core | Node reference and contract tests | OAuth helper primitives |
Four rules the SDKs enforce
The system browser, alwaysASWebAuthenticationSessionon iOS, Custom Tabs on Android. Never an embedded WebView: a WebView lets the host application observe everything typed into it, the person cannot tell your screen from an imitation, and passkeys will not work.A public client- A mobile application never carries a client secret — a secret inside a binary is a published secret. The app authenticates at the token endpoint with PKCE alone (
none), or exchanges the code from your backend. Verified HTTPS callbacks- Universal Links on iOS, App Links on Android, registered exactly in the developer console. A custom URL scheme can be claimed by any application on the device, and UnifyID refuses untrusted custom schemes.
One transaction, stored encrypted- State, nonce and the PKCE verifier survive the app being backgrounded and nothing else: Keychain or Android Keystore, bound to the transaction, with a short expiry. A returning callback that finds no transaction fails closed.
The flow in each SDK
Android / Kotlin
val client = UnifyIDClient(
UnifyIDConfig(
clientId = "<your client id>",
redirectUri = "https://app.example/oauth/callback",
scopes = listOf("openid", "identity_verified"),
)
)
val transaction = client.createTransaction()
client.authorize(activity, transaction)
// in onNewIntent:
val code = client.complete(callbackIntent, transaction)iOS / Swift
let configuration = UnifyIDConfiguration(
clientId: "<your client id>",
redirectURI: URL(string: "https://app.example/oauth/callback")!,
scopes: ["openid", "identity_verified"]
)
let client = UnifyIDClient(configuration: configuration)
let result = try await client.authorize(anchor: anchor)The SDK generates PKCE, opens the system session and validates the callback. Then exchange the code with the original verifier — from the app as a public client, or from your backend.
apple-app-site-association
{ "applinks": { "details": [
{ "appIDs": ["TEAMID.com.example.app"],
"components": [{ "/": "/oauth/callback" }] }
]}}Before you ship
- Test cancellation, a restart mid-flow, an offline return, an expired transaction and an unverified callback, and confirm none of them produces a session.
- Confirm the production callback domain is verified in the shipped build. A verification file that works in staging and not in production is a launch-day failure.
- Do not log authorization codes, access tokens or identity claims. Send crash diagnostics without personal identity information.
- Handle revoked consent explicitly: the next userinfo call answers
401, and the app needs somewhere to send the person.
What this does not do
- The SDKs do not change the sign-in. It is the same authorize, consent, code, token, userinfo as the web; the SDK opens the browser and catches the callback.
- The drop-in component is not for mobile.
<unifyid-continue>deliberately uses a full redirect on narrow screens; a native app uses these SDKs instead.
Verified against API commit 234dba22 on 2026-10-18. The routes, scopes, checklist, events and limits on these pages are read from that export, not typed.