androidengineers.Book a session

Security Architecture

Biometric Auth & UX

article20 minHard

Biometric authentication (fingerprint, face, iris) gives users both security and convenience. The BiometricPrompt API provides a consistent, system-managed UI across Android versions and hardware.

BiometricManager: Check Availability

Before showing any biometric UI, check whether the device can authenticate:

class BiometricHelper(private val context: Context) {

    fun canAuthenticate(): BiometricCapability {
        val manager = BiometricManager.from(context)
        return when (manager.canAuthenticate(
            BiometricManager.Authenticators.BIOMETRIC_STRONG or
            BiometricManager.Authenticators.DEVICE_CREDENTIAL
        )) {
            BiometricManager.BIOMETRIC_SUCCESS -> BiometricCapability.AVAILABLE
            BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> BiometricCapability.NO_HARDWARE
            BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> BiometricCapability.HARDWARE_UNAVAILABLE
            BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> BiometricCapability.NOT_ENROLLED
            else -> BiometricCapability.UNAVAILABLE
        }
    }
}

enum class BiometricCapability {
    AVAILABLE,
    NO_HARDWARE,
    HARDWARE_UNAVAILABLE,
    NOT_ENROLLED,
    UNAVAILABLE
}

BiometricPrompt: Show the Prompt

class BiometricAuthManager(private val activity: FragmentActivity) {

    private val executor = ContextCompat.getMainExecutor(activity)

    fun authenticate(
        onSuccess: (BiometricPrompt.AuthenticationResult) -> Unit,
        onError: (Int, CharSequence) -> Unit,
        onFailed: () -> Unit
    ) {
        val biometricPrompt = BiometricPrompt(
            activity,
            executor,
            object : BiometricPrompt.AuthenticationCallback() {
                override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                    onSuccess(result)
                }

                override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                    onError(errorCode, errString)
                }

                override fun onAuthenticationFailed() {
                    onFailed()  // Called when a recognized finger/face doesn't match
                }
            }
        )

        val promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setTitle("Verify your identity")
            .setSubtitle("Use your fingerprint or face to continue")
            .setAllowedAuthenticators(
                BiometricManager.Authenticators.BIOMETRIC_STRONG or
                BiometricManager.Authenticators.DEVICE_CREDENTIAL  // fallback to PIN/pattern
            )
            .build()

        biometricPrompt.authenticate(promptInfo)
    }
}

Biometric-Gated Keystore Operations

The most secure pattern: require biometric authentication to use a Keystore key:

fun generateBiometricKey(alias: String): SecretKey {
    val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
    keyGenerator.init(
        KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
            .setUserAuthenticationRequired(true)           // ← requires biometric/PIN
            .setUserAuthenticationParameters(
                30,                                         // timeout: 30 seconds
                KeyProperties.AUTH_BIOMETRIC_STRONG or KeyProperties.AUTH_DEVICE_CREDENTIAL
            )
            .build()
    )
    return keyGenerator.generateKey()
}

// Use the biometric-gated key:
val cryptoObject = BiometricPrompt.CryptoObject(cipher)
biometricPrompt.authenticate(promptInfo, cryptoObject)

// In onAuthenticationSucceeded:
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
    val authenticatedCipher = result.cryptoObject?.cipher ?: return
    val decryptedData = authenticatedCipher.doFinal(encryptedToken)
    // Use decryptedData — you're sure the user just authenticated
}

UX Best Practices

Show biometric prompt at the right time:

// ✅ Show on sensitive actions, not on every resume
class PaymentFragment : Fragment() {
    fun onPayNowClicked() {
        biometricAuthManager.authenticate(
            onSuccess = { processPayment() },
            onError = { code, message -> showError(message) },
            onFailed = {}  // don't show error per-failure — system shows its own UI
        )
    }
}

Handle enrollment gracefully:

when (biometricHelper.canAuthenticate()) {
    BiometricCapability.NOT_ENROLLED -> {
        // Guide user to enroll
        val enrollIntent = Intent(Settings.ACTION_BIOMETRIC_ENROLL).apply {
            putExtra(Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
                BiometricManager.Authenticators.BIOMETRIC_STRONG)
        }
        startActivity(enrollIntent)
    }
    BiometricCapability.AVAILABLE -> showBiometricPrompt()
    else -> fallbackToPin()
}

Error codes to handle specially:

override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
    when (errorCode) {
        BiometricPrompt.ERROR_USER_CANCELED,
        BiometricPrompt.ERROR_NEGATIVE_BUTTON -> {
            // User cancelled — don't show an error dialog
        }
        BiometricPrompt.ERROR_LOCKOUT,
        BiometricPrompt.ERROR_LOCKOUT_PERMANENT -> {
            showMessage("Too many attempts — use your PIN")
        }
        else -> showError(errString)
    }
}

Key Takeaways

ConceptRule
canAuthenticate()Check before showing any biometric UI
DEVICE_CREDENTIAL fallbackAlways include — users without biometrics still need access
CryptoObjectLink authentication to a specific crypto operation for maximum security
ERROR_USER_CANCELEDDon't show error dialog — the user chose to cancel
ERROR_LOCKOUTInform user to use PIN; system enforces the lockout
setUserAuthenticationRequired(true)Keystore key can only be used after biometric auth

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
Biometric Auth & UX | Android System Design | Android Engineers