SenseCrypt Docs
SDKs & appsMobile SDK

Key management

Rotate and revoke device keys with the SenseCrypt Mobile SDK — the make-before-break rotation ceremony, the rotation hint, and single-key revocation.

A device holds one key per enrolled identity/application pair. Two flows manage them: rotation replaces the key material for every key on the device, and revocation removes a single key.

Both are short flows: they emit a confirmation state carrying what's affected, wait for approve(), gate on a biometric, and submit.

Listing what the device holds

let keys: [Key] = authn.keys
let current: Key? = authn.currentKey
val keys: List<Key> = authn.keys
val current: Key? = authn.currentKey

keys only ever contains usable keys — records whose underlying hardware key was wiped are filtered out, and keys that survived an app reinstall are cleared by open(). Use it to decide whether to offer a rotate/revoke UI at all.

Key rotation

Rotation is a make-before-break ceremony: new key material is generated and recorded under temporary aliases before the old keys are retired, so a device is never locked out mid-rotation.

Starting a rotation

let flow = try authn.startKeyRotationFlow()

Task {
    for await state in flow.stateStream() {
        handle(state)
    }
}
val flow = withContext(Dispatchers.IO) { authn.startKeyRotationFlow() }

lifecycleScope.launch {
    flow.state.collect { state -> handle(state) }
}

KeyRotationFlow.state is a StateFlow, so it does expose .value.

startKeyRotationFlow() throws AuthnRuntimeError.NoKeys when there is nothing to rotate. Hide the rotate action when keys is empty — but still handle the throw, because the last key can be revoked between your check and the tap.

Rotation states

StatePayloadWhat your UI does
idleNothing yet.
confirmingkeys: [Key]Pre-confirm dialog. Lists every key that will rotate.
authenticatingBiometricThe system biometric prompt is up.
generatingNewKeysGenerating key material. Show "this may take a moment" — it is slow on older devices.
submittingPosting to the portal.
succeededrevokedKeys: [RevokedKeyNotice]Terminal success.
cancelledTerminal.
failedreason: FailureReasonTerminal.

The confirming payload carries every key the device holds, so your dialog can say "Rotate 3 keys" and show which applications and email addresses are affected.

Approving

flow.approve()   // proceed
flow.cancel()    // abandon
flow.approve()
flow.cancel()

Reading the result

succeeded(revokedKeys:) lists every dead key the ceremony discovered and cleaned up — keys the server had already revoked or terminated, which were dropped rather than rotated. The list is empty when every key rotated cleanly.

Each RevokedKeyNotice carries email, clientId, appLabel, and terminated: Bool. Surface these to the user: an entry here means an enrollment they thought they had is gone.

succeeded with a non-empty list can be emitted without any portal round-trip at all — if every local key turned out to be dead, there was nothing left to rotate. Don't assume success implies a successful network call.

The rotation hint

The portal signals, on any signed response, that this device is still using its previous key after a rotation overlap. The SDK captures that signal and exposes it as a one-shot flag:

if authn.takeKeyRotationHint() {
    // Converge onto the new key.
    let flow = try authn.startKeyRotationFlow()
}
if (authn.takeKeyRotationHint()) {
    val flow = withContext(Dispatchers.IO) { authn.startKeyRotationFlow() }
}

Poll it at a natural checkpoint — after a successful sign-in is the usual choice. Reading it clears it, but the portal re-sets it on the next request made with the old key, so a missed prompt self-corrects on the following sign-in. You do not need to persist it.

Revocation

Revocation removes one key, identified by its id.

let flow = try authn.startRevocationFlow(keyId: key.id)

Task {
    for await state in flow.stateStream() {
        handle(state)
    }
}
val flow = withContext(Dispatchers.IO) { authn.startRevocationFlow(key.id) }

lifecycleScope.launch {
    flow.state.collect { state -> handle(state) }
}

Throws AuthnRuntimeError.UnknownKey(keyId) if the id isn't held by this device.

Revocation states

StatePayloadWhat your UI does
idleNothing yet.
confirmingkey: KeyConfirm dialog for this one key.
authenticatingBiometricBiometric prompt.
submittingPosting.
succeededTerminal. The key is gone.
cancelledTerminal.
failedreason: FailureReasonTerminal.

Drive it with approve() and cancel(), same as rotation.

Revocation is irreversible. The user must re-enroll to use this device for that identity again. Make the confirmation dialog say so — the confirming state hands you the Key precisely so you can name the affected application and email.

Branding for key lists

To render a key list with the right logo per entry:

let branding: Branding? = authn.brandingFor(keyId: key.id)
val branding: Branding? = authn.brandingFor(key.id)

Returns the cached branding for that key, or nil — in which case fall back to a default icon or a monogram. See Branding.

Audit log

let entries: [AuditEntry] = authn.auditLog(limit: 50)
val entries: List<AuditEntry> = authn.auditLog(limit = 50)

A local, device-side record of SDK activity — timestamp, kind, optional identityId, and a note. limit defaults to 50. Useful for an in-app activity screen.

On this page