user29604802
user29604802

Reputation: 21

react native key chain error- Cannot read property 'setGenericPasswordForOptions' of null

I need to implement face id in iOS and finger print in android after signup process. Means, I need the app to first check for face id recognition feature in the device and if it seems that feature not available, then it should check for fingerprint recognition. So, through one of these methods, the app should set up authentication - The device should store the credentials on the device encrypted on the device keychain.

My workflow is like this: Signup process becomes successful and i am getting the alert "Account created successfully!". then it shows the popup asking "Would you like to enable faceId/ finger print authentication?". Upon clicking yes, it opens the finger print sensor in android phone. After i keep my finger on sensor, i got the error alert "Could not enable Face ID. Please ensure it is set up on your device." as per the error alert given in my signup screen. The same is the case for iPhone as well.

> 
> Error : //console messages  (android phone) 
                                                                          (NOBRIDGE) LOG  Biometric hardware available: true
>  (NOBRIDGE) LOG  Biometrics enrolled: true
>  (NOBRIDGE) LOG  Supported biometric types: [1]
>  (NOBRIDGE) LOG  Biometric hardware available: true
>  (NOBRIDGE) LOG  Biometrics enrolled: true
>  (NOBRIDGE) LOG  Supported biometric types: [1]
>  (NOBRIDGE) ERROR  Biometric setup failed: [TypeError: Cannot read property 'setGenericPasswordForOptions' of null]
>  (NOBRIDGE) ERROR  Biometric setup error: [TypeError: Cannot read property 'setGenericPasswordForOptions' of null]   
                                            
> //console messages  (ios phone)                             
                                       
> (NOBRIDGE) LOG  Biometric hardware available: true
>  (NOBRIDGE) LOG  Biometrics enrolled: true
>  (NOBRIDGE) LOG  Supported biometric types: [2]
>  (NOBRIDGE) LOG  Biometric hardware available: true
>  (NOBRIDGE) LOG  Biometrics enrolled: true
>  (NOBRIDGE) LOG  Supported biometric types: [2]
>  (NOBRIDGE) ERROR  Biometric setup failed: [TypeError: Cannot read property 'setGenericPasswordForOptions' of null]
>  (NOBRIDGE) ERROR  Biometric setup error: [TypeError: Cannot read property 'setGenericPasswordForOptions' of null]

BiometricAuth.js

import * as LocalAuthentication from 'expo-local-authentication';
import * as Keychain from 'react-native-keychain';
import { Platform } from 'react-native';

export const BiometricAuth = {
  // Check what type of biometric authentication is available
  checkBiometricSupport: async () => {
    try {
      const compatible = await LocalAuthentication.hasHardwareAsync();
      console.log('Biometric hardware available:', compatible);

      if (!compatible) {
        return { 
          supported: false, 
          error: 'No biometric hardware available on this device.' 
        };
      }

      const enrolled = await LocalAuthentication.isEnrolledAsync();
      console.log('Biometrics enrolled:', enrolled);

      if (!enrolled) {
        return { 
          supported: false, 
          error: 'No biometrics have been enrolled on this device.' 
        };
      }

      const types = await LocalAuthentication.supportedAuthenticationTypesAsync();
      console.log('Supported biometric types:', types);

      const hasFaceId = types.includes(LocalAuthentication.AuthenticationType.FACIAL_RECOGNITION);
      const hasFingerprint = types.includes(LocalAuthentication.AuthenticationType.FINGERPRINT);

      return {
        supported: true,
        faceIdAvailable: hasFaceId,
        fingerprintAvailable: hasFingerprint,
        preferredMethod: hasFaceId ? 'faceId' : hasFingerprint ? 'fingerprint' : null
      };
    } catch (error) {
      console.error('Biometric support check failed:', error);
      return { 
        supported: false, 
        error: error.message 
      };
    }
  },

  // Enable biometric authentication and store credentials
  enableBiometric: async (username, password) => {
    try {
      const biometricSupport = await BiometricAuth.checkBiometricSupport();

      if (!biometricSupport.supported) {
        throw new Error('Biometric authentication is not available on this device');
      }

      // Set appropriate prompt message based on available method
      const promptMessage = Platform.select({
        ios: biometricSupport.faceIdAvailable ? 'Enable Face ID login' : 'Enable Touch ID login',
        android: 'Enable fingerprint login',
        default: 'Enable biometric login'
      });

      // Authenticate with available biometric method
      const result = await LocalAuthentication.authenticateAsync({
        promptMessage,
        disableDeviceFallback: false, // Allow fallback to device passcode
        fallbackLabel: 'Use passcode' // Label for fallback button
      });

      if (result.success) {
        // Store credentials in keychain
        await Keychain.setGenericPassword(username, password);
        return true;
      }
      return false;
    } catch (error) {
      console.error('Biometric setup failed:', error);
      throw error;
    }
  },

  // Authenticate using biometrics and retrieve credentials
  authenticateWithBiometric: async () => {
    try {
      const biometricSupport = await BiometricAuth.checkBiometricSupport();

      if (!biometricSupport.supported) {
        throw new Error('Biometric authentication is not available');
      }

      const promptMessage = Platform.select({
        ios: biometricSupport.faceIdAvailable ? 'Log in with Face ID' : 'Log in with Touch ID',
        android: 'Log in with fingerprint',
        default: 'Log in with biometrics'
      });

      const result = await LocalAuthentication.authenticateAsync({
        promptMessage,
        disableDeviceFallback: false,
        fallbackLabel: 'Use passcode'
      });

      if (result.success) {
        const credentials = await Keychain.getGenericPassword();
        if (credentials) {
          return {
            success: true,
            username: credentials.username,
            password: credentials.password
          };
        }
        throw new Error('No credentials stored');
      }
      return { success: false };
    } catch (error) {
      console.error('Biometric authentication failed:', error);
      throw error;
    }
  },

  // Remove stored biometric credentials
  removeBiometric: async () => {
    try {
      await Keychain.resetGenericPassword();
      return true;
    } catch (error) {
      console.error('Failed to remove biometric credentials:', error);
      throw error;
    }
  }
};

**AndroidManifest.xml **

<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />

SignupScreen.js

import { BiometricAuth } from "../components/BiometricAuth";

const enableBiometric = async () => {
    try {
      const success = await BiometricAuth.enableBiometric(userName, password);
      if (success) {
        Alert.alert(
          "Success", 
          `${biometricType === 'faceId' ? 'Face ID' : 'Fingerprint'} has been enabled successfully!`
        );
        setBiometricVisible(false);
      } else {
        Alert.alert("Error", "Failed to enable biometric authentication. Please try again.");
      }
    } catch (error) {
      console.error("Biometric setup error:", error);
      Alert.alert(
        "Error",
        "Could not enable biometric authentication. Please ensure it is set up on your device."
      );
    }
  };

I have installed expo-local-authentication package & react-native-keychain. Tried troubleshooting steps like uninstalling & reinstalling dependencies , clearing cache & rebuilding app etc.. nothing works... any help is much appreciated..

Upvotes: 2

Views: 45

Answers (1)

sergio.nava
sergio.nava

Reputation: 195

this may not answer the question, but if you are using expo the suggested way is to use SecureStore https://docs.expo.dev/versions/latest/sdk/securestore/

Upvotes: 0

Related Questions