Biometrics
An app that guards something (a vault, a payment, a settings page) asks the device to verify the
person before opening it. day-part-biometrics shows the platform’s own authentication dialog
(Touch ID, Face ID, Android’s biometric prompt, Windows Hello, HarmonyOS user authentication) with a
reason in the person’s language, lets the device credential pass where the app allows it, and
reports one answer: verified, or why not.
Add to your app
Defined in day-part-biometrics. Add this to your app’s Cargo.toml
[dependencies] table, using the same Day Git source as the rest of your app:
[dependencies]
day-part-biometrics = { git = "https://github.com/daybrite/day.git" }
The implementation is selected by the compilation target; no toolkit feature flags are needed. The
crate declares that it uses the portable biometrics permission, so day build asks the app to
declare it too (docs/permissions): USE_BIOMETRIC on Android and ACCESS_BIOMETRIC on
HarmonyOS are granted at install with no dialog, and iOS shows the reason the first time Face ID is
used.
[permissions]
biometrics = true # the reason is the catalog's `permission_biometrics` message
| Platforms / toolkits | Availability |
|---|---|
| iOS, macOS | Touch ID, Face ID, Optic ID, and the passcode or account password when allowed. |
| Android | The system’s biometric prompt on API 28 and later, with the device credential when allowed; Unsupported below. |
| Windows | Windows Hello (PIN, face, fingerprint), owned by the active window. Hello decides itself what it asks for. |
| HarmonyOS | The User Authentication Kit’s dialog (face, fingerprint, and the PIN when allowed); a passed prompt carries the kit’s token. |
| Linux (GTK / Qt), Web | No API an application may use: Unsupported. |
Authoring
use day_part_biometrics::{Availability, BiometricError, Prompt};
let ready = day_part_biometrics::availability() == Availability::Available;
day::task(async move {
let prompt = Prompt::new(res::str::unlock_reason().format())
.fallback_title(res::str::use_passcode().format());
match day_part_biometrics::authenticate_future(prompt).await {
Ok(verified) => open_vault(verified.method),
Err(BiometricError::UserCancelled) => {}
Err(e) => status.set(e.to_string()),
}
});
availability() asks whether a prompt can succeed now (Availability: Available, NotEnrolled,
NoHardware, PasscodeNotSet, Lockout, DisabledByPolicy, Busy, Unsupported), and
biometry() which sensor the device offers (Biometry: Fingerprint, Face, Iris, Unknown, None).
Both are probes of the moment; support() is the fixed fact of whether this target has an API at
all.
A Prompt carries the reason the dialog shows (required; localize it), an optional
fallback_title for the button that leads away from the biometric, whether the
device_credential (passcode, PIN, pattern, password) may pass the prompt (it may, by default),
and a challenge HarmonyOS binds its token to. authenticate(prompt, callback) and
authenticate_future(prompt) show the dialog and answer exactly once with a Verified
(how the person passed, and the platform token where one exists) or a BiometricError.
cancel() dismisses the dialog on screen.
One prompt at a time
Every platform shows one authentication dialog at a time. A second authenticate while one is on
screen fails at once with BiometricError::Busy. A prompt whose reason is empty fails with
InvalidPrompt before reaching the platform (Apple aborts on an empty reason; the others show a
blank dialog).
What a pass proves
A Verified answer means the OS showed its dialog and the person passed it. It is a fact about the
moment, not a key: nothing is unlocked by it except what the app does next. Where a stored secret
should be bound to the same check, use day-part-keychain’s Protection::UserPresence, whose
read performs this prompt itself on the platforms that need one and lets the keychain or keystore
do the binding.
Errors
BiometricError distinguishes what the app should do next: UserCancelled and SystemCancelled
(nothing), UserFallback (the person chose the fallback button of a biometric-only prompt: show the
app’s own alternative), Failed (the attempt did not match and the dialog closed), Lockout and
LockoutPermanent (the device credential clears them), NotEnrolled, NoHardware,
PasscodeNotSet, Busy, DisabledByPolicy, NotInteractive (no window to attach the dialog to),
InvalidPrompt, Unsupported, and Platform { code, message } for the rest.
Threads
Completions run where the platform delivers them (a LocalAuthentication reply queue, an Android
executor, a WinRT thread-pool thread, the HarmonyOS JS thread), which is why the callback is
Send. authenticate_future awaited under day::task resumes on the UI thread, so the code after
the .await writes signals directly (docs/async). Nothing here posts to a main loop, so the
crate works in a plain main and under cargo test.
Per-platform native realization
Platform guidance: Apple: Local Authentication; Android: show a biometric authentication dialog; Windows Hello; HarmonyOS: User Authentication Kit.
| OS | API | dependency |
|---|---|---|
| iOS, macOS | LAContext: canEvaluatePolicy(_:error:) and biometryType for the probes, evaluatePolicy(_:localizedReason:reply:) for the dialog, invalidate() to cancel | objc2, block2, objc2-foundation |
| Android | BiometricManager.canAuthenticate for the probe, BiometricPrompt for the dialog, a CancellationSignal to cancel | a Java arm (day-bridge) |
| Windows | UserConsentVerifier.CheckAvailabilityAsync for the probe, IUserConsentVerifierInterop::RequestVerificationForWindowAsync for the dialog, owned by the active window | windows |
| HarmonyOS | userAuth.getAvailableStatus for the probes, getUserAuthInstance(...).start() for the dialog, cancel() to cancel | an ArkTS arm (day-bridge) |
| Linux, Web | — | none (Unsupported) |
How each platform realizes the prompt
- iOS and macOS: a fresh
LAContextper prompt (one that has evaluated once answers the next evaluation from its cache without showing anything), kept alive until the reply socancelcan invalidate it.device_credentialselects the policy:deviceOwnerAuthenticationfalls back to the passcode or account password on its own;deviceOwnerAuthenticationWithBiometricsasks for a biometric only and reports the fallback button asUserFallback.availabilityalways asks about the biometric policy. A pass through the mixed policy reportsMethod::Unknown, because the reply does not say which credential passed. - Android: the platform prompt in
android.hardware.biometrics, which takes an application context and needs no library. On API 30 and later the prompt is built withBIOMETRIC_STRONG, plusDEVICE_CREDENTIALwhen allowed; on API 28 and 29 the device credential is offered where the OS supports it, and a biometric-only prompt carries the fallback title as its negative button. Each miss is shown in the dialog and not reported until the dialog ends. Below API 28 there is no prompt an app may show without the deprecated fingerprint API and its own permission, so the crate reportsUnsupportedthere. - Windows:
CheckAvailabilityAsyncfirst, because a request on a machine with no Hello credential does not fail, it waits. The dialog is owned by the window handle captured on the calling thread (the active window of a Day action) and the verification is awaited on a worker thread with its own apartment, so the UI keeps pumping. Hello never says which credential passed, so a pass reportsMethod::Unknownandbiometry()reportsUnknownwhen anything is set up. Thedevice_credentialflag is ignored: Hello offers its PIN itself. - HarmonyOS: the dialog lists the modalities it is given and refuses one the device lacks, so
the arm probes face, fingerprint and (when allowed) the PIN with
getAvailableStatusand lists only those with an executor, enrolled or not; a device with nothing enrolled then fails atstart()withNotEnrolled. A passed prompt returns the kit’s token (up to 1024 bytes), bound to the prompt’schallenge, which a Universal Keystore key generated with user authentication accepts once;day-part-keychainuses this for its protected items.ACCESS_BIOMETRICis required even for a PIN-only prompt.
What it shows about the extension system
A headless crate with no UI Piece, like day-part-haptics. Its Android and HarmonyOS halves are
daybridge arms (docs/bridge) in Java and ArkTS, and its [package.metadata.day.permissions] uses
line is what makes day build insist on the app’s biometrics declaration (docs/extending): the
crate names the machine-facing half, the app writes the reason. The Day Showcase’s Security page
shows the probes and the prompt beside the keychain’s protected item.