Building a React App with Biometric Authentication

Building a React App with Biometric Authentication
In today’s digital age, ensuring robust security for applications is paramount. One effective method is implementing fingerprint security using WebAuthn. This blog will walk you through adding fingerprint authentication in a Node.js application using the SimpleWebAuthn library with a React.js frontend. We will cover both frontend and backend code to give you a complete understanding of the implementation.
Prerequisites
- Basic understanding of Node.js, Express.js, and React.js.
- Familiarity with handling AJAX requests.
- A working Node.js environment.
Frontend Implementation
Let’s start by setting up the frontend. We’ll use React.js to handle the user interface for registering and authenticating fingerprints.
Setting Up React Project
First, create a new React project using Create React App:
npx create-react-app fingerprint-auth
cd fingerprint-auth
npm install @simplewebauthn/browser axiosRegistering a Fingerprint
Create a component for registering a fingerprint:
// src/RegisterFingerprint.js
import React from 'react';
import axios from 'axios';
import { startRegistration } from '@simplewebauthn/browser';
const RegisterFingerprint = () => {
const registerPasskey = async () => {
try {
const res = await axios.post('/api/auth/register-challenge');
if (res.data.code === 200) {
const authResult = await startRegistration(res.data.data);
if (authResult) {
await axios.post('/api/auth/verify-challenge', {
challenge: res.data.data.challenge,
credential: authResult,
});
alert('Fingerprint registered successfully');
}
} else {
alert(res.data.message);
}
} catch (err) {
console.error(err);
}
};
return (
<div>
<button onClick={registerPasskey}>Register Fingerprint</button>
</div>
);
};
export default RegisterFingerprint;Logging in with Fingerprint
Create a component for logging in using a fingerprint:
// src/LoginFingerprint.js
import React from 'react';
import axios from 'axios';
import { startAuthentication } from '@simplewebauthn/browser';
const LoginFingerprint = () => {
const biometricLogin = async () => {
try {
const res = await axios.post('/api/auth/login-challenge');
if (res.data.checkFingerPrint === 'false') {
alert('Please register biometric first');
return;
}
if (res.data.code === 200) {
const authResult = await startAuthentication(res.data.data);
const verifyRes = await axios.post('/api/auth/verify-login-challenge', {
challenge: res.data.challenge,
credential: authResult,
});
if (verifyRes.data.code === 200) {
window.location.href = '/dashboard';
} else {
alert(verifyRes.data.message);
}
} else {
alert(res.data.message);
}
} catch (err) {
console.error(err);
}
};
return (
<div>
<button onClick={biometricLogin}>Login with Fingerprint</button>
</div>
);
};
export default LoginFingerprint;Backend Implementation
Now, let’s move on to setting up the backend. We will use the SimpleWebAuthn library to handle the WebAuthn protocol.
Dependencies
First, install the required dependencies:
npm install @simplewebauthn/server express body-parserSetting Up Routes
Below are the backend routes for registering and verifying fingerprints.
Register Challenge Route
// routes/auth.js
const { generateRegistrationOptions, verifyRegistrationResponse } = require('@simplewebauthn/server');
const express = require('express');
const router = express.Router();
router.post('/register-challenge', async (req, res) => {
try {
const challengePayload = await generateRegistrationOptions({
rpID: 'localhost',
rpName: 'My Application',
userName: req.user.id,
});
if (challengePayload) {
res.json({ code: 200, data: challengePayload, status: 'success' });
} else {
res.json({ code: 500, message: 'Something went wrong. Please contact your administrator.', status: 'error' });
}
} catch (err) {
console.error(err);
res.status(500).json({ message: 'Internal Server Error' });
}
});
module.exports = router;Verify Challenge Route
// routes/auth.js
router.post('/verify-challenge', async (req, res) => {
try {
const verificationResult = await verifyRegistrationResponse({
expectedChallenge: req.body.challenge,
expectedOrigin: 'https://myapp.example.com',
expectedRPID: 'myapp.example.com',
response: req.body.credential,
});
if (verificationResult.verified) {
// Save the fingerprint information to the database
await saveFingerprint(req.user.id, verificationResult.registrationInfo);
res.json({ code: 200, status: 'success', message: 'Biometric Registration Successful!' });
} else {
res.json({ code: 500, message: 'Invalid user!', status: 'error' });
}
} catch (err) {
console.error(err);
res.status(500).json({ message: 'Internal Server Error' });
}
});Login Challenge Route
// routes/auth.js
const { generateAuthenticationOptions, verifyAuthenticationResponse } = require('@simplewebauthn/server');
router.post('/login-challenge', async (req, res) => {
try {
const user = await getUser(req.user.id); // Fetch the user from the database
if (!user || !user.fingerprint) {
return res.json({ code: 500, message: 'Please register biometric first', status: 'error', checkFingerPrint: 'false' });
}
const options = await generateAuthenticationOptions({
rpID: 'myapp.example.com',
userVerification: 'preferred',
});
res.json({ code: 200, data: options, challenge: options.challenge, status: 'success', checkFingerPrint: 'true' });
} catch (err) {
console.error(err);
res.status(500).json({ message: 'Internal Server Error' });
}
});Verify Login Challenge Route
// routes/auth.js
router.post('/verify-login-challenge', async (req, res) => {
try {
const user = await getUser(req.user.id); // Fetch the user from the database
if (!user || !user.fingerprint) {
return res.json({ code: 500, message: 'Fingerprint not found', status: 'error' });
}
const result = await verifyAuthenticationResponse({
expectedChallenge: req.body.challenge,
expectedOrigin: 'https://myapp.example.com',
expectedRPID: 'myapp.example.com',
response: req.body.credential,
authenticator: JSON.parse(user.fingerprint),
});
if (result.verified) {
res.json({ code: 200, status: 'success', message: 'Login Successful!' });
} else {
res.json({ code: 500, message: 'Invalid user!', status: 'error' });
}
} catch (err) {
console.error(err);
res.status(500).json({ message: 'Internal Server Error' });
}
});Conclusion
Implementing fingerprint security in your Node.js application using WebAuthn is a robust way to enhance security. By following the steps outlined in this guide, you can add fingerprint authentication to your application, providing users with a secure and convenient login method.
Make sure to customize the dummy data, URLs, and database interactions to fit your application’s specific needs. Happy coding!