Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.synheart.ai/llms.txt

Use this file to discover all available pages before exploring further.

The mobile auth SDK runs an explicit state machine per (app_id, device). Transitions are validated; invalid transitions throw InvalidStateTransition. The state is persisted alongside device_id so the SDK can resume across app restarts.

States

Dart enumWire stringMeaning
unregistered"unregistered"No device identity exists for this app_id.
challengeReceived"challengeReceived"Challenge fetched from server, awaiting key generation.
keyReady"keyReady"Hardware keypair generated, awaiting attestation.
registering"registering"Attestation proof generated, register call in flight.
registered"registered"Device identity established, ready to sign.
keyInvalid"keyInvalid"OS-detected key invalidation; requires re-registration.

Allowed transitions

unregistered       → challengeReceived
challengeReceived  → keyReady
keyReady           → registering
registering        → registered
registering        → unregistered      (failure → reset → retry)
registered         → registering       (key rotation)
registered         → keyInvalid        (OS detected key gone)
keyInvalid         → unregistered      (wipe and start fresh)
Any other transition throws InvalidStateTransition.
                 ┌─────────────┐
                 │ unregistered│ ◄─────────────┐
                 └─────┬───────┘               │
                       │ fetch challenge       │ wipe
                       ▼                       │
            ┌──────────────────┐               │
            │ challengeReceived│               │
            └─────┬────────────┘               │
                  │ generate key               │
                  ▼                            │
            ┌──────────┐                       │
            │ keyReady │                       │
            └────┬─────┘                       │
                 │ attest + register           │
                 ▼                             │
       ┌──────────────────┐                    │
       │   registering    │ ────fail──────────►│
       └─────┬────────────┘                    │
             │ success                         │
             ▼                                 │
       ┌────────────┐  rotate ┌─────────────┐  │
       │ registered │────────►│ registering │──┘
       └─────┬──────┘         └─────────────┘
             │ OS invalidation

       ┌────────────┐  wipe   ┌─────────────┐
       │ keyInvalid │────────►│unregistered │
       └────────────┘         └─────────────┘

Mapping to public API

API callTriggered transition
configure(baseUrl)(no state change)
isRegistered(appId)(no state change)
registerDevice(appId) step 1unregistered → challengeReceived
key generationchallengeReceived → keyReady
attestation + register callkeyReady → registering
register successregistering → registered
register failureregistering → unregistered
rotateKey(appId)registered → registering → registered
OS detected key invalidationregistered → keyInvalid
resetDeviceIdentity(appId)any state → unregistered

Persistence

The current state is stored alongside device_id, key alias, platform, registered_at, key_rotated_at, and clock_offset_ms.
PlatformStorage
iOSKeychain (state + metadata) + Secure Enclave (key reference).
AndroidEncryptedSharedPreferences (state + metadata) + Keystore (key alias).
Private key bytes are never persisted in app-managed storage — they live only in hardware.

Key invalidation detection (RFC §11.1)

The SDK transitions registered → keyInvalid automatically when the OS reports the key is no longer usable.
PlatformDetection
AndroidKeyPermanentlyInvalidatedException on any Keystore operation; UnrecoverableKeyException when alias no longer resolves.
iOSerrSecItemNotFound (key deleted from Secure Enclave); errSecAuthFailed (biometric context changed); LAError.biometryLockout for biometry-bound keys.
On detection:
  1. SDK emits KeyInvalidated error.
  2. State transitions to keyInvalid.
  3. The next registerDevice(appId) call wipes local state (keyInvalid → unregistered) and starts fresh.
The SDK never silently retries with the invalid key.

State after resetDeviceIdentity

resetDeviceIdentity(appId) is the one-step path from any state back to unregistered. RFC §9.3 restricts when it’s allowed:
  • Server explicitly instructs identity reset (e.g. DEVICE_REVOKED).
  • User explicitly requests it (“sign out of all devices”).
  • Key invalidation is detected and rotation is impossible.
It MUST NOT be called as a convenience retry. After it returns:
  • The hardware key is deleted (best-effort; on hardware failures the alias is abandoned).
  • device_id and metadata are cleared.
  • Next API call requires a full re-registration.
  • Registration — happy-path transitions.
  • ErrorsKeyInvalidated, InvalidStateTransition, recovery paths.
  • Signing — only registered enables this.