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 / toolkitsAvailability
iOS, macOSTouch ID, Face ID, Optic ID, and the passcode or account password when allowed.
AndroidThe system’s biometric prompt on API 28 and later, with the device credential when allowed; Unsupported below.
WindowsWindows Hello (PIN, face, fingerprint), owned by the active window. Hello decides itself what it asks for.
HarmonyOSThe User Authentication Kit’s dialog (face, fingerprint, and the PIN when allowed); a passed prompt carries the kit’s token.
Linux (GTK / Qt), WebNo 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.

OSAPIdependency
iOS, macOSLAContext: canEvaluatePolicy(_:error:) and biometryType for the probes, evaluatePolicy(_:localizedReason:reply:) for the dialog, invalidate() to cancelobjc2, block2, objc2-foundation
AndroidBiometricManager.canAuthenticate for the probe, BiometricPrompt for the dialog, a CancellationSignal to cancela Java arm (day-bridge)
WindowsUserConsentVerifier.CheckAvailabilityAsync for the probe, IUserConsentVerifierInterop::RequestVerificationForWindowAsync for the dialog, owned by the active windowwindows
HarmonyOSuserAuth.getAvailableStatus for the probes, getUserAuthInstance(...).start() for the dialog, cancel() to cancelan ArkTS arm (day-bridge)
Linux, Web—none (Unsupported)

How each platform realizes the prompt

  • iOS and macOS: a fresh LAContext per prompt (one that has evaluated once answers the next evaluation from its cache without showing anything), kept alive until the reply so cancel can invalidate it. device_credential selects the policy: deviceOwnerAuthentication falls back to the passcode or account password on its own; deviceOwnerAuthenticationWithBiometrics asks for a biometric only and reports the fallback button as UserFallback. availability always asks about the biometric policy. A pass through the mixed policy reports Method::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 with BIOMETRIC_STRONG, plus DEVICE_CREDENTIAL when 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 reports Unsupported there.
  • Windows: CheckAvailabilityAsync first, 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 reports Method::Unknown and biometry() reports Unknown when anything is set up. The device_credential flag 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 getAvailableStatus and lists only those with an executor, enrolled or not; a device with nothing enrolled then fails at start() with NotEnrolled. A passed prompt returns the kit’s token (up to 1024 bytes), bound to the prompt’s challenge, which a Universal Keystore key generated with user authentication accepts once; day-part-keychain uses this for its protected items. ACCESS_BIOMETRIC is 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.