“Due diligence is an investigation, audit, or review performed to confirm facts or details of a matter under consideration.”
There are several criteria you have to consider. Biometrics is usually a feature utilized in security-heavy focused applications to authenticate users; for example, your bank will likely sign your account out — either immediately once you close the app or in a very short time — to protect your finances should someone get control of your device. Therefore, security will surely play an important role in your decision-making process. Hopefully, every step you take in this direction will make it harder for a potential attacker.
Biometrics is also meant to make life easier for users so that they don’t need to input their login credentials every time they need to authenticate themselves. That is why we need to look for a user-friendly solution, meaning the libraries we decide to implement should be actively maintained and offer the latest biometrics standards expected by users.
Finally, development time is money, so if the client's budget is limited, you have to take it into consideration and offer the best value solution.
What Are the Options?
Once you start researching potential solutions, you will realize that you face a dilemma regarding where the big part of biometrics implementation should take place. On the backend, the mobile app or actually somewhere in between?
Mobile App Implementation
This approach is a very pragmatic and fast solution especially in cases when one does not want to rely on your backend counterpart’s implementation. There can be many reasons: a limited client’s budget, time sensitivity when the backend team is focused on another feature or just no need for a complex solution.
How does it work? Upon the user's first signup or login, we can store the user's credentials securely on the mobile device (iOS Keychain or Android Keystore). To achieve this, we can utilize, for example, the react-native-keychain library that allows us to hide the sensitive data behind biometrics authentication. Once a user's authenticated session expires, we offer a biometrics challenge and, if successfully passed, we retrieve the stored credentials to initiate a login API call.
This solution is not very sophisticated and therefore has some drawbacks. For example, if the mobile application complements a website where the user can change or reset his/her password, the next mobile login request after biometrics authentication will fail and we will need to ask for the user's credentials again. Security might also be quite a concern because the user’s credentials are stored on a mobile device, which is not considered best practice. The react-native-keychain library warns that the data could even be easily decrypted on devices running older than the Android 6.0 operating system. Use this option for fast implementation and simple flows where security is not the highest priority.
Having backend implementation allows us to utilize an extra layer of security, which is desirable for applications that want to go the extra mile in securing users’ data.
The solution that offers itself is utilizing an RSA algorithm by creating a public/private key pair on the device when biometric authentication gets enabled. One way is to keep the private key securely on the device while the public key travels to the backend and is saved under the particular user. Note that one user can have multiple devices, meaning multiple public keys.
As the diagram shows, upon biometric authentication (step 3) the private key signs a piece of data, sends it to the backend (4) and then the backend uses the paired public key to verify the data and sends back the result (5). In case of a login action, it will return an access token to initiate a new session. Alternatively, we could generate key pairs on the backend and make the mobile app request the public key. Remember that the private key should not travel.
This way, we shift a big portion of work to the backend, which can lead to much longer delivery time and cost for the client. On the other hand, this approach can be reused in other parts of the application, like approving financial transactions with biometrics — unlike in the first option, where credentials can only be used for a single purpose.
In React Native, this solution is readily available with the react-native-biometrics library, which is, unfortunately, not actively maintained (last commit 2/2020) and its biometrics part does not support the latest standards, such as fallback to passcode when Face ID fails. To offer the users the best experience, we can implement our approach with two separate libraries: expo-local-authentication for the biometrics part and react-native-rsa-native for the RSA algorithm. Use this option for a more security-focused approach when backend resources are available and the deadline is not a week.
If our application utilizes refresh tokens, we can smartly combine the two previous options together. Upon signup or login, we can store a new refresh token securely on the device using the react-native-keychain library mentioned in the mobile app implementation. Once our session expires, we prompt the user to retrieve the refresh token stored behind biometrics authentication. If the user passes the challenge, we use the refresh token to call the backend and refresh the session/get a new access token.
The big security advantage compared to the first solution is that we don't have to keep the login credentials stored on the device. Also, backend verification of the refresh token provides certain security aspects we implemented with the second solution. Overall, this approach is very practical to implement, especially in the case when refresh tokens are already utilized. The only downside is that it has a single purpose of refreshing the user's session.
While implementing biometric authentication on React Native is definitely not rocket science, time needs to be invested into due diligence to find the best solution that balances important criteria such as security, cost-effectiveness and user experience.