A biometric prompt that only calls a success callback proves little if the app process is compromised, because the callback can be faked. A stronger design ties the prompt to a Keystore key: the key is unusable until the user authenticates. On Android that means a CryptoObject and a key with user authentication required.
What MASVS asks
MASVS-AUTH-2 says the app performs local authentication securely according to platform best practices, and notes that some apps rely on biometrics or a local PIN. MASVS-AUTH-3 adds extra authentication for sensitive operations. Both sit alongside the rule that enforcement of authentication and authorization must be on the remote endpoint.
Two ways to wire it up
Callback only. The app shows BiometricPrompt, waits for onAuthenticationSucceeded, and then proceeds, perhaps reading a stored token. The app logic decides what happens next. An attacker who can hook the app can call that same code path.
Crypto-bound. Android's documentation describes a CryptoObject, which binds the biometric authentication to a cryptographic operation. Supported objects are Signature, Cipher, Mac and KeyAgreement. You generate a key that requires user authentication, initialize a cipher with it, and pass the CryptoObject to authenticate(). The operation only succeeds if the user actually authenticated, and the work happens in the Keystore system process.
The Keystore docs explain the benefit: binding keys to user authentication prevents a compromised app process, after key creation, from bypassing the requirement. They also note the limit: it does not protect against compromise during or before key generation.
Choose the key mode
Android's biometric guide gives three patterns:
- Biometric-only key, authenticated per use. Strongest. Invalidated by default when a new biometric is enrolled (
setInvalidatedByBiometricEnrollment, default true). - Biometric or lock screen with a validity window. Convenient, since you can operate without re-prompting inside the window. But you cannot pass a CryptoObject with this key type and you must allow fallback to non-biometric credentials.
- Authentication per use with a duration of zero. Suited, per the docs, to high value actions such as large payments or health record updates.
Practical rules
- Request
BIOMETRIC_STRONGfor anything sensitive, and addDEVICE_CREDENTIALif you want lock screen fallback. Do not combineDEVICE_CREDENTIALwith a negative button text. - Call
canAuthenticate()first and handle the none-enrolled case by sending the user to enrollment. - Keep the default confirmation step for high risk actions such as purchases. Disabling it is only a hint and the system may ignore it.
- Plan for invalidation: when keys are invalidated by new enrollments, the user needs a safe way back in, such as full re-login.
- Never treat the local check as the only gate. The server should still verify the session or token for the action.
What this does not fix
It does not make a stolen token safe. If your app stores a long lived token anywhere an attacker can read, local biometrics are decoration. Keep tokens short lived and revocable, and protect them with a key that needs the authentication.
Quick check
Is checking the callback result enough?
For low risk screens it may be. For sensitive actions use a CryptoObject with an authentication-bound key so the Keystore enforces the check.
Why did my biometric key stop working?
Biometric-only keys are invalidated by default when new biometrics are enrolled. Generate a new key after the user signs in again.
Sources
Last reviewed: 19 October 2026.