Add Biometric authentication in Node JS

Add Biometric authentication in Node JS
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. We will cover both frontend and backend code to give you a complete understanding of the implementation.
Prerequisites
- Basic understanding of Node.js and Express.js.
- Familiarity with jQuery for handling AJAX requests.
- A working Node.js environment.
Frontend Implementation
Let’s start by setting up the frontend. We’ll use jQuery to handle AJAX requests for registering and authenticating fingerprints.
Registering a Fingerprint
The following code handles the registration of a fingerprint:
<script src="https://unpkg.com/@simplewebauthn/browser/dist/bundle/index.umd.min.js"></script><script src="https://unpkg.com/@simplewebauthn/browser/dist/bundle/index.umd.min.js"></script>
function registerPasskey() {
$.ajax({
type: "POST",
url: "/api/auth/register-challenge",
success: async function (res) {
if (res.code == 200) {
const authenticationResult = await SimpleWebAuthnBrowser.startRegistration(res.data);
if (authenticationResult) {
$.ajax({
type: "POST",
url: "/api/auth/verify-challenge",
contentType: "application/json",
dataType: "json",
data: JSON.stringify({
challenge: res.data.challenge,
credential: authenticationResult,
}),
beforeSend: function () {
$("#register-passkey-btn").text("Registering...");
},
complete: function () {
$("#register-passkey-btn").hide();
},
success: function (res) {
if (res.code == 200) {
toastr.success(res.message);
checkBiometric();
} else {
toastr.error(res.message);
}
},
});
}
} else {
toastr.error(res.message);
}
},
});
}Logging in with Fingerprint
The following code handles logging in using a fingerprint:
function biometricLogin() {
if ($("#devices").val() == "fingerprint") {
$.ajax({
type: "POST",
url: "/api/auth/login-challenge",
success: async function (data) {
if (data.checkFingerPrint == "false") {
toastr.error("Please register biometric first");
return;
}
if (data.code == 200) {
const challenge = data.data;
const authenticationResult = await SimpleWebAuthnBrowser.startAuthentication(challenge);
$.ajax({
type: "POST",
url: "/api/auth/verify-login-challenge",
dataType: "json",
contentType: "application/json",
data: JSON.stringify({
challenge: data.challenge,
credential: authenticationResult,
}),
success: function (data) {
if (data.code == 200) {
window.location.href = "/dashboard";
} else {
$("#alert").html(data.message).addClass("shake");
resetRecaptcha();
}
},
});
} else {
$("#alert").html(data.message).addClass("shake");
resetRecaptcha();
}
},
});
}
}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/serverSetting Up Routes
Below are the backend routes for registering and verifying fingerprints.
Register Challenge Route
This route generates a registration challenge:
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" });
}
});Verify Challenge Route
This route verifies the registration challenge response:
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
This route generates an authentication challenge:
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
This route verifies the authentication challenge response:
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!