To make user experience smoother many apps are moving towards biometric login instead of traditional username and password based process.
By using biometric login, instead of having to remember an account username and password every time to open your app, users can just use their biometric credentials to confirm their presence and authorize access to the private content.
How Biometric Authentication works
In username-password authentication, the app sends the credentials to a server and then the server generates and returns a token for that particular user which will be used to login in the app.
In the case of biometric, things get little bit different. This time also you will have to use username-password and get a token from the server but the token will be encrypted using a secret key backed up by the user’s biometrics and then it will be stored in the memory of the app. Next time the user needs to login, instead of asking the server for the token, the stored token will be decrypted using their biometrics.
Now let’s start writing some code!
Start by creating a new project in Android studio with an Empty activity.
In your build.gradle file of the app module, add the following gradle dependency.
We will create a CryptographyManager interface to handle encryption, decryption and storage of the user token.
Here we have declared functions for initiliazation of encryption/decryption, encryption/decryption of data and storing of Ciphers with the respective parameters.
Now we will create an implementation class for this interface. In this class we will override each function and give them implementations one by one.
Firstly, we will define variables which we will be using in our implementations.
Override the getInitializedCipherForEncryption() method followed by getOrCreateSecretKey() and getCipher() functions as follows:
Firts we created a Cipher using a transformation string. Then we generated a secret key using the keyname passed as a parameter.
In the getOrCreateSecretKey() function we used Android Keystore which lets you store cryptographic keys in a container to make it more difficult to extract from the device. To generate key we used KeyGenParameterSpec.Builder to define some params. Then we generate a KeyGenerator using AES (Advanced Encryption Standard) algorithm and Android Keystore. Then we initialize the keygenerator with the defined params and return the generated key.
override getInitializedCipherForDecryption() method
In this function again we will get the cipher and secret key from the method defined above. This time we will initialize the cipher in the decryption mode using Galois/Counter Mode (GCM) mode.
override encryptData and decryptData functions.
The encryptData() function takes a string and a cipher as parameters and returns a CipherTextWrapper() (which is a data class to wrap cipher text and initialization vector that we will create next).
Similarly decryptData function takes a byte array and a cipher to decrypt and return a string.
Before moving ahead we will create a CipherTextWrapper class.
I have also override the equals() and hashcode() as recommended by Android studio.
Now let’s override the remaining functions
These functions are responsible for saving and retrieving CipherTextWrapper from SharedPreferences.
Now let’s look at the complete CryptographyManagerImpl class
To get an instance of this private CryptographyManagerImpl class, we will use the top level function
Now we will create a BiometricPromptUtils class. This file contains two functions. First is createBiometricPrompt which returns a BiometricPrompt
This function takes activity and BiometricPrompt.AuthenticationResult. We have implemented BiometricPrompt.AuthenticationCallback for various states of authentication like failure, error and success. Then we return a BiometricPrompt by passing activity,main executor and authentication callback to it.
Next we have createPromptInfo() which returns a BiometricPrompt.PromptInfo
This function takes an activity as a parameter. It is responsible for building a prompt with various params like title, subtitle, description, etc.
Next, we will be creating a Constants.kt file to define some constants.
Now we will setup login functionality with a sample user
First create a sample user object
Then we will create class to define states of login form
Then create a data class for defining result of login
Then build all business logic in LoginViewModel. In this class we will build all our logic for login with validation and create a sample user with a fake token.
Now, create a layout file activity_login.xml
Now, we will create LoginActivity and implement biometric authentication
First we will declare some global variable which will be required in future
Setup onCreate() method
Here, first we check if the device can authenticate using Biometric or not. If it can then we check if there is any CipherTextWrapper present in the memory or not. If it is present then we call for showing biometric prompt for decryption otherwise we take the user to create a biometric credential in the EnableBiometricLoginActivity class. After that we again check for CipherTextWrapper to handle username-password based login.
Next we will define showBiometricPromptForDecryption() method followed by decryptTokenFromStorage() method
Here first we will get a cipher for decryption from cryptographyManager. Then create a biometric prompt using createBiometricPromt(). Now if the authetication succeeded then get a AuthenticationResult object returned from it. Using that object we can decrypt CipherText in CipherTextWrapper and get the decrypted token and assign it to user. After that we can update the status of user as per our use case.
Define the setupForLoginWithPassword() method
This method observes on the login state and result to update the UI.
Now we will update onResume() method to check whether biometric prompt needs to be shown or not.
Now, we will implement EnableBiometricLoginActivity to enable biometric encrytion with the token.
First of all, let’s declare some global variables which will be needed in future
Now in onCreate() we will handle all the observers of login states and result to update the UI.
Upon success login showBiometricPromptForEncryption() function will be called to encrypt biometric with the token which will be dicussed next.
This function first checks if the device can authenticate using biometric or not. If it does, it creates a BiometricPrompt for encryption and upon authentication succeeded it encrypts and store the token in the sharedpreferences using encryptAndStoreToken() function.
encryptAndStoreToken() function takes AuthenticationResult as a parameter and using that it encrypts and saves toke in the memory in the form of CipherTextWrapper.
Now build and run your app. I have attached a demo of the app below.
Congrats 🎉! You have successfully implemented Biometric authentication in your app.
For complete code reference checkout this Github repository