Ship your own Authenticator app
Run a tenant on a mobile app you build and distribute yourself — what to set up with Google and Apple, what to change in the build, and how to register the app's identity, push credentials and app-link host on the console's Mobile App page.
By default a tenant's users install the SenseCrypt Authenticator from the App Store and Google Play, and SenseCrypt verifies that app. A tenant can instead run its own app: a build you distribute under your own developer accounts, with your own name, icon, package and bundle identifiers, signing keys and push project. There are two ways to get such an app:
- Rebuild the Authenticator. SenseCrypt provides the Authenticator source together with the Mobile SDK artifacts and a licence. You change the identifiers and configuration described below and ship it from your own accounts.
- Embed the SDK in your own app. Your app drives the SDK flows itself. The page Running as the tenant's app lists what the host app must declare.
Either way the server has to know the app it is talking to. That is what the console's Mobile App page registers, and this guide walks through it.
Nothing SenseCrypt-specific may be reused. The identifiers, signing certificates, Firebase project and store listings must all be your own. The server verifies your app against what you register here: app attestation checks the identity, push notifications go through your push project, and sign-in links are issued on your app-link host. A rebuilt app only works once both the build and the console page say the same thing.
What the registration controls
These server behaviours read the tenant's mobile-app registration:
| Surface | What changes for a tenant on its own app |
|---|---|
| App attestation | Play Integrity and App Attest results are checked against your package name, signing certificates, team ID and bundle ID, in the mode you choose (off, audit or enforce). |
| Push notifications | CIBA sign-in and Face PKI requests are pushed through your Firebase project, using the service-account key you upload, and the push text names your app. iOS push rides Firebase Cloud Messaging too, so there is no separate APNs credential to upload. |
| App links | /.well-known/assetlinks.json and /.well-known/apple-app-site-association on your app-link host describe your app, so the OS opens it for sign-in links. |
| Sign-in links | QR codes, emails and push notifications for this tenant are issued on your app-link host, and tap-to-open links use your custom URL scheme. |
| Passkey origins | The Android app-signing fingerprints you register become the accepted android:apk-key-hash WebAuthn origins for the tenant. |
| Sign-in pages | "Open the app" copy and the store buttons name and link your app. |
| End-user emails and SMS | PIN, CIBA-approval and device-added emails call the authenticator by your app name and are branded with the application's Branding rather than SenseCrypt's; the SMS verification code names your app too. |
A tenant with no registration, or one that keeps App source on SenseCrypt Authenticator, behaves exactly as before: the stock app, the deployment's own identity and push project. A stored custom configuration is inactive, not blended, while the source is the stock app, so you can switch back and forth without re-entering anything.
Before you start: accounts with lead time
| Account | What you need from it |
|---|---|
| Google Cloud and Firebase, one project | Register your Android and iOS apps in Firebase with the package name and bundle ID your builds use. Upload your APNs authentication key to Firebase so iOS push rides FCM. Link the same project in Play Console so Play Integrity can vouch for your app. Create a service account with the Play Integrity API and Firebase Cloud Messaging API roles and export its JSON key. You upload that key on the Mobile App page; it never goes into the source tree. |
| Apple Developer | Your team ID, and two App IDs: the app with the AutoFill Credential Provider, App Attest, Associated Domains, Keychain Sharing and Push Notifications capabilities; the credential-provider extension with AutoFill Credential Provider, App Attest and Keychain Sharing; plus one App Group shared by both. |
| Google Play | A listing. An internal test track is enough to start, but it must exist: Play Integrity's Play-recognised verdict requires Play distribution. |
Step 1: build the app under your own identity
If you are rebuilding the Authenticator source, these are the values to change. If you embed the SDK, the same items apply to your own project; see Running as the tenant's app.
Android
- The application ID (
applicationIdinapp/build.gradle.kts), for examplecom.example.authenticator. Leavenamespaceas it is unless you also move the Kotlin sources: the manifest resolves its activity and service classes against it. - The Play Integrity cloud project number of your Google Cloud project (
0turns Play Integrity off in the SDK; the server then sees an unattested request, which passes only while the tenant's attestation mode isofforaudit). - The production SenseCrypt base URL.
- The app-link host: the host you will register on the console (see App links below). It is the
app_link_hoststring resource; the manifest'sautoVerifyApp Links intent filter reads it and must claimhttps://<host>/v1/link/*. google-services.jsonfrom your Firebase project.- Release signing with your own keystore. The SHA-256 of the signing certificate is what you register on the console. If Play App Signing manages your release key, use the certificate shown under App signing in Play Console.
- Your custom URL scheme in the deep-link intent filter. Keeping
sensecryptcollides with the official app on a phone that has both.
iOS
- Bundle IDs for the app and the credential-provider extension, and your development team.
- The SenseCrypt base URL (
BACKEND_BASE_URL) in both the app's and the extension'sInfo.plist, byte-identical including the trailing slash: the extension treats any difference as a portal switch and wipes the enrolled keys. The app-link host goes in the app'sInfo.plist(APP_LINK_HOST) and, decisively, in the associated-domains entitlement below. - The associated-domains entitlement
applinks:<your app-link host>, on the app target. - Your own Keychain Sharing group and App Group identifiers, in both targets.
GoogleService-Info.plistfrom your Firebase project.- Your custom URL scheme in URL Types.
- The App Attest environment:
productionfor store builds;developmentonly while testing development-signed builds, paired with the console's Require production App Attest environment switch turned off.
SDK and licence
The SDK binaries and your mobile.lic are delivered to you directly. The licence goes into the app's assets or resources, never into the credential-provider extension. See Install.
Step 2: register the app on the console
Open Mobile App in the tenant's console. Reading the page needs the mobile_app:read capability; saving needs mobile_app:update. Everything below is also available as GET and PATCH /v1/admin/mobile-app, scoped to the tenant of the host you call.
App source
Choose Your own mobile app. The switch asks for confirmation because it takes effect immediately:
- Push notifications stop reaching devices enrolled through the SenseCrypt Authenticator, because they belong to a different Firebase project.
- Sign-in links and store buttons start pointing at your app.
- Attestation starts in Audit mode unless the same
PATCHnames a mode or the stored configuration already holds Audit or Enforce; the console's switch sends neither, so from the console it always starts in Audit. One click never takes a tenant from the deployment's enforced floor to no attestation.
Users then install your app and enrol in it. Switching back to SenseCrypt Authenticator deactivates your configuration without deleting it; devices enrolled through your app stop receiving pushes in turn.
App name and custom URL scheme
- App name: what sign-in pages and emails call the app ("Open Acme Authenticator", "Acme Authenticator is your verification code"). Up to 60 characters: letters, digits, spaces and
. , ( ) -. - Custom URL scheme: the scheme your app claims for tap-to-open links of the form
yourscheme://link/…. Lowercase, 2 to 30 characters, starting with a letter.http,https,javascript,data,file,intentandsensecryptare refused. Set the same value in the Android intent filter and the iOS URL Types.
Leave the scheme empty and no tap-to-open link is minted for your app. Sign-in pages fall back to the store buttons, and OS app links still open the app from a universal link. A link is never minted with the stock sensecrypt scheme for a custom app, because it would open an app the configuration no longer matches.
App attestation
The device payload fetch, the passkey face report and App Attest enrolment are checked against Google Play Integrity on Android and Apple App Attest on iOS, using the identity you register below.
| Mode | Behaviour |
|---|---|
off | No attestation checks run. For an app that is not yet distributed through the stores. |
audit | Checks run and the verdict is logged, but no request is blocked. Use it to confirm the setup before enforcing. |
enforce | A request that fails attestation is rejected. The user sees a generic error; the reason goes only to the server log. |
Attestation is per tenant. Nothing on an individual application can weaken it, so a relying party never gets an exemption. A custom app whose identity fields are still empty fails closed under enforce: it is never accepted on the strength of the stock app's identity. audit exists so that this phase is observable without locking anyone out.
Android
- Package name: the application ID of your build.
- Signing-certificate SHA-256 fingerprints: one per certificate, up to 10, as colon-separated hex (
AA:BB:…). Include the debug certificate while testing and remove it for production. - Required verdict: Device integrity accepts
MEETS_DEVICE_INTEGRITYor stronger. Strong integrity additionally requires hardware-backed proof and recent security updates on the device. - Play Store URL: where sign-in pages send Android users who do not have the app. Empty derives the public listing from the package name, which only resolves once the app is published. Use the internal-test opt-in link until then.
iOS
- Team ID (10 characters) and Bundle ID. Together they form the App ID your attestations are checked against.
- Require production App Attest environment: on by default. Development-signed builds attest against Apple's sandbox; turn this off only while testing pre-release builds.
- App Store URL: where sign-in pages send iPhone users who do not have the app. Empty hides the App Store button.
Google service account
Upload the JSON key of the service account from Before you start. One key serves both Play Integrity (decoding Android attestation tokens) and push (FCM in your Firebase project). The key is stored sealed and is write-only: the page shows its client email and a fingerprint prefix, the API reports the client email, upload time and full fingerprint, and neither ever returns the key. You can replace or remove it.
Two rules to know:
- Replacing the key with one from a different Google Cloud project asks for confirmation. Push and Play Integrity only work through the project your app build is registered in, so a key from another project would break both.
- No key means no push. Pushes for a custom-app tenant are never sent through SenseCrypt's own project. Until you upload a key, CIBA and Face PKI requests for this tenant reach users by email only.
App links
Sign-in links in QR codes, emails and pushes are issued on the app-link host, and that host publishes the two well-known documents the operating systems read:
https://<host>/.well-known/apple-app-site-associationlists<team id>.<bundle id>for the path/v1/link/*.https://<host>/.well-known/assetlinks.jsonlists your package name and signing fingerprints with thehandle_all_urlsandget_login_credsrelations.
Both are served per host from what you registered, so there is nothing to upload. Each returns 404 until the matching identity fields are set.
Leave the picker on This tenant's host and links use the tenant's own hostname, https://<slug>.sensecrypt.com. Pin exactly that host in the app's associated domains and App Links filter. Pick a verified custom domain only when the app is built for that domain, for example one app shared by several tenants on one domain. The host must be one of the tenant's own hosts; anything else is refused with the 422 rule app_link_host.not_tenant_host.
A custom app's links are never issued on SenseCrypt's shared app host. That host publishes the stock app's identity, so a link there would leave the OS with no verified handler for your app.
Step 3: roll out
-
Save with attestation in Audit. Install your build, enrol a user, sign in. The server log records each attestation verdict without blocking.
-
Confirm the two well-known documents on your app-link host return your identity:
curl https://<host>/.well-known/assetlinks.json curl https://<host>/.well-known/apple-app-site-association -
Confirm a push arrives: start a CIBA request for an enrolled user.
-
Switch to Enforce once real traffic passes in audit.
Simulators and emulators cannot attest. Under enforce their device flows fail with attestation_failed; under off or audit they work.
Under the hood: the Mobile App endpoint
# authenticated with the mobile_app:read capability, on the tenant's host
curl https://{slug}.sensecrypt.com/v1/admin/mobile-app# authenticated with the mobile_app:update capability
curl -X PATCH https://{slug}.sensecrypt.com/v1/admin/mobile-app \
-H 'Content-Type: application/json' \
-d '{
"app_source": "custom",
"confirm_app_switch": true,
"app_name": "Acme Authenticator",
"custom_scheme": "acme",
"mode": "audit",
"android_package_name": "com.example.authenticator",
"android_cert_sha256": ["AA:BB:…"],
"android_verdict_level": "device",
"ios_team_id": "ABCDE12345",
"ios_bundle_id": "com.example.authenticator",
"google_service_account_json": "{…}"
}'PATCH is a partial update, and every rule is checked before the first write, so a refused request leaves the configuration untouched. The read model adds eligible_app_link_hosts (what the picker offers), the service-account metadata, and the deployment's store links for the stock app.
| Status | error or rule code | Meaning |
|---|---|---|
409 | mobile_app_switch_confirmation_required | app_source changed without confirm_app_switch: true. |
409 | mobile_app_project_change_confirmation_required | The new service-account key belongs to a different Google Cloud project; resend with confirm_project_change: true. |
422 | app_link_host.not_tenant_host | The host is not the tenant's slug host or one of its verified domains. |
422 | field rules | Malformed app name, package name, fingerprint, team ID, bundle ID, scheme, store URL, app-link hostname or key, in the validation envelope. |
Every save that changes something is recorded in the audit log as mobile_app.updated, naming the fields that changed and the key fingerprint, never the key.
See the API reference for GET /v1/admin/mobile-app and PATCH /v1/admin/mobile-app.
Related
- Running as the tenant's app — what an SDK host app declares to match this registration.
- Mobile (Authenticator) — the stock app this replaces.
- Custom domains — verified domains eligible as the app-link host.
- Security — the attestation model.
- Troubleshooting — diagnosing
attestation_failed.
Customize the sign-in experience
Brand the SenseCrypt sign-in page per OIDC application — organization name, primary and accent colors, and a logo you upload — so users see your identity, not a generic prompt.
Refresh tokens and sessions
Keep users signed in with SenseCrypt refresh tokens — requesting offline_access, handling rotation and reuse detection, revoking sessions, and implementing logout against the OP browser session.