SelfLender / react-native-biometrics

React Native module for iOS and Android biometrics
MIT License
653 stars 222 forks source link

SimplePromt does not work for Android 10 and lower (fix with Patch-Package) #244

Open Ainias opened 1 year ago

Ainias commented 1 year ago

Hi! 👋

Firstly, thanks for your work on this project! 🙂

I've encountered a problem where the device credentials are not working when using simple prompt with android 10 and below. I checked the source code and found that it is not supported by the underlying library if used with strong biometrics. This is necessary if you use the crypto adapter. If you don't use it, weak biometrics is working correctly with device credentials.

As I only need the simplePrompt, I've created a patch to let it work with devices below android 10 and no finger print: Here is the diff that solved my problem:

Today I used patch-package to patch react-native-biometrics@3.0.1 for the project I'm working on.

diff --git a/node_modules/react-native-biometrics/android/src/main/java/com/rnbiometrics/ReactNativeBiometrics.java b/node_modules/react-native-biometrics/android/src/main/java/com/rnbiometrics/ReactNativeBiometrics.java
index 624ecd9..73655b7 100644
--- a/node_modules/react-native-biometrics/android/src/main/java/com/rnbiometrics/ReactNativeBiometrics.java
+++ b/node_modules/react-native-biometrics/android/src/main/java/com/rnbiometrics/ReactNativeBiometrics.java
@@ -182,24 +182,34 @@ public class ReactNativeBiometrics extends ReactContextBaseJavaModule {
         }
     }

-    private PromptInfo getPromptInfo(String promptMessage, String cancelButtonText, boolean allowDeviceCredentials) {
-        PromptInfo.Builder builder = new PromptInfo.Builder().setTitle(promptMessage);
+     private PromptInfo getPromptInfo(String promptMessage, String cancelButtonText, boolean allowDeviceCredentials, boolean ignoreAndroidRestrictions) {
+            PromptInfo.Builder builder = new PromptInfo.Builder().setTitle(promptMessage);

-        builder.setAllowedAuthenticators(getAllowedAuthenticators(allowDeviceCredentials));
+            builder.setAllowedAuthenticators(getAllowedAuthenticators(allowDeviceCredentials, ignoreAndroidRestrictions));

-        if (allowDeviceCredentials == false || isCurrentSDK29OrEarlier()) {
-            builder.setNegativeButtonText(cancelButtonText);
+            if (allowDeviceCredentials == false || (isCurrentSDK29OrEarlier() && !ignoreAndroidRestrictions)) {
+                builder.setNegativeButtonText(cancelButtonText);
+            }
+
+            return builder.build();
         }

-        return builder.build();
-    }
+        private PromptInfo getPromptInfo(String promptMessage, String cancelButtonText, boolean allowDeviceCredentials) {
+           return getPromptInfo(promptMessage, cancelButtonText, allowDeviceCredentials, false);
+        }

-    private int getAllowedAuthenticators(boolean allowDeviceCredentials) {
-        if (allowDeviceCredentials && !isCurrentSDK29OrEarlier()) {
-            return BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
+        private int getAllowedAuthenticators(boolean allowDeviceCredentials) {
+            return getAllowedAuthenticators(allowDeviceCredentials, false);
+        }
+
+        private int getAllowedAuthenticators(boolean allowDeviceCredentials, boolean ignoreAndroidRestriction) {
+            if (allowDeviceCredentials && (!isCurrentSDK29OrEarlier())) {
+                return BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
+            } else if (allowDeviceCredentials && ignoreAndroidRestriction){
+                return BiometricManager.Authenticators.BIOMETRIC_WEAK | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
+            }
+            return BiometricManager.Authenticators.BIOMETRIC_STRONG;
         }
-        return BiometricManager.Authenticators.BIOMETRIC_STRONG;
-    }

     private boolean isCurrentSDK29OrEarlier() {
         return Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q;
@@ -222,7 +232,7 @@ public class ReactNativeBiometrics extends ReactContextBaseJavaModule {
                                 Executor executor = Executors.newSingleThreadExecutor();
                                 BiometricPrompt biometricPrompt = new BiometricPrompt(fragmentActivity, executor, authCallback);

-                                biometricPrompt.authenticate(getPromptInfo(promptMessage, cancelButtonText, allowDeviceCredentials));
+                                biometricPrompt.authenticate(getPromptInfo(promptMessage, cancelButtonText, allowDeviceCredentials, true));
                             } catch (Exception e) {
                                 promise.reject("Error displaying local biometric prompt: " + e.getMessage(), "Error displaying local biometric prompt: " + e.getMessage());
                             }

This issue body was partially generated by patch-package.

aniket-holcim commented 1 year ago

Hi @Ainias Can you share me this file ReactNativeBiometrics.java. I am also getting issue on android 9

Ainias commented 1 year ago

Hi @aniket-holcim We had another issue, namely that on some Android 10 devices the library would authenticate with finger print only, but the devices did not have finger print. Therefore it would always throw an error.

We forked the library and added a function for the old keyguardAuthentication. Now whenever we get an error 'Hardware not present' we do an keyguardAuthentication (We catch the error on the JS-side).

Our fork is available here: https://github.com/churchtools/react-native-biometrics

Our authentication-Code in JS is basically the following:


biometrics
        .simplePrompt({ promptMessage: title + '\n\n' + reason })
        .then(async ({ success, error }) => {
            if (!success && !isIOS && error === 'Hardware not present') {
                const { success: keyguardSuccess, error: keyguardError } = await biometrics.keyguardAuthentication({
                    promptMessage: title,
                    promptReason: reason
                });
                                 success = keyguardSuccess;
            }
                        // Check for success and errors
        }).catch(e => {
                      // Error handling of errors thrown
                })