Skip to content

Biometric authentication

namnh-0652 edited this page May 25, 2023 · 5 revisions

One method of protecting sensitive information or premium content within your app is to request biometric authentication, such as using face recognition or fingerprint recognition. This guide explains how to support biometric login flows in your app.

Note: This library is a small wrapper on the latest androidx biometric library to support you quickly setup secure biometric authentication for your app. If you want more controls then you should combine to read this library's code and follow the official androidx biometric docs to implement your feature.

Before you get started, I recommend you to read these articles below to understand what is biometric authentication, how it work and what we need to do to implement the biometric authentication process.

Features

  • Checking supported biometric devices
  • Create BiometricPrompt
  • Verify Biometric, encrypt and save authentication data for next login
  • Verify Biometric and decrypt saved authentication data for login process

Installation

Prerequisites

  • Android 23+

Steps

From project build.gradle (or settings.gradle), add Jitpack maven

repositories {
    maven { url 'https://jitpack.io' }
}

Add these dependencies to your app/build.gradle

dependencies {
    implementation "com.github.sun-asterisk.tech-standard-android-auth:core:${latest_version}"
    implementation "com.github.sun-asterisk.tech-standard-android-auth:biometricauth:${latest_version}"
    implementation "com.github.sun-asterisk.tech-standard-android-auth:credentialsauth:${latest_version}" // Optional, you can use your own credentials authentication method.
}

Usage

Init the biometric authentication configs

From your Application class, call initBiometricAuth method.

initBiometricAuth(allowDeviceCredentials = true) {
    keystoreAlias = "sample_key_name"
    keySize = 256
    algorithm = KeyProperties.KEY_ALGORITHM_AES
    blockMode = KeyProperties.BLOCK_MODE_CBC
    padding = KeyProperties.ENCRYPTION_PADDING_PKCS7
}

Checking supported biometric devices

To check the device has supported biometric or not

BiometricAuth.isBiometricAvailable() // support and enrolled
BiometricAuth.isBiometricUnAvailable() // not support 
BiometricAuth.isBiometricInsecure() // support but insecure
BiometricAuth.isBiometricNotEnrolled() // support but not enrolled

Create BiometricPrompt

This help when you prompt the clear biometric description to user when they want to enable biometric authentication or use biometric authentication for login

BiometricAuth.createPromptInfo(
   context = requireContext(),
   title = "Biometric Authentication Sample",
   subtitle = "Enable biometric authentication",
   description = "Please complete biometric to enable biometric authentication",
   confirmationRequired = false,
   negativeTextButton = "Cancel",
)

Verify Biometric, encrypt and save authentication data for next login

To show biometric prompt to user when they want to enable biometric authentication for the next login.

BiometricAuth.processBiometric(
   fragment = this,
   mode = BiometricMode.ENCRYPT, // encrypt mode
   promptInfo = getBiometricPrompt(),
) { result ->
    // handle result from biometric verification
}

When the result is success, you will need to get the cipher from result to encrypt your data (which is used to verify for the next login, it can be a token, credentials, or an secret key which to confirm with server... this is decided by your application business), then save the encrypted result

if (result is BiometricResult.Success) {
   val cipher = result.getCipher() // get the cipher
   val data = BiometricAuth.encryptData(YOUR_AUTHENTICATION_DATA, cipher) // encrypt data
   BiometricAuth.saveEncryptedData(data) // save encrypted data
}
or more simple
if (result is BiometricResult.Success) {
   val cipher = result.getCipher() // get the cipher
   val data = BiometricAuth.encryptAndPersistAuthenticationData(YOUR_AUTHENTICATION_DATA, cipher) {
      // TODO: Unrecoverable error case.
   }
}

When the result is failed, you will need to check and try to resolve each case of error.

// This is an example, you can handle error by your requirement
// biometric is locked out when attempts failed many times
if (result is BiometricResult.Error) {
   when {
       result.isBiometricLockout() => // TODO: notify to user
       result.isKeyInvalidatedError() => // TODO: a new biometric is added or all biometrics are removed => should login to re-enable biometric
       else => // TODO: other error cases
   
}

Verify Biometric and decrypt saved authentication data for login process

In the next login, user can present their biometric data to do login process. You need to ask user to do that

val biometricPrompt = BiometricAuth.createPromptInfo(...) // see create BiometricPrompt
BiometricAuth.processBiometric(
    fragment = this,
    mode = BiometricMode.DECRYPT, // decrypt mode
    promptInfo = biometricPrompt,
) { result ->
    // handle result from biometric verification
}

When the result is success, you will need to get the cipher from result to decrypt your last saved encrypted data. Use this data to verify and confirm that this user can be logged in now (maybe verify with your server, depends on your business).

if (result is BiometricResult.Success) {
   val clazz = YOUR_AUTHEN_CLASS::class.java
   val cipher = result.getCipher() // get the cipher
   val savedData = BiometricAuth.getEncryptedData() // get the encrypted data
   val authenData = BiometricAuth.decryptData(savedData.ciphertext, cipher, clazz) // decrypted data
   // verify authenData to confirm that user can be logged in
}

Like encrypt mode when the result is failed, you will need to check and try to resolve each case of error.

Documentation

Flow for biometric authentication

flowchart TD
    A(Open App) --> B(Login Screen)
    B --> C{Biometrics<br>Enabled?}
    C --> |Yes| E(Biometric process)
    C --> |No| D{Enable<br>Biometrics}
    D --> |No| G(Send<br>username/password<br>to server)
    D --> |Yes| I(Send<br>username/password<br>to server)
    G --> H(Server returns<br>tokens)
    H --> Z(Login Completed)
    I --> J(Server returns<br>tokens)
    J --> K(Biometric process)
    K --> |Success| L(Unlock and gets<br>key from keystore)
    L --> O(Encrypt token<br>and store it)
    O --> Z
    E --> |Success| P(Unlock and gets<br>key from keystore)
    P --> F(Get encrypted token<br>from storage)
    F --> M{Decrypt token}
    M --> |No| Y(End)
    M --> |Yes|N(Use token<br>as logged in)
    N-->Z
    Z --> Y
Loading

Basically, the simplest biometric authentication flow is declared below:

  • When enable biometric authentication, you will need to login via credentials and get the Token (or anything your app and server allow that will use for verifying that user is logged in).
  • Do encrypt the token and store it in your storage (shared preferences or Sqlite or any where) for the next login
  • In the next login, when user presents their biometric success, retrieve the saved encrypted token from storage and verify it with server.