[LocalAuthentication] FaceID immediately falls back to pin code screen

Please provide the following:

  1. SDK Version: 42
  2. Platforms(Android/iOS/web/all): iOS
  3. Add the appropriate “Tag” based on what Expo library you have a question on.

Hi all, I have recently implemented Local Authentication for my app, with success!
Or at least, I thought so…

I tested the code on both Android (Samsung Galaxy A50, Samsung Galaxy s10, Xiaomi Redmi 9t) and iOS (iPhone 6, iPhone 8) and everything worked as expected, i.e. the fingerprints being scanned correctly.

Now, I recently got my hands on an iPhone X, which I used to test the Face ID login (since it does not contain a fingerprint sensor), but without success. When triggering the login, nothing pops up to indicate face id will be scanning. Instead I get immediately prompted to enter my device’s pin code, which is of course the fallback option for when face id can’t scan the user’s face.

And then my search began.

First of all, I couldn’t test the face id itself locally, because it isn’t available in the Expo Go app.
What I could test locally:

  1. LocalAuthentication.hasHardwareAsync(), which returns true
  2. LocalAuthentication.isEnrolledAsync(), which returns true
  3. LocalAuthentication.supportedAuthenticationTypesAsync(), which returns true

Which I also expected, as I didn’t receive an error message stating one of those things was wrong.

Next, I also checked my iOS settings, to check if there’s somewhere a setting preventing me from using Face ID for all my apps, or this particular app. But I have found no such thing.

Following, I connected the device with a Macbook to read out all of its logs on my production build. But there I only found the following when triggering the Face ID login:

standaard 13:32:14.745229+0200 coreauthd -[ContextProxy evaluatePolicy:options:uiDelegate:originator:reply:]_block_invoke → (null), Error Domain=com.apple.LocalAuthentication Code=-1004 “User interaction is required.” UserInfo={BiometryType=2, BiometryDatabaseHash={length = 32, bytes = 0x0fb65ad8 fd8abfe0 a7d5d53a 4ef9fda4 … 06e80935 568f312b }, AvailableMechanisms=(
7
), NSLocalizedDescription=User interaction is required.} on
standaard 13:32:14.745831+0200 ComMedditionCarey -[LAClient evaluatePolicy:options:uiDelegate:reply:]_block_invoke → (null), Error Domain=com.apple.LocalAuthentication Code=-1004 “User interaction is required.” UserInfo={BiometryType=2, BiometryDatabaseHash={length = 32, bytes = 0x0fb65ad8 fd8abfe0 a7d5d53a 4ef9fda4 … 06e80935 568f312b }, AvailableMechanisms=(
7
), NSLocalizedDescription=User interaction is required.} on
standaard 13:32:14.745968+0200 ComMedditionCarey -[LAContext canEvaluatePolicy:error:]_block_invoke → YES on
standaard 13:32:14.746213+0200 ComMedditionCarey -[LAContext dealloc] , <LAClient: 0x2819e9e00> on

Don’t know what to think of this, but seems like this is correct, as the user should interact with the device at that point.

Last thing that popped into my head was that there would be something wrong with the permissions, since I never saw a pop-up to ask for permission to use the Face ID. Most of the time, e.g. with camera, when opening the camera for the first time, first you will receive a permission request, after which you can use the camera. I have seen no such thing for the Face ID.
I remembered to have added a permission, specifically for Face ID, to my app.json. And indeed, it was there.

// app.json
{
  ...
  "ios": {
    ...
    "infoPlist": {
      "CFBundleAllowMixedLocalizations": true
    }
  },
  "locales": {
    "en": "./lang/en.json",
    "fr": "./lang/fr.json",
    "nl": "./lang/nl.json"
  },
  ...
}

// en.json
{
  ...
  "NSFaceIDUsageDescription": "Give this app permission to use your Face ID to log in.",
  ...
}

And it was at that moment that I didn’t know what else to try.

Has anyone else had this issue? I read a topic that seems to have have a similar problem (Expo LocalAuthentication), but it never has been answered.

Also, I couldn’t make a reproducible demo, because it can’t be tested on Expo Go.

Thanks for the help!

Tom

Our code looks like this:

export async function enableBiometricAuthentication() {
  const hasHardware = await LocalAuthentication.hasHardwareAsync();
  const isEnrolled = await LocalAuthentication.isEnrolledAsync();
  const supportedAuthenticationTypes = await LocalAuthentication.supportedAuthenticationTypesAsync();

  if (!isEnrolled || !supportedAuthenticationTypes.length) {
    Alert.alert(
      i18n.t('error.oops'),
      i18n.t('chatbotRegistration.noSavedBiometrics')
    );
    return false;
  } else if (!hasHardware) {
    Alert.alert(
      i18n.t('error.oops'),
      i18n.t('chatbotRegistration.noBiometricAuthenticationSupport')
    );
    return false;
  }

  // Authenticate before user can use biometric authentication
  const options = {
    promptMessage: i18n.t('login.authenticate'),
    cancelLabel: i18n.t('general.cancel')
  };

  const isAuthenticated = await LocalAuthentication.authenticateAsync(options);
  if (!isAuthenticated.success) {
    Alert.alert(
      i18n.t('error.oops'),
      i18n.t('chatbotRegistration.authenticationFailed')
    );
    return false;
  }

  AsyncStorage.setItem('biometricAuthentication', 'true');
  return true;
}

You should check which types of authentication are supported exactly, not just if supportedAuthenticationTypes is truthy.

Wonder if this is similar to Expo Local Authentication issue if you have more than 1 apps installed on IOS - #10 by mfaizan2

Thanks for the response!
I also checked the types that were actually there, and on the iPhone X, it was “2”. So that also is correct.

About the other topic. I also had a look at that one, but unfortunately, I only run one expo app on that iOS device.

I only run one expo app on that iOS device.

What do you mean? This issue cannot be tested on the Expo app, since FaceID is not supported there

I mean one production app, created with Expo.

I see! understood

Sounds like this might be a bug- could you open a bug report here with a reproducible demo?

I’d love to. But how can I make a reproducable demo? Can’t be tested in the Expo client… :smile:

If someone wants to see it in the app I created, I can make them a test account. Maybe that’s an option? What do you think?