1

BiometricManager has a canAuthenticate method that can return either of 4 flags:

when (biometricManager.canAuthenticate()) {
    BiometricManager.BIOMETRIC_SUCCESS ->
        Log.d("MY_APP_TAG", "App can authenticate using biometrics.")
    BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE ->
        Log.e("MY_APP_TAG", "No biometric features available on this device.")
    BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE ->
        Log.e("MY_APP_TAG", "Biometric features are currently unavailable.")
    BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED ->
        Log.e("MY_APP_TAG", "The user hasn't associated " +
        "any biometric credentials with their account.")
}

Now it seems logical to only trigger the BiometricPrompt if the result of that call is BIOMETRIC_SUCCESS and otherwise fall back to a different authentication method (i.e. app-specific password).

But if I set setDeviceCredentialAllowed(true) on the BiometricPrompt, it can still use the device password even if the canAuthenticate check does not return BIOMETRIC_SUCCESS (I think in this case it returns BIOMETRIC_ERROR_NONE_ENROLLED).

I could additionally use KeyguardManager to check if a pin/password/pattern is set:

val kgm = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
if (kgm.isDeviceSecure) {
    [...]
}

But this only works on API 23+. However, setDeviceCredentialAllowed works on API levels below 23. But there seems to be no real alternative to check if a device password is set on these older devices

So my question is: With all these different options, what is the correct flow to show the user the appropriate authentication method? How should canAuthenticate and isDeviceSecure be used together and what check should be used for API levels below 23?

Florian Walther
  • 6,237
  • 5
  • 46
  • 104

1 Answers1

2

You should use canAuthenticate() only when you are trying to authenticate exclusively with biometrics, not with anything else. And in that case the is a well documented recommended approach.

If your interest is setDeviceCredentialAllowed(true), while the functionality itself only works on API 21+, you have a few implementation options depending on your minSdkVersion.

API 23+

 if (keyguardManager.isDeviceSecure()){
     biometricPrompt.authenticate(promptInfo)
 }

API 16 to pre-API 23

if (keyguardManager.isKeyguardSecure) {
   biometricPrompt.authenticate(promptInfo)
}

KeyguardManager.isKeyguardSecure() is equivalent to isDeviceSecure() unless the device is SIM-locked.

API 14 to pre-API 16

If you are targeting lower than API 16 or SIM-lock is an issue, then the callback onAuthenticationError() is your best bet.

Isai Damier
  • 976
  • 6
  • 8
  • Thank you for your answer. However, I have some doubts: 1) `You should use canAuthenticate() only when you are trying to authenticate exclusively with biometrics, not with anything else` Why is that? I mean, I can use both biometrics and device credentials as a fallback. 2) Could the sim lock with `isKeyguardSecure` cause false-positive checks if the sim is locked but no pin/pattern/password is set? 3) Why check for API levels below 21? To my knowledge, `setDeviceCredentialAllowed` doesn't work lower than `API 21` – Florian Walther Jan 07 '20 at 21:42
  • 1) I answer a similar question https://stackoverflow.com/questions/59566044/biometricprompt-why-should-we-check-keyguardmanager-isdevicesecure-before-ena/59570158#59570158. 3) it all depends on how you want to implement your code if your app's minSdkVersion falls below 21. – Isai Damier Jan 07 '20 at 21:50
  • That other question is also mine. I assume what you mean is that we should use `CryptoObject` and that's not compatible with `setDeviceCredentialAllowed`. But what if we use `BiometricPrompt` without `CryptoObject`? – Florian Walther Jan 07 '20 at 22:42