Let’s be honest—we’ve all wished we could log into websites using our fingerprints or Face ID, just like on mobile apps, right? Well, thanks to web biometrics, that dream isn’t so far-fetched anymore. Imagine ditching those long, complicated passwords and simply using our fingerprint or face to sign in to our favorite websites. Sounds cool, doesn’t it?
Web biometrics, powered by WebAuthn, is making this possible. It's a fancy name for something quite simple: authenticating us with the same kind of security as our phone’s fingerprint sensor or facial recognition, but directly in our web browser. No more worrying about passwords getting leaked or stolen—just a quick scan, and we’re in.
In this tutorial, we’re going to get hands-on with integrating fingerprint and Face ID login into our Angular apps. We’ll cover the essentials, like how the WebAuthn API works and what we need to do on the backend to keep everything secure and smooth. It’s easier than you might think, and by the end, we’ll have our app all set up for the future of authentication. So, let’s dive in and make logging in a breeze!
Understanding WebAuthn: The Basics for Fingerprints and Face-recognition in Angular Apps
Alright, before we jump into the code, let’s get a quick handle on what WebAuthn is all about. Think of WebAuthn as the bridge that connects our apps to the cool biometric features we love on our phones—like fingerprints and Face ID—right in our browsers. It uses public key cryptography to authenticate users, which means no more storing plain old passwords that hackers can easily snatch up. Instead, we’re talking about securely generated keys that make our logins both safe and seamless.
Key Objects and Their Roles
To get things rolling, we need to understand a couple of key players in the WebAuthn game: PublicKeyCredentialCreationOptions and PublicKeyCredentialRequestOptions. Don’t let the long names scare you—they’re just fancy ways to tell the browser how we want to register and authenticate users.
1. PublicKeyCredentialCreationOptions
This is our go-to object when setting up new user credentials. It includes:
- challenge: A unique, random value generated by the server to ensure the response is fresh and can’t be reused.
- rp: Stands for Relying Party (our app) and includes details like the app’s name and ID.
- user: Info about the user, like a unique ID, username, and display name.
- pubKeyCredParams: A list of public key algorithms we’ll allow.
- authenticatorSelection: Helps us choose the right type of authenticator based on things like attachment type (platform or cross-platform) and user verification level.
2. PublicKeyCredentialRequestOptions
When it’s time to verify our users, this object takes the spotlight. It includes:
- challenge: Just like before, this ensures our authentication request is fresh and unique.
- allowCredentials: Specifies which credentials are allowed for the user.
- userVerification: Dictates whether user verification (like a fingerprint scan) is required.
With these objects in hand, our Angular app will be able to guide users through registering their biometric data and authenticating quickly and securely. Next, we’ll get into the code and see how to make this magic happen in our app!
Setting Up the Angular App
In this section, we’ll guide you through setting up an Angular application with biometric authentication using WebAuthn. We’ll focus on using fingerprints and Face ID, so let's get our hands dirty!
Step 1: Setting Up Our Angular Project
To get started, let’s create a new Angular project. Open your terminal and type the following commands:
ng new web-biometrics-demo
cd web-biometrics-demo
ng serve
This sets up a basic Angular application, and running ng serve
will start your app on http://localhost:4200/
. You should see the default Angular welcome page. Now, we’re ready to integrate WebAuthn for biometric authentication.
Step 2: Creating the WebAuthn Service
We need a service in Angular to manage all our WebAuthn functionality, including registration and authentication using biometrics. Let’s create this service by running:
ng generate service services/webauthn
Now, open webauthn.service.ts
and add the following code:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class WebAuthnService {
constructor() { }
// Generates a random buffer to use as a challenge, which is a unique value needed for security
private generateRandomBuffer(length: number): Uint8Array {
const randomBuffer = new Uint8Array(length);
window.crypto.getRandomValues(randomBuffer); // Fills the buffer with cryptographically secure random values
return randomBuffer;
}
// Registers a new credential (like a fingerprint or Face ID) for the user
async register() {
// Generate a unique challenge for the registration process
const challenge = this.generateRandomBuffer(32);
// PublicKeyCredentialCreationOptions is the core object needed for registration
const publicKey: PublicKeyCredentialCreationOptions = {
challenge: challenge, // A random value generated by the server to ensure the request is fresh and unique
rp: { // Relying Party (your app) information
name: "OurAwesomeApp" // Display name of your app
},
user: { // User information
id: this.generateRandomBuffer(16), // A unique identifier for the user
name: "user@example.com", // User's email or username
displayName: "User Example" // A friendly name for the user
},
pubKeyCredParams: [{ // Array of acceptable public key algorithms
type: "public-key",
alg: -7 // Represents the ES256 algorithm (Elliptic Curve Digital Signature Algorithm)
}],
authenticatorSelection: { // Criteria for selecting the appropriate authenticator
authenticatorAttachment: "platform", // Ensures we use the device's built-in biometric authenticator like Touch ID or Face ID
userVerification: "required" // Requires user verification (e.g., fingerprint or face scan)
},
timeout: 60000, // Timeout for the registration operation in milliseconds
attestation: "direct" // Attestation provides proof of the authenticator's properties and is sent back to the server
};
try {
// This will prompt the user to register their biometric credential
const credential = await navigator.credentials.create({ publicKey }) as PublicKeyCredential;
this.storeCredential(credential, challenge); // Store the credential details locally for demo purposes
console.log("Registration successful!", credential);
return credential; // Return the credential object containing the user's public key and other details
} catch (err) {
console.error("Registration failed:", err);
throw err; // Handle any errors that occur during registration
}
}
// Authenticates the user with stored credentials (like a fingerprint or Face ID)
async authenticate() {
const storedCredential = this.getStoredCredential(); // Retrieve stored credential information
if (!storedCredential) {
throw new Error("No stored credential found. Please register first."); // Error if no credentials are found
}
// PublicKeyCredentialRequestOptions is used to prompt the user to authenticate
const publicKey: PublicKeyCredentialRequestOptions = {
challenge: new Uint8Array(storedCredential.challenge), // A new challenge to ensure the request is fresh and unique
allowCredentials: [{ // Specifies which credentials can be used for authentication
id: new Uint8Array(storedCredential.rawId), // The ID of the credential to use
type: "public-key"
}],
userVerification: "required", // Requires user verification (e.g., fingerprint or face scan)
timeout: 60000 // Timeout for the authentication operation in milliseconds
};
try {
// This will prompt the user to authenticate using their registered biometric credential
const credential = await navigator.credentials.get({ publicKey }) as PublicKeyCredential;
console.log("Authentication successful!", credential);
return credential; // Return the credential object with authentication details
} catch (err) {
console.error("Authentication failed:", err);
throw err; // Handle any errors that occur during authentication
}
}
// Stores credential data in localStorage (for demo purposes only; this should be handled securely in production)
private storeCredential(credential: PublicKeyCredential, challenge: Uint8Array) {
const credentialData = {
rawId: Array.from(new Uint8Array(credential.rawId)), // Converts the raw ID to an array for storage
challenge: Array.from(challenge) // Converts the challenge to an array for storage
};
localStorage.setItem('webauthn_credential', JSON.stringify(credentialData)); // Store the data as a JSON string
}
// Retrieves stored credential data from localStorage
private getStoredCredential(): any {
const storedCredential = localStorage.getItem('webauthn_credential');
return storedCredential ? JSON.parse(storedCredential) : null; // Parse the stored JSON back into an object
}
}
What’s Happening in the Code?
generateRandomBuffer
: Creates a random buffer that serves as a challenge to ensure each authentication or registration request is unique.register
: This method sets up the biometric registration process. It usesPublicKeyCredentialCreationOptions
to define parameters like thechallenge
,relying party
(your app), user information, and acceptable public key algorithms. Whennavigator.credentials.create()
is called, the browser prompts the user to register their biometric data.authenticate
: This method handles user authentication with biometrics. It usesPublicKeyCredentialRequestOptions
to define the authentication challenge and credentials that can be used. The method prompts the user to authenticate with their registered biometrics.-
storeCredential
andgetStoredCredential
: These methods handle storing and retrieving credentials inlocalStorage
for demonstration purposes.In a real-world app, you’d securely store this information on your backend.
Step 3: Building the UI
Now, let’s create a basic UI with buttons to trigger the registration and login functions. This UI will provide feedback based on whether the registration or login was successful.
Open app.component.ts
and replace the content with the following:
import { Component } from '@angular/core';
import { WebAuthnService } from './services/webauthn.service';
@Component({
selector: 'app-root',
template: `
<div class="auth-container">
<h1>Web Biometrics in Angular</h1>
<button (click)="register()">Register with Fingerprint</button>
<button (click)="login()">Login with Face ID</button>
<p *ngIf="message" [ngClass]="{'success': isSuccess, 'error': !isSuccess}">{{ message }}</p>
</div>
`,
styles: [`
.auth-container {
text-align: center;
padding: 50px;
}
.success {
color: green;
}
.error {
color: red;
}
button {
margin: 10px;
padding: 10px 20px;
font-size: 16px;
}
p {
margin: 10px;
font-size: 16px;
}
`]
})
export class AppComponent {
message: string | null = null; // Message to display feedback to the user
isSuccess: boolean = false; // Indicates if the last action was successful
constructor(private webAuthnService: WebAuthnService) { }
// Trigger registration process and update the UI based on the outcome
async register() {
try {
await this.webAuthnService.register();
this.message = "Registration successful!"; // Success message if registration works
this.isSuccess = true;
} catch (err) {
this.message = "Registration failed. Please try again."; // Error message if something goes wrong
this.isSuccess = false;
}
}
// Trigger authentication process and update the UI based on the outcome
async login() {
try {
await this.webAuthnService.authenticate();
this.message = "Authentication successful!"; // Success message if authentication works
this.isSuccess = true;
} catch (err) {
this.message = "Authentication failed. Please try again."; // Error message if something goes wrong
this.isSuccess = false;
}
}
}
What’s Happening in the Component?
register
and login methods
: These methods call the respective register and authenticate methods from the WebAuthnService
. If successful, a success message is displayed; otherwise, an error message is shown.
Template and Styling: The template includes buttons to trigger registration and login, and it displays messages to the user based on the operation's outcome. The buttons are styled for simplicity.
That’s it! We’ve built a basic Angular app with WebAuthn-based biometric authentication, supporting fingerprints and Face ID. This setup captures the core concepts and lays a foundation that can be expanded with additional features and security measures for a production environment.
Backend Considerations
When implementing biometric authentication like fingerprints or Face ID in web applications using WebAuthn, the backend plays a crucial role in managing the security and flow of data. Here’s a breakdown of how the backend processes work in theory, focusing on registration and login functionalities.
Registration: Sign-up
1. User Registration Flow:
User Data Capture: During registration, the user provides basic credentials, such as an email and password. If biometric data is also being registered, this is captured as part of the WebAuthn response.
Password Hashing: For security, passwords are never stored in plain text. Instead, they are hashed using a library like bcrypt before being stored in the database.
-
Storing WebAuthn Credentials:
- Challenge Handling: The server sends a challenge during the registration process, which is a randomly generated value to prevent replay attacks.
- Response Validation: When the client responds with the WebAuthn data, it includes clientDataJSON and attestationObject that need to be decoded and verified.
- Credential Storage: After validation, key data from the response—like the webauthnId (a unique identifier for the credential) and the publicKey (used to verify future authentications)—are stored in the database alongside the user record.
2. Backend Code Responsibilities:
The backend uses libraries like cbor to decode binary data formats from the WebAuthn response, extracting necessary elements like the public key and authenticator data.
It ensures that the challenge from the initial registration request matches what is returned in the WebAuthn response to verify the authenticity of the registration.
If the WebAuthn response passes all checks, the credentials are saved in the database, linked to the user account.
Login
1. User Login Flow:
Challenge Generation: Similar to registration, the server generates a
challenge
that must be responded to by the client’s authenticator during login.-
Validating the WebAuthn Response:
- The client sends back a PublicKeyCredentialRequestOptions object containing the response to the challenge.
- The backend decodes and verifies this response, ensuring that the challenge and the credentials match what is stored in the database.
-
Credential Verification:
- The public key stored during registration is used to verify the signature in the login response.
- If the credentials match, the backend allows the login and generates an authentication token (like a JWT) for the session.
Error Handling:
Mismatch or Invalid Response: If the challenge response does not match the expected values, or if the WebAuthn credentials do not verify correctly, the backend responds with an error, preventing unauthorized access.
Fallback to Password: If WebAuthn fails or is unavailable, the system can revert to traditional password verification, ensuring users can still access their accounts.
Security Considerations
Data Integrity: The integrity of WebAuthn credentials is critical. Any modification in storage or transmission would cause verification to fail, thereby securing the authentication process.
Challenge Nonces: The use of unique, time-limited challenges ensures that responses cannot be reused, protecting against replay attacks.
Public Key Storage: Storing only public keys (which cannot be used to impersonate the user) enhances security, as private keys remain on the client device.
By following these principles, the backend effectively manages biometric authentication, ensuring a secure, seamless experience for users wanting to use features like fingerprint or Face ID in their Angular apps.
Summary
In this tutorial, we walked into integrating biometric authentication with Angular using WebAuthn. We covered the essentials, from understanding key WebAuthn objects like PublicKeyCredentialCreationOptions
and PublicKeyCredentialRequestOptions
to setting up Angular services and UI components for a smooth registration and login process. We also discussed the backend considerations necessary for handling biometric authentication securely.
For those eager to see WebAuthn in action, I have provided a demo and a repository with a complete implementation. You can check out the demo here and explore the source code on GitHub at this repository.
Embracing biometric authentication not only enhances security but also simplifies the user experience, paving the way for a future where logging in is as easy as a fingerprint scan or a quick face recognition. As you integrate these features into your Angular apps, you'll be contributing to a more secure and user-friendly web. Happy coding!
Top comments (4)
awesome, I had no idea that API in web existed. But then again, did u see the Web API reference page? :) Thank you.
Alright, developer.mozilla.org/en-US/docs/W...
Nice tutorial!
Thank you, I am glad you found it nice!