Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

[local-authentication] Handle BIOMETRIC_ERROR_NONE_ENROLLED errors to account for copy changes #9500

Closed
etaiklein opened this issue Jul 30, 2020 · 4 comments

Comments

@etaiklein
Copy link

etaiklein commented Jul 30, 2020

馃悰 Bug Report

Summary of Issue

supportedAuthenticationTypesAsync returns an array of types including FINGERPRINT, FACE, IRIS, etc.
hasHardwareAsync and isEnrolledAsync seem to check all types.

On a Galaxy S10 you can have only FINGERPRINT ([1]) respond from supportedAuthenticationTypesAsync (see thread here for why S10 does this #7838). So, if you only have have an enrolled FACE and not have an enrolled FINGERPRINT.

This means we tell users fingerprint is available but it automatically falls back to device passcode every time

We could solve this by allowing hasHardwareAsync and isEnrolledAsync to take an argument to check a specific biometry type.

Environment - output of expo diagnostics & the platform(s) you're targeting

Bare app

Expo CLI 3.22.3 environment info:
System:
OS: macOS 10.15.5
Shell: 3.2.57 - /bin/bash
Binaries:
Node: 10.19.0 - ~/.nvm/versions/node/v10.19.0/bin/node
npm: 6.13.4 - ~/.nvm/versions/node/v10.19.0/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
IDEs:
Android Studio: 3.5 AI-191.8026.42.35.6010548
Xcode: 11.6/11E708 - /usr/bin/xcodebuild
npmPackages:
react: 16.11.0 => 16.11.0
react-dom: 16.9.0 => 16.9.0
react-native: 0.62.2 => 0.62.2
react-navigation: 3.12.1 => 3.12.1
npmGlobalPackages:
expo-cli: 3.22.3

Reproducible Demo

   try {
      LocalAuthentication.supportedAuthenticationTypesAsync().then(
        (biometryTypes: BiometryTypes) => {
          if (!biometryTypes) {
            return;
          }
          const chosenBiometry = biometryTypes.slice(-1).pop(); // iris -> face -> touch
          LocalAuthentication.hasHardwareAsync().then((hasDeviceHardware) => {
            if (!hasDeviceHardware) {
              return;
            }
            LocalAuthentication.isEnrolledAsync().then((hasFingerprintOrFacialData) => {
              if (!hasFingerprintOrFacialData) {
                return;
              }
              setBiometryType(chosenBiometry);
            });
          });
        }
      );
    } catch {
      // eslint-disable-next-line no-console
      console.error('Failed to retrieve supported biometry type');
    }

...
<button onPress={async () => {console.log(chosenBiometry, chosenBiometry ? await LocalAuthentication.authenticateAsync() : 'do not prompt')} />

Steps to Reproduce

Trigger code above on a Galaxy S10 with face Enrolled but fingerprint not Enrolled.
authenticateAsync will refuse to open fingerprint scanner and instead fall back to device passcode
Console log will print 1, {success: false ... }

Expected Behavior vs Actual Behavior

Expected behavior is to be retrieve only biometric types that are actually available:

Suggested code:

    try {
      LocalAuthentication.supportedAuthenticationTypesAsync().then(
        (biometryTypes: BiometryTypes) => {
          if (!biometryTypes) {
            return;
          }
          const chosenBiometry = biometryTypes.slice(-1).pop(); // iris -> face -> touch
          LocalAuthentication.hasHardwareAsync(chosenBiometry).then(
            (hasDeviceHardware) => {
              if (!hasDeviceHardware) {
                return;
              }
              LocalAuthentication.isEnrolledAsync(chosenBiometry).then(
                (hasFingerprintOrFacialData) => {
                  if (!hasFingerprintOrFacialData) {
                    return;
                  }
                  setBiometryType(chosenBiometry);
                }
              );
            }
          );
        }
      );
    } catch {
      // eslint-disable-next-line no-console
      console.error('Failed to retrieve supported biometry type');
    }

...
<button onPress={async () => {console.log(chosenBiometry, chosenBiometry ? await LocalAuthentication.authenticateAsync() : 'do not prompt')} />

Console log will print undefined, {success: false ... }

@etaiklein etaiklein added the needs validation Issue needs to be validated label Jul 30, 2020
@etaiklein etaiklein changed the title [Android][Expo-local-authentication] [Android][Expo-local-authentication] isEnrolledAsync checks types not included in supportedAuthenticationTypesAsync Jul 30, 2020
@byCedric
Copy link
Member

Hi @etaiklein, thanks for reporting this issue. Unfortunately, the isEnrolledAsync is a method from the pre-biometricprompt time. We now moved over to Google's new unified local authentication system, called biometric prompt. Google decided to not expose information if certain authentication methods are available or not. That makes fixing this close to impossible.

The whole idea of not exposing this information is to unify the UI used during the authentication. All vendors have their own version implemented, improving recognizability for these screens. The developer can now only request to authenticate, not determine what UI should be visible. We implemented a workaround for the supportedAuthenticationTypesAsync for developers to adjust possible descriptions within the biometric prompts.

@tsapeta We might want to deprecate some of these methods to make it more aligned with the biometric prompt.

Hope this helps!

@byCedric byCedric added Android LocalAuthentication and removed needs validation Issue needs to be validated labels Jul 30, 2020
@etaiklein
Copy link
Author

Hi @etaiklein, thanks for reporting this issue. Unfortunately, the isEnrolledAsync is a method from the pre-biometricprompt time. We now moved over to Google's new unified local authentication system, called biometric prompt. Google decided to not expose information if certain authentication methods are available or not. That makes fixing this close to impossible.

Thanks for the great info @byCedric. So seems like the best we can do is just handle error code 11: BIOMETRIC_ERROR_NONE_ENROLLED. And/or adjust the copy to reflect that we wont know if the user has the right biometry enrolled.

One quick follow-up question:

  1. Is there still value in checking isEnrolledAsync and/orhasHardwareAsync in addition to supportedAuthenticationTypesAsync?

i.e. Is simplifying to my demo code above to the new code below equivalent?

  try {
      LocalAuthentication.supportedAuthenticationTypesAsync().then(
        (biometryTypes: BiometryTypes) => {
          if (!biometryTypes) {
            return;
          }
          setBiometryType(chosenBiometry);
        }
      );
    } catch {
      // eslint-disable-next-line no-console
      console.error('Failed to retrieve supported biometry type');
    }

@byCedric
Copy link
Member

Adding BIOMETRIC_ERROR_NONE_ENROLLED seems like a good idea! Thanks for that. 馃槃 @tsapeta do you agree with that as well? To quickly answer your questions:

Is there still value in checking isEnrolledAsync and/or hasHardwareAsync?

I think there is still some value in checking if the device is secured, and "some sort of" local authentication can be used. It's using the Biometric.canAuthenticate, but without a certain encryption strength/authenticators. But, eventually both isEnrolledAsync and hasHardwareAsync have the identical implementation, but handles different errors, making them (almost) the same.

Is there still value in checking ... in addition to supportedAuthenticationTypesAsync?

Yes, this one is actually pretty useful when you want to customize the message your users see when authenticating. You can use different messages based on the available hardware.

Is simplifying to my demo code above to the new code below equivalent?

Yes, I think so. But the whole idea from Google is that the developer (you) don't have to worry about the used biometric authentication. So just requesting to "authenticate" should be enough for you to verify the user is protected or authenticated locally.

Hope it helps!

@byCedric byCedric reopened this Jul 30, 2020
@byCedric byCedric changed the title [Android][Expo-local-authentication] isEnrolledAsync checks types not included in supportedAuthenticationTypesAsync [local-authentication] Handle BIOMETRIC_ERROR_NONE_ENROLLED errors to account for copy changes Jul 30, 2020
@byCedric byCedric added the enhancement New feature or request label Jul 30, 2020
@tsapeta
Copy link
Member

tsapeta commented Jul 31, 2020

@byCedric Yeah, I agree with all that you said! 馃檪

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants