Closed srofi closed 11 months ago
To clarify, I'm seeing the PIN fallback option (since allowDeviceCredential == true), but only when displaying the biometric screen (only if the device has one)
Device credential fallback is precisely that: credential entry after biometry is presented.
What you are proposing is to present the Android lock screen directly (which might not be a PIN, but rather a pattern) if biometry is not available. I searched online, did not find a way. Then I asked ChatGPT, and it said:
No, it's not possible to programmatically present the system PIN/pattern entry on Android due to security reasons. Android does not provide APIs for developers to directly interact with these system-level authentication mechanisms.
Hello!
I've been playing with and vetting this project today and have come across the same situation.
It would be nice for BiometricAuth.authenticate()
(perhaps with some combination of options) to simply challenge the user for device security, regardless of enrollment of biometrics, pin, password, or pattern. Likewise, it would be nice for BiometricAuth.checkBiometry()
to resolve positively if one of the above was enrolled rather than if and only if biometrics were enrolled. At that point, it would be more DeviceSecurity
than it is BiometricAuth
.
I work on a native Android and iOS app in which we have this behavior. (Not open source, sorry.) It is possible to challenge a user to enter their PIN or password in the case where they don't have biometrics enrolled.
On Android SDK 30+ devices, it can be done by tweaking the allowed authenticators of the BiometricPrompt
. Something like this:
BiometricPrompt.PromptInfo.Builder().apply {
setTitle("Title")
setSubtitle("subtitle)
setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.DEVICE_CREDENTIAL)
}.build()
On devices below Android SDK 30, I think this is the default behavior when calling KeyguardManager.createConfirmDeviceCredentialIntent()
. I see the library isn't using this, however, so there may be some complexity there.
I won't dig into the iOS side of things now, but it's much less of a hassle overall.
I believe this would at least solve @srofi's problem.
This is a change that I would find immensely valuable as well.
On Android SDK 30+ devices, it can be done by tweaking the allowed authenticators of the BiometricPrompt. Something like this: setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.DEVICE_CREDENTIAL)
Thanks for bringing this up! I do that already, but my call to checkBiometry(), was preventing the device credential from appearing if no biometry was available. If the Android docs didn't suck so much, maybe I would have figured that out earlier!
In any case, to support this behavior (and the corresponding change to checkBiometry
) I agree I really should rename the plugin to DeviceSecurity. Can't do it right now, have to finish up a project first. In the meantime, if you really need the fix, you can fork the repo, delete or comment out the lines referenced above, run pnpm build
, and install the plugin directly from your local fork.
Thank you both on your replies! Yes, I managed to default to PIN/Pattern by setting allowDeviceCredential
to true
when calling the plugin, and modifying the check on the Android implementation of the authenticate
method where it rejects the call if there's no biometric hardware on the device.
@srofi Thank you for the guidance. That seems to be enough to get it working how we'd like.
@aparajita We're making good headway on making this behave nicely in the case where a device is not enrolled with biometrics. However, it makes the API and code a little dishonest since it isn't necessarily Biometry we're asking for at this point. I think it would be good form to address that before a PR.
I see two or three general approaches here:
checkBiometry()
becomes checkDeviceSecurity()
. This is destructive maybe worthy a major version bump.checkBiometry()
and checkDeviceSecurity()
, but not that big of a dealAt this point, I'm leaning towards option 2. Since I'd like to get this merged upstream, I would appreciate your perspective on this. Alternatively, if you would prefer not to accept a PR at all, that would be good to know here.
@TheIronMarx Just do what you need and use your own fork. To fully support non-biometric security means creating a new plugin and then deprecating the current plugin (which you can't do), so I will not merge your changes into the existing plugin. I will make the new plugin in January.
Sounds good! I appreciate your quick response.
@TheIronMarx On further thought, I am not going to create a new plugin so that existing users won't have to change any code. authenticate()
will always attempt biometry first; device security cannot be presented independently. Thus authenticate()
is the right terminology.
As for determining if device security is available, I will just add a boolean flag to CheckBiometryResult. Again, this makes sense because device security cannot be used independently of attempting biometry.
FYI v7.0.0 has just been released which adds the following features:
allowDeviceCredentials
is true and no biometry is available.deviceIsSecure
flag to checkBiometry()
so you can know if device credentials are set.strongBiometryIsAvailable
flag to checkBiometry()
.androidBiometryStrength
to authenticate()
options, to force only strong biometry to be used on Android.This version should require no code changes. It was flagged as a potentially breaking change because of the change in the way the allowDeviceCredentials
flag affects behavior.
Thanks for all of the feedback!
I'm trying to implement this plugin for Android, and it works great when I do have any biometric types on device. But I'd like it to fallback to the PIN input if not. Is this possible? This is are the options I'm passing to the authenticate method right now:
And it's returning this error: