Setting Up Biometric Login on Your React Native App
Tags: reactnative, expo, mobile, javascript
Published: 2023-05-29 02:37:25

#Introduction

Biometrics login is a convenient feature to have on your app, this convenience not only saves time but also improves user satisfaction and engagement. With biometric login, mobile app users can enjoy a seamless and secure authentication experience, making it a win-win for both security-conscious individuals and those seeking a hassle-free login solution.

On this post we are going to setup a biometric login feature that works appart from the normal email + password login. 

## Considerations
Check out the excellent [Alto Engineering blog post](https://alto.com/blog/post/part-1-implementing-biometric-login) about considerations for Implementing Biometric Login to learn about pros and cons of different ways to use a mobile device's biometrics to authenticate a user, with varying degrees of security.

On this post we are going to implement the **Create a separate authentication mechanism for biometrics** way on a expo RN app.

> Note: We will only see the front-end implementation.

### Setup
We will need to add some libraries to the project:
- [Expo Local Authentication](https://docs.expo.dev/versions/latest/sdk/local-authentication/) in order to make use of the device biometrics
- [Expo Secure Store](https://docs.expo.dev/versions/latest/sdk/securestore/) in order to save the private token encrypted securely on the user device.
- [React Native RSA Native](https://github.com/amitaymolko/react-native-rsa-native) in order to sign the payload with the private key.


## Flows
### First Login

For the first login we are going to require strictly the email and password, the flow is as follows:

![Login Flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b2hgktmozy53pf0sf6qq.png)

#### 1. Login
The user must login with his credentials the first time in order to assure he is the owner of the account.
#### 2. Show Biometrics
Ask for biometrics access so we can setup the biometrics login
```typescript
import {
    authenticateAsync
} from 'expo-local-authentication';

const askForBiometrics = async () => 
    await authenticateAsync({
        disableDeviceFallback: true,
        promptMessage: "Enable biometrics login",
        cancelLabel: "Not now",
    });
```
#### 3. User confirms biometrics
#### 4. Create a key pair
Create the public and private key, save the private key on the device in order to sign a payload, the public key will be use in the backend.

```typescript
import {
    authenticateAsync
} from 'expo-local-authentication';
import {
    RSA
} from 'react-native-rsa-native';
...

const enableBiometrics = async () => {
    const biometricsResult = await askForBiometrics();
    if (biometricsResult?.success) {
        //Create Key pair
        const keys = await RSA.generateKeys(1024);
        // Use secure Store as it provides a way to encrypt and securely store key–value pairs locally on the device.
        await SecureStore.setItemAsync(
            "USER_BIOMETRIC_KEY",
            keys.private
        );
    }
};
```
#### 5. Send the public key
User public key will be saved on the backend in order to verify the signature.
```typescript
...
const enableBiometrics = async () => {
    ...
    await postBiometricKey({
        biometricKey: {
            publicKey: keys.public
        }
    });
    ...
};

```

Final Code
```typescript
import {
    authenticateAsync
} from 'expo-local-authentication';
import {
    RSA
} from 'react-native-rsa-native';

const askForBiometrics = async () => 
    await authenticateAsync({
        disableDeviceFallback: true,
        promptMessage: "Enable biometrics login",
        cancelLabel: "Not now",
    });

const enableBiometrics = async () => {
    const biometricsResult = await askForBiometrics();
    if (biometricsResult?.success) {
        const keys = await RSA.generateKeys(1024);
        await SecureStore.setItemAsync(
            "USER_BIOMETRIC_KEY",
            keys.private
        );
        await postBiometricKey({
            biometricKey: {
                publicKey: keys.public
            }
        });
    }
};
```

### Next Logins
For next logins we will ask for biometrics if they have enabled it:

![Biometrics flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/emsuvovtbe0olwy43z59.png)

#### 1. User Opens App
#### 2. Show Biometrics
Ask for biometrics confirmation
```typescript
import {
    authenticateAsync
} from 'expo-local-authentication';

const authenticate = async () => {
    const biometricsResult = await authenticateAsync({
        disableDeviceFallback: true,
        promptMessage: "Login with biometrics",
        cancelLabel: "Cancel",
    });
}
```
#### 3. User Confirms Biometrics
#### 4. Creates a signature with private key
Sign a payload with the private key we saved on the user device using SecureStore.

```typescript
import * as SecureStore from 'expo-secure-store';
import dayjs from 'dayjs';

const generateSignature = async () => {
    // Get private key from secure store
    const privateKey = await SecureStore.getItemAsync(
        "USER_BIOMETRIC_KEY"
    );
    const payload = {
        // You can add info you want on the payload
        // DO NOT SEND THE PRIVATE KEY
        // 5 minutes expiration
        exp: dayjs().add(5, 'minutes').unix(),
    };

    if (biometrics) {
        const signature = await sign(privateKey, payload);
        return signature;
    }
};
```
**Sign payload**

```typescript
import {
    RSA
} from 'react-native-rsa-native';

export async function sign(privateKey: string, payload: any) {
    const signature = await RSA.signWithAlgorithm(
        payload,
        privateKey,
        'SHA256withRSA'
    );
    return signature;
}
```

#### 5. Send signature to API
```typescript
 const sendSignature = async () => {
    const signature = await generateSignature();
    if (signature) {
        // POST to API
        await postSignature({
            signature
        });
    }
};
```
#### 6. API validates signature
#### 7. API returns a session token

**Final Code**
```typescript
import {
    RSA
} from 'react-native-rsa-native';
import * as SecureStore from 'expo-secure-store';
import dayjs from 'dayjs';
import {
    authenticateAsync
} from 'expo-local-authentication';

const authenticate = async () => {
    const biometricsResult = await authenticateAsync({
        disableDeviceFallback: true,
        promptMessage: "Login with biometrics",
        cancelLabel: "Cancel",
    });
    if(biometricsResult?.success){
      await sendSignature();
    }
}

const sendSignature = async () => {
    const signature = await generateSignature();
    if (signature) {
        await postSignature({
            signature
        });
    }
};

const generateSignature = async () => {
    const biometrics = await SecureStore.getItemAsync(
        "USER_BIOMETRIC_KEY"
    );
    const payload = {
        exp: dayjs().add(5, 'minutes').unix(),
    };

    if (biometrics) {
        const signature = await sign(biometrics, payload);
        return signature;
    }
};


export async function sign(privateKey: string, payload: any) {
    const signature = await RSA.signWithAlgorithm(
        payload,
        privateKey,
        'SHA256withRSA'
    );
    return signature;
}

```
---
## Conclusion
And thats it! Now we have different flows isolated for login with email + password and biometrics.

Implementing face and fingerprint ID login offers evident advantages for both app users and the overall application. If you're contemplating integrating biometric login into your app, this blog post aims to provide you with valuable insights and guidance to navigate the process successfully.