Cadres IT Operations & Infrastructure
Sheet POR-09 Rev 2026.08
Start Trial

Sheet POR-09 — Identity Manual

Mobile Authenticator & Recovery

Authenticator enrollment, recovery posture, and the human workflow around stronger sign-in assurance.

Audience: Identity operators and support leads Focus: Authenticator lifecycle and recovery posture

Scope

Strong authentication only works when enrollment, recovery, and support workflows are clear to the people who administer them. This guide keeps the operator-facing identity assurance model and removes the low-level implementation detail used by the internal product manual.

Current Scope

Portal now ships two first-party native app projects:

  1. ios/PortalAuthenticator
  2. android/PortalAuthenticator

Both apps now truthfully ship the same narrow mobile slice:

  1. local manual TOTP entry, edit, delete, and QR import
  2. local rolling-code display and on-device vault persistence
  3. optional local biometric or device-auth gate over already-stored app state
  4. fixed Cadres development Portal web and API endpoints
  5. generate and keep a device-bound Ed25519 key on the device
  6. complete the browser-issued linked-device challenge
  7. inventory linked devices
  8. revoke linked devices
  9. load a narrow governance approvals inbox and detail
  10. approve or deny governance access requests through the linked-device action-challenge flow
  11. (Android only, and iOS pre-Public-Vault builds) report public-vault readiness and blocker state as groundwork only

Portal’s backend now also ships the next mobile-safe workflow contract:

  1. governance requester catalog, submit, history, and detail facades
  2. governance approval and access-review read facades
  3. support-access read facades
  4. device-authenticated action challenges for governance, access-review, and support-access decisions

The current native apps consume only the governance approval subset of those workflow routes. Governance requester UI, access-review UI, and support-access UI remain non-shipped mobile scope.

Portal also now ships the mobile push delivery channel (IU-012-S01 / G-02):

  1. APNs (iOS) / FCM (Android) credential registration on the linked device
  2. transactional outbox + MobilePushDispatchWorker that drains pending pushes
  3. number-matching code displayed in the push prompt and bound into the signed challenge payload — the user must compare the code with the browser and complete the in-app signed proof; the notification alone cannot approve, deny, or revoke

  4. provider-disabled and credential-missing states fall back to the existing pull inbox without losing the underlying challenge

What this wave still does not ship:

  1. live APNs/FCM delivery without operator-configured Apple/Google credentials (the code path is in place; live evidence requires APNs key and FCM service account in the deployment environment)

  2. (Android) public-vault account bootstrap, sync, backup, or recovery — iOS ships this; see Public Vault (Personal Backup) — iOS

  3. governance requester UI, access-review UI, or support-access UI

  4. mobile session review or revoke

Public Vault (Personal Backup) — iOS

ios/PortalAuthenticator’s free personal Authenticator tab (Settings → Vault Backup & Recovery) ships a real, end-to-end-encrypted backup account — deliberately a separate identity system from enterprise Portal accounts, so it can offer genuine self-service deletion (Apple 5.1.1(v)) without depending on tenant/employer management:

  1. sign up with email + a passkey (no employer or Portal tenant required)
  2. a mandatory recovery key, shown once with an explicit “I saved this” confirmation, as the backstop if every enrolled device is lost

  3. live device-to-device enrollment — a new device shows a QR code, an already-trusted device scans and approves it, wrapping the vault key for the new device’s public key over ECDH (no plaintext key ever touches the server)

  4. a “Your codes aren’t backed up” banner on the Authenticator tab that encrypts and pushes local entries with one tap

  5. self-service account deletion, separate from the existing enterprise “unlink this authenticator” action

The backend never sees plaintext vault keys or plaintext authenticator secrets — only opaque, client-encrypted blobs. See ceremony and RLS isolation model. Not yet shipped: pulling synced ciphertext back into local entries on a second device (today’s sync pushes local entries out; the read-side merge is still open), and Android parity.

Pair A Device

Portal ships a native browser pairing UI at Profile → Mobile Devices → Pair Mobile Device (the relevant workflow). No browser DevTools or manual API calls are required.

Workflow:

  1. Sign in to Portal in your browser.
  2. Navigate to ProfileMobile Devices → click Pair Mobile Device.
  3. A QR code, Pairing ID, and Short code appear (all valid for 5 minutes). The QR carries a high-entropy one-time secret in addition to the visible fields; manual entry needs both the Pairing ID and the Short code so that wrong attempts can be attributed to your pairing row.
  4. Open either native app: - iOS: run ios/PortalAuthenticator from Xcode on an iPhone or simulator - Android: open android/PortalAuthenticator in Android Studio and run it on an Android 13+ device or emulator, or use the checked-in Gradle wrapper from the repo root
  5. In the app, tap Link Device. If the current shipped native build does not yet implement the QR-scan or manual Pairing ID + Short code screens, complete pairing instead through Sign In With Portal in the app (the browser-issued link-challenge path documented in Sign In To Manage Linked Devices below). The browser QR/short-code path is the supported contract for any new third-party mobile authenticator client.

The pairing flow emits:

  • Audit action mfa.mobile.pairing_initiated when the QR code is generated.
  • Audit action mfa.mobile.device_paired when the device redeems the code.
  • Audit action mfa.mobile.pairing_redeem_locked when 5 wrong verifier submissions lock the pairing row (IU-012-S03 / R-0201).

Security notes:

  • The QR payload carries a 32-byte high-entropy secret that is infeasible to brute-force within the 5-minute TTL; only the SHA-256 hash is stored server-side.
  • Manual entry is protected by a per-pairing lockout: after 5 wrong attempts against your Pairing ID the row is invalidated and you must start a new pairing session. An IP-rotating attacker cannot exceed 5 guesses per outstanding pairing.

Active-device cap and dormant expiry (IU-012-S04 / mobile-authenticator G-03):

  • Each user can keep up to 5 active linked mobile devices by default (). Pairing while at the cap fails Profile → Mobile Devices and pair again.

  • A pairing that hits the cap leaves the QR/short-code session in challenged state for the rest of the 5-minute window — after revoking an old device you can complete phase 2 without restarting the QR flow.

  • Active devices are auto-revoked after 180 dormant days (default; ). A device’s idle clock is its last_seen_at (the last signed mobile action); QR-paired devices that never sign anything age from pairing time.

  • Auto-revoked devices stay visible as revoked evidence in Profile → Mobile Devices and must be paired again with a fresh key if they come back into use. Auto-revoke cancels any pending action challenges on the device and is audited as auth.mobile_device_auto_revoked.

Sign In To Manage Linked Devices

The app now uses Portal’s real first-party native auth path:

  1. open the Devices tab
  2. review the fixed Cadres development Portal web and API endpoints shown in the app
  3. tap Sign In With Portal
  4. complete the browser-based Portal login, MFA, tenant selection, or consent flow inside the native browser handoff: - iOS uses ASWebAuthenticationSession - Android uses Custom Tabs plus the app’s deep-link callback
  5. Portal redirects back to the app’s registered callback URL

After sign-in, the Devices tab can:

  1. list the authenticated user’s linked devices for the selected tenant context

Operational Notes

  1. The iOS app stores the Ed25519 private key in Keychain with device-only accessibility. It does not claim Secure Enclave backing in this tranche.
  2. The Android app stores a software-generated Ed25519 private key encrypted at rest with an Android Keystore wrapping key. It does not claim hardware-backed or StrongBox-backed Ed25519 in this tranche.
  3. The native apps use Portal’s real browser-based sign-in, not embedded fake login forms.
  4. The backend accepts the native token path only on the explicit linked-device-management and mobile-workflow facade routes. Other browser-session-sensitive self-service routes still rely on the existing portal_session contract.
  5. High-value mobile workflow decisions now require both the canonical Portal identity checks and a one-time linked-device action challenge signed by the exact active device key.
  6. Both apps now ship local biometric or device-auth gating only over already-stored app state. That local unlock is not a second auth model and not a permission bypass.

Verified Device Attestation

Pairing now requires platform attestation evidence in addition to the Ed25519 device-key proof. iOS uses Apple App Attest. Android uses Play Integrity or Android Key Attestation. Existing linked devices that show attestation_status = unverified must be re-paired before they can approve high-value mobile workflow challenges.

Operators can inspect for attestation_status, attestation_provider, attestation_assurance_level, attestation_verified_at, and sanitized attestation_verification_json. Raw App Attest objects, Play Integrity tokens, X.509 chains, and private-key material are not exposed.

If a mobile action challenge returns linked_device_attestation_required, revoke and re-pair the device from the app. Local development can use the debug attestation provider only when both DEBUG=true and MOBILE_ATTESTATION_DEBUG_BYPASS=true; production rejects that bypass at startup.

Enable Push Notifications

  1. Sign in to the native app and complete the linked-device pairing as described above.

  2. The app prompts for notification permission immediately after sign-in (iOS user-notifications consent; Android 13+ ).

  3. If you grant permission the app calls APNs/FCM token. The server stores it encrypted at rest and exposes the SHA-256 fingerprint on so operators can confirm delivery readiness without seeing the raw token.

  4. The next mobile workflow action you initiate from the browser fires a push containing a 3-digit confirmation code.

  5. Open the app, verify the code matches the browser, unlock locally (biometric or device passcode), and approve or deny — the in-app Ed25519 signature is what authorizes the workflow action. A lock-screen tap alone cannot approve.

  6. To stop receiving pushes for this device, tap Disable Push in the app (DELETE the credential) or revoke the device. Revoking a device also invalidates its push credential and cancels any pending deliveries.