ZevAuth
Sign up

Concepts

Mobile apps

Sign-in from iOS and Android apps, with universal links, app links and PKCE.

Native apps sign in the way the OAuth standard for native apps (RFC 8252) recommends. The app opens ZevAuth in the phone’s own browser sign-in sheet, and the person comes back to the app with a one-time code that only that app can redeem.

That sheet is ASWebAuthenticationSession on iOS and Chrome Custom Tabs on Android. Never use an embedded WebView: the person can’t see the address bar, and the app could read what they type.

How a sign-in works

  1. Your app creates a random code verifier and a state, and keeps both in memory.
  2. It opens /v1/auth/zevid/start in the system sign-in sheet with:
    • redirect_uri: your app’s link;
    • state;
    • code_challenge: the SHA-256 of the verifier, base64url-encoded;
    • code_challenge_method=S256.
  3. The person signs in. If they’re already signed in to ZevID in Safari or Chrome, they’re recognised.
  4. ZevAuth sends them back to your link with code and your state. Check the state matches what you sent.
  5. Your app exchanges the code at POST /v1/auth/handoff/exchange with codeVerifier and the same redirectUri, and receives a session.
POST /v1/auth/handoff/exchange
Authorization: Bearer pk_live_…

{ "code": "hoc_…", "codeVerifier": "…", "redirectUri": "https://acme.com/app/callback" }

The code works once and expires after 60 seconds. If someone else intercepts the redirect, the code is still useless to them without your app’s verifier.

Errors come back to the same link with error, error_description and your state. Tokens never appear in a URL.

The JavaScript SDK does all of this in a browser too: client.signIn.withZevId({ pkce: true }) binds the sign-in to the tab that started it.

Where a sign-in may return

LinkDevelopmentProduction
Universal link (iOS) or app link (Android) on your domainAllowedAllowed once your domain’s association file lists your app
Custom URL scheme, such as com.acme.app:/callbackAllowed, with PKCENot allowed, unless ZevAuth support enables it for your app
http://localhostAllowedNot allowed

Any app on a phone can claim a custom scheme, so a scheme proves nothing about which app opens it. A universal link or app link does, because the phone checks your domain’s association file before opening your app.

Registering your app

  1. Verify your project domain under Project settings → Domains & callbacks.

  2. Register the link itself as a callback, for example https://acme.com/app/callback.

  3. Open Project settings → Mobile apps and choose Add app:

    • iOS: the bundle ID (in Xcode: your target → General → Bundle Identifier) and your Apple team ID (your Apple developer account → Membership details).
    • Android: the package name (the applicationId in build.gradle) and the SHA-256 fingerprint of each signing certificate. If you publish with Play App Signing, add the app signing key’s fingerprint from Play Console → your app → Test and release → App integrity → App signing, because Google signs what people install. For your own key or a development build, run keytool -list -v -keystore your.keystore -alias your-alias. Paste fingerprints in any format; the console fixes colons and case.
  4. Publish the association file on the link domain. The Association files panel on the same page generates it from your apps, ready to copy:

    • iOS: https://acme.com/.well-known/apple-app-site-association, served as application/json with no file extension.
    • Android: https://acme.com/.well-known/assetlinks.json.

    Serve them directly, without a redirect: phones don’t follow one.

  5. Press Check again. The app shows Link verified once the file lists it.

We read the file when you add the app, when you press Check again, and once a day after that, so a file that changes or disappears is noticed. If the app isn’t listed, the console says exactly what was missing.

Limits

Per environment
iOS apps10
Android apps10
Signing certificates per Android app5
Apple teams1: all iOS apps in an environment belong to the same team

What people see when they sign in with ZevID

Once an app’s link is verified, “Sign in with ZevID” knows about it. On a phone that matches a verified app, the ZevID consent screen names your app with the device, such as “Acme on iPhone” or “Acme on Android”, so people know exactly where they are signing in. Until then it shows your app’s name alone. Sign-in works either way.

The Shown in Sign in with ZevID status on the Mobile apps page says whether your verified apps are up to date there. If an identifier is ever not accepted, the page says which one and why. We retry automatically; contact ZevAuth support if it persists.

How long a ZevID session lasts

A session that started with “Sign in with ZevID” needs a fresh ZevID sign-in after at most 90 days, or after 30 days without being used, on every platform. You can shorten both for your environment under Project settings → Mobile apps → Sign in with ZevID sessions. You can’t make them longer. These are today’s limits; if they are ever tightened, sessions follow the new limits automatically.

Each new ZevID sign-in also asks ZevID for a sign-in no older than that window, so a session never starts already past it.

When a window passes, refreshing the session fails with reauthentication_required. The SDK signs the person out and shows “For your security, please sign in with ZevID again”.

A ZevID session also ends straight away, on the web and in your apps, when the person:

  • removes your app from their ZevID account;
  • signs out of the ZevID session your app’s sign-in came from (only the sessions made from it end; their other devices stay signed in);
  • signs out of ZevID everywhere, changes their ZevID password, or has their ZevID account closed (every ZevID session in every app ends).

Sessions from “Sign in with ZevID” are also checked against ZevID at least once a day, so a sign-out is honoured even if a notification was missed.

Errors

StatusCodeMeans
400pkce_method_unsupportedcode_challenge_method must be S256.
400invalid_code_challengeThe challenge must be the base64url SHA-256 of the verifier.
400pkce_requiredA custom URL scheme needs PKCE.
400invalid_statestate must be 1 to 512 characters.
403native_redirect_not_verifiedIn production, the link’s domain doesn’t list any of your registered apps yet.
403custom_scheme_not_allowedCustom URL schemes are for development unless ZevAuth support has enabled them for your app.
400pkce_verification_failedThe verifier doesn’t match the challenge the sign-in started with.
400redirect_uri_requiredA PKCE sign-in must send the same redirectUri to the exchange.
400redirect_uri_mismatchThe code was issued for a different redirectUri.
401reauthentication_requiredA ZevID session needs a fresh ZevID sign-in.
400native_app_limitThe environment already has 10 apps for that platform, or the app has more than 5 certificates.
400native_app_team_mismatchYour other iOS apps belong to a different Apple team.

Updated at, Sunday, November 1, 2026