The AndroidX Biometric API
Why Biometrics?
Passwords and PINs are easily forgotten or observed. Biometric authentication—fingerprint, face, iris—provides a more natural and secure way to verify user identity. Android's biometric framework ties authentication to hardware-backed cryptographic keys, ensuring that biometric data never leaves the secure enclave.
AndroidX Biometric Library
The AndroidX Biometric library (androidx.biometric) provides a consistent API across devices and Android versions. It replaces the deprecated FingerprintManager and FaceManager APIs.
// build.gradle.kts
implementation("androidx.biometric:biometric:1.2.0-alpha05")
BiometricPrompt vs BiometricManager
- BiometricManager — Checks if the device supports biometrics and if the user has enrolled them
- BiometricPrompt — The actual authentication dialog and flow
val biometricManager = BiometricManager.from(context)
when (biometricManager.canAuthenticate(
BiometricManager.Authenticators.BIOMETRIC_STRONG
)) {
BiometricManager.BIOMETRIC_SUCCESS -> {
// Biometrics available and enrolled
}
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> {
// No biometric hardware
}
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
// No biometrics enrolled, prompt user to enroll
}
BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> {
// Security update needed
}
BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> {
// Biometrics not supported
}
BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> {
// Unknown status
}
}
Authentication Strength Levels
| Level | Authenticator | Security |
|---|---|---|
| BIOMETRIC_STRONG | Fingerprint, Face (with liveness) | Hardware-backed, suitable for payment and sensitive operations |
| BIOMETRIC_WEAK | Basic face recognition | Software-level, suitable for app convenience features |
| DEVICE_CREDENTIAL | PIN, pattern, password | Fallback when biometrics are unavailable |
Always prefer BIOMETRIC_STRONG for authentication that protects sensitive data or operations.
Implementing BiometricPrompt
Basic Authentication Flow
class LoginActivity : AppCompatActivity() {
private val executor = ContextCompat.getMainExecutor(this)
private val biometricPrompt = BiometricPrompt(this, executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
navigateToMainScreen()
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
when (errorCode) {
BiometricPrompt.ERROR_NEGATIVE_BUTTON,
BiometricPrompt.ERROR_USER_CANCELED -> {
// User cancelled — do nothing
}
BiometricPrompt.ERROR_LOCKOUT -> {
showMessage("Too many attempts. Try again later.")
}
BiometricPrompt.ERROR_LOCKOUT_PERMANENT -> {
showMessage("Biometric locked. Use device PIN.")
}
else -> showMessage(errString.toString())
}
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
// Fingerprint not recognized — prompt shows retry automatically
}
}
)
fun showBiometricPrompt() {
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Verify Your Identity")
.setSubtitle("Scan your fingerprint to continue")
.setNegativeButtonText("Use Password")
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
.build()
biometricPrompt.authenticate(promptInfo)
}
}
Combining Biometrics with Device Credential
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Verify Your Identity")
.setSubtitle("Use fingerprint, face, or your device PIN")
.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_STRONG or
BiometricManager.Authenticators.DEVICE_CREDENTIAL
)
.build()
// Note: setNegativeButtonText is NOT allowed when DEVICE_CREDENTIAL is included
biometricPrompt.authenticate(promptInfo)
CryptoObject for Key-Bound Authentication
For maximum security, bind the biometric authentication to a cryptographic operation. The key is only released from the Android Keystore after successful biometric verification.
private fun createCryptoObject(): BiometricPrompt.CryptoObject {
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"
)
val keyGenSpec = KeyGenParameterSpec.Builder(
"biometric_key",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setUserAuthenticationRequired(true)
.setInvalidatedByBiometricEnrollment(true)
.build()
keyGenerator.init(keyGenSpec)
keyGenerator.generateKey()
val key = keyStore.getKey("biometric_key", null) as SecretKey
val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding")
cipher.init(Cipher.ENCRYPT_MODE, key)
return BiometricPrompt.CryptoObject(cipher)
}
fun showBiometricPromptWithCrypto() {
val cryptoObject = createCryptoObject()
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Authenticate to Decrypt")
.setNegativeButtonText("Cancel")
.build()
biometricPrompt.authenticate(promptInfo, cryptoObject)
}
The setInvalidatedByBiometricEnrollment(true) flag invalidates the key if the user adds or removes a biometric, preventing stale keys from being used.
Enrollment, Fallback, and Edge Cases
Handling No Biometric Enrollment
Always check if biometrics are enrolled before showing the prompt. If not, guide the user to device settings.
if (biometricManager.canAuthenticate(BIOMETRIC_STRONG) ==
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
AlertDialog.Builder(this)
.setTitle("Biometrics Not Set Up")
.setMessage("Please set up fingerprint or face unlock in device settings.")
.setPositiveButton("Open Settings") { _, _ ->
val intent = Intent(Settings.ACTION_BIOMETRIC_ENROLL).apply {
putExtra(
Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
KeyguardManager.BIOMETRIC_STRONG
)
}
startActivity(intent)
}
.setNegativeButton("Use Password") { _, _ ->
loginWithPassword()
}
.show()
}
Cancellation Handling
Users can cancel the biometric prompt by tapping outside or pressing back. Handle this gracefully:
// Track whether authentication was cancelled vs failed
private var authCancelled = false
private val biometricPrompt = BiometricPrompt(this, executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
if (errorCode == BiometricPrompt.ERROR_USER_CANCELED ||
errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) {
authCancelled = true
// Don't show error — user chose to cancel
}
}
}
)
Key Invalidation After Biometric Changes
When setInvalidatedByBiometricEnrollment(true) is set, the key is invalidated if:
- A new fingerprint is enrolled
- A new face is enrolled
- All biometrics of one type are removed
This means your app must handle re-encryption after biometric changes:
try {
val cipher = createDecryptCipher()
val data = decryptData(encryptedData, cipher)
// Success
} catch (e: UserNotAuthenticatedException) {
// Key was invalidated — re-authenticate and re-encrypt
showBiometricPrompt()
} catch (e: KeyPermanentlyInvalidatedException) {
// All biometrics removed — generate new key and re-encrypt
recreateBiometricKey()
}
Interview Tips
- Difference between CryptoObject and no CryptoObject: CryptoObject ties the biometric auth to a cryptographic key, providing stronger security. Without it, biometrics only gates access to code logic.
- Why setInvalidatedByBiometricEnrollment matters: Prevents keys from being used after the user's biometric profile changes.
- Security model: Biometric data never leaves the secure enclave. The framework only returns a success/failure signal.
Quiz
1. What is the difference between BIOMETRIC_STRONG and BIOMETRIC_WEAK?
2. Why use a CryptoObject with BiometricPrompt?
3. What happens to the biometric-bound key when the user enrolls a new fingerprint?
4. Can BiometricPrompt use DEVICE_CREDENTIAL alongside biometrics?
Flashcards
Question
What is the purpose of BiometricPrompt.CryptoObject?
Click to reveal answer
Answer
It wraps a cryptographic object (cipher, MAC, or signature) and ensures the key is only released from the Android Keystore after successful biometric authentication.
Question
What does setInvalidatedByBiometricEnrollment do?
Click to reveal answer
Answer
When true, the bound cryptographic key is invalidated if the user adds or removes biometrics, preventing stale keys from being used with different biometric profiles.
Question
How do you check if biometrics are available on a device?
Click to reveal answer
Answer
Use BiometricManager.from(context).canAuthenticate() which returns BIOMETRIC_SUCCESS, BIOMETRIC_ERROR_NO_HARDWARE, BIOMETRIC_ERROR_NONE_ENROLLED, or other error codes.
Question
Why should BIOMETRIC_STRONG be preferred over BIOMETRIC_WEAK?
Click to reveal answer
Answer
BIOMETRIC_STRONG requires hardware-backed security and liveness detection, making it suitable for payment and sensitive operations. BIOMETRIC_WEAK allows software-level matching with lower security guarantees.
Revision Notes
Key Takeaways
- 1. Use AndroidX Biometric library for cross-device compatibility, not the deprecated FingerprintManager
- 2. BIOMETRIC_STRONG with CryptoObject provides the highest security level for payment and auth flows
- 3. setInvalidatedByBiometricEnrollment invalidates keys when biometric profiles change
- 4. Always check BiometricManager.canAuthenticate() before attempting biometric authentication
- 5. Provide a device credential fallback for users without biometrics enrolled
Interview Tips
- • Explain how CryptoObject binds biometric auth to the Android Keystore
- • Know the difference between BIOMETRIC_STRONG and BIOMETRIC_WEAK
- • Be ready to discuss key invalidation after biometric enrollment changes
- • Explain why biometric data never leaves the secure enclave
Cheat Sheet
Biometric Authentication Cheat Sheet
AndroidX Biometric:
- BiometricManager — check availability
- BiometricPrompt — show auth dialog
- CryptoObject — bind to cryptographic key
Strength Levels:
- BIOMETRIC_STRONG — hardware-backed, liveness detection
- BIOMETRIC_WEAK — software-level matching
- DEVICE_CREDENTIAL — PIN/pattern/password fallback
Key Config:
- setUserAuthenticationRequired(true) — key needs biometric
- setInvalidatedByBiometricEnrollment(true) — invalidate on biometric changes
Error Codes:
- ERROR_LOCKOUT — too many failed attempts
- ERROR_LOCKOUT_PERMANENT — permanently locked
- ERROR_USER_CANCELED — user dismissed prompt
Best Practices:
- Check canAuthenticate() before showing prompt
- Handle ERROR_NONE_ENROLLED by guiding to settings
- Use CryptoObject for sensitive operations
- Always provide a non-biometric fallback