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, with its private key in the device's secure hardware. 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.currentKeyval keys: List<Key> = authn.keys
val current: Key? = authn.currentKeykeys 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
| State | Payload | What your UI does |
|---|---|---|
idle | — | Nothing yet. |
confirming | keys: [Key] | Pre-confirm dialog. Lists every key that will rotate. |
authenticatingBiometric | — | The system biometric prompt is up. |
generatingNewKeys | — | Generating key material. Show "this may take a moment" — it is slow on older devices. |
submitting | — | Posting to the portal. |
succeeded | revokedKeys: [RevokedKeyNotice] | Terminal success. |
cancelled | — | Terminal. |
failed | reason: FailureReason | Terminal. |
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() // abandonflow.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, terminated: Bool and replaced: Bool. Surface these to the user: an entry here means an enrollment they thought they had is gone. replaced is true when the server said why — the user added the app on another device under a tenant that allows one device per user — so say the account moved rather than that an administrator removed it. The branding sync reports the same keys as BrandingSyncSummary.replacedKeys.
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
| State | Payload | What your UI does |
|---|---|---|
idle | — | Nothing yet. |
confirming | key: Key | Confirm dialog for this one key. |
authenticatingBiometric | — | Biometric prompt. |
submitting | — | Posting. |
succeeded | — | Terminal. The key is gone. |
cancelled | — | Terminal. |
failed | reason: FailureReason | Terminal. |
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.
A key can also be revoked from the server side: an administrator can revoke a user's device from the admin console or the Management API. The device is not notified at that moment. Its next signed request for that identity, such as a sign-in or an approval, fails with deviceKeyRevoked (or deviceKeyTerminated when the user no longer has an application they could enroll through), and the SDK removes that key from the device. Treat it as a re-enrollment trigger, as described in errors: after deviceKeyRevoked the user can enroll this device again.
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.
Related
- Registration — minting the first key.
- Errors —
AuthnRuntimeErrorand failure reasons. - Reference —
Key,RevokedKeyNotice,Branding,AuditEntry. - Signing key rotation — the server-side counterpart.
Registration flow
Enroll a user and mint a device key with the SenseCrypt Mobile SDK — email PIN verification, the self-signup branch with attribute collection and consent, and face capture.
UI components
The SenseCrypt Mobile SDK's bundled QR-scan and face-capture surfaces for SwiftUI, UIKit, and Jetpack Compose — plus the FaceCaptureDriver contract for building your own.