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 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 in order to make use of the device biometrics
- Expo Secure Store in order to save the private token encrypted securely on the user device.
- 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:
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
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.
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.
...
const enableBiometrics = async () => {
...
await postBiometricKey({
biometricKey: {
publicKey: keys.public
}
});
...
};
Final Code
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:
1. User Opens App
2. Show Biometrics
Ask for biometrics confirmation
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.
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
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
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
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.
Top comments (0)