Passkey Web Integration Guide
This document provides step-by-step instructions for integrating passkey registration and authentication in your web application using IDaaS.
Overview
Web passkey integration allows users to authenticate using biometric authentication (fingerprint, face recognition) or device PINs instead of traditional passwords. Passkeys are stored securely on the user's device and linked to your domain, providing a seamless and secure authentication experience.
Browser Support
Before implementing passkeys, ensure your target browsers support the WebAuthn API:
- Chrome: Version 67+ (Full support)
- Firefox: Version 60+ (Full support)
- Safari: Version 14+ (Full support with platform authenticators)
- Edge: Version 18+ (Full support)
Prerequisites
- IDaaS Account: Ensure you have an IDaaS account with access to the Administration and Authentication APIs.
- HTTPS Domain: Your web application must be served over HTTPS (except for localhost during development).
- SSL Certificate: Valid SSL certificate for your domain.
- Browser Compatibility: Target browsers that support the WebAuthn API.
Create Administration API Application
- Log in to your IDaaS account
- Navigate to Applications and create a new Administration API application
- Provide the required details:
- Application name
- Description
- Required permissions for FIDO token management
- Save the application ID and shared secret for your backend integration
Configure Relying Party Settings
- Navigate to the Passkey/FIDO2 settings page of your IDaaS account
- Enable the Enable Passkeys from Custom Web or Native Apps option
- Click the Add button to add your domain
- Enter your Relying Party ID (your domain, e.g.,
yourdomain.com). For web applications, only the Relying Party ID is required - Click OK to close the dialog, then click Save to apply the changes
Create Authentication API Application
- Navigate to Applications and create a new Authentication API application
- Provide the required details:
- Application name
- Description
- Save the application ID for your frontend integration
Configure Resource Rules
- Navigate to the Resource Rules page
- Create a new resource rule for your Authentication API application
- Select Passkey/FIDO2 as one of the authentication methods
- Ensure Passkey is enabled in the Login Flow settings
Integration Steps
Backend Setup
See the Getting Started guide for initializing the IDaaS Admin API SDK in your backend application.
Your backend will handle communication with IDaaS APIs, while your frontend manages the WebAuthn API browser interactions.
Frontend Implementation
1. WebAuthn Feature Detection
First, check if the browser supports WebAuthn:
// Check for WebAuthn support
function isWebAuthnSupported() {
return window.PublicKeyCredential !== undefined && typeof window.PublicKeyCredential === 'function';
}
// Check for platform authenticator availability
async function isPlatformAuthenticatorAvailable() {
if (!isWebAuthnSupported()) return false;
return await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
}
// Example usage
if (await isPlatformAuthenticatorAvailable()) {
// Show passkey registration/authentication options
showPasskeyOptions();
} else {
// Fallback to traditional authentication
showPasswordLogin();
}
2. Helper Functions
Create utility functions for WebAuthn operations:
// Convert ArrayBuffer to Base64URL
function arrayBufferToBase64Url(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
// Convert Base64URL to ArrayBuffer
function base64UrlToArrayBuffer(base64url) {
const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');
const padding = '='.repeat((4 - (base64.length % 4)) % 4);
const binaryString = atob(base64 + padding);
const buffer = new ArrayBuffer(binaryString.length);
const view = new Uint8Array(buffer);
for (let i = 0; i < binaryString.length; i++) {
view[i] = binaryString.charCodeAt(i);
}
return buffer;
}
// Convert string to ArrayBuffer
function stringToArrayBuffer(str) {
return new TextEncoder().encode(str);
}
Passkey Registration
1. Start Registration Process
Initiate the passkey registration process by requesting a challenge from your backend.
If you don't have the UUID of the user you want to register a passkey for, you can get it by fetching the user details via the POST /api/web/v3/users/userid API.
During registration, the browser sends a credential creation request (navigator.credentials.create) to the
authenticator, which triggers the operating system's native fingerprint verification UI. Once the user scans their
fingerprint successfully, the authenticator generates and returns the public key credential to the browser.
async function startPasskeyRegistration(userId) {
try {
// Request challenge from your backend
const response = await fetch(
`${baseUrl}/api/web/v1/fidotokens/challenge/${userId}`, // userId is the UUID of the user for whom the fido token is to be created
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
},
);
if (!response.ok) {
throw new Error('Failed to start registration');
}
const challengeData = await response.json();
// Proceed with WebAuthn registration
return await completePasskeyRegistration(challengeData);
} catch (error) {
console.error('Registration failed:', error);
throw error;
}
}
2. Complete Registration with WebAuthn
After the user successfully registers their passkey via a supported browser, your web application will receive an attestation response containing the attestation object and client data JSON. These values must be sent to your backend server to complete the WebAuthn passkey registration process with IDaaS.
async function completePasskeyRegistration(challengeData) {
try {
// Prepare WebAuthn credential creation options
const publicKeyCredentialCreationOptions = {
challenge: base64UrlToArrayBuffer(challengeData.challenge),
rp: {
name: challengeData.rpName,
id: challengeData.rpId || window.location.hostname,
},
user: {
id: stringToArrayBuffer(challengeData.userId),
name: challengeData.userName,
displayName: challengeData.userDisplayName,
},
pubKeyCredParams: [
{
alg: -7, // ES256
type: 'public-key',
},
{
alg: -257, // RS256
type: 'public-key',
},
],
authenticatorSelection: {
authenticatorAttachment: challengeData.registrationAuthenticatorAttachment?.toLowerCase() || 'platform',
requireResidentKey: challengeData.registrationRequireResidentKey === 'REQUIRED',
residentKey: challengeData.registrationRequireResidentKey?.toLowerCase() || 'required',
userVerification: challengeData.registrationUserVerification?.toLowerCase() || 'required',
},
timeout: challengeData.timeoutMillis,
excludeCredentials:
challengeData.registeredCredentials?.map((credId) => ({
id: base64UrlToArrayBuffer(credId),
type: 'public-key',
})) || [],
};
// Create the credential
const credential = await navigator.credentials.create({
publicKey: publicKeyCredentialCreationOptions,
});
if (!credential) {
throw new Error('Failed to create credential');
}
// Prepare data for backend
const registrationData = {
attestationObject: arrayBufferToBase64Url(credential.response.attestationObject),
clientDataJSON: arrayBufferToBase64Url(credential.response.clientDataJSON),
name: 'My-Passkey', // User-friendly name for the passkey
};
// Send to backend to complete registration
const completionResponse = await fetch(
`${baseUrl}/api/web/v1/fidotokens/complete/${userId}`, // userId is the UUID of the user for whom the fido token is to be created
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
registrationData,
}),
},
);
if (!completionResponse.ok) {
throw new Error('Failed to complete registration');
}
const result = await completionResponse.json();
console.log('Passkey registered successfully:', result);
return result;
} catch (error) {
console.error('WebAuthn registration failed:', error);
throw error;
}
}
The request body includes a 'name' parameter representing the passkey name, which must be unique for each passkey associated with the user.
After successful registration, the user's Passkey/FIDO2 credential will appear in their profile under the list of registered authenticators,
3. Backend Registration Endpoints
Start Registration
Call IDaaS API: GET /api/web/v1/fidotokens/challenge/:id
Return the challenge data received from this endpoint to the frontend.
Complete Registration
Call IDaaS API: POST /api/web/v1/fidotokens/complete/:id
Include the attestation data (attestationObject and clientDataJSON) received from the frontend to the above endpoint request to complete the passkey registration with IDaaS.
Passkey Authentication
1. Start Authentication Process
To authenticate users with passkeys on the web, your application should initiate the authentication flow by sending a request to your backend server, which will in turn request an authentication challenge from IDaaS.
async function startPasskeyAuthentication(userId = null) {
try {
// Request challenge from backend
const response = await fetch(`${baseUrl}/api/web/v2/authentication/users/authenticate/PASSKEY`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
applicationId: '6781549d-433c-44ea-a42f-4705c26f3245', // Unique identifier of the Identity as a Service Authentication API application
userId: userId, // Optional - if null, user can select from available passkeys
rpId: window.location.hostname, // The Relying Party ID associated with the passkey
}),
});
if (!response.ok) {
throw new Error('Failed to start authentication');
}
const challengeData = await response.json();
// Proceed with WebAuthn authentication
return await completePasskeyAuthentication(challengeData);
} catch (error) {
console.error('Authentication failed:', error);
throw error;
}
}
Your backend must then return the challenge to the browser and pass it into the WebAuthn API via
navigator.credentials.get(). The user will complete the authentication by interacting with their device's biometric
sensors.
The userId parameter is optional:
- If not provided, the browser will prompt the user to choose a passkey from the list of available credentials on their device.
- If provided, the authentication flow will attempt to use the specified passkey directly.
Important: The challenge request API will return a token that must be supplied in the subsequent authentication completion request to finalize the process.
2. Complete Authentication with WebAuthn
After the user successfully authenticates via navigator.credentials.get(), the browser returns a PublicKeyCredential (assertion) to your web app. This assertion includes the authenticator data, client data JSON, credential ID, signature and user handle. Your web app must send these values to your backend to complete the authentication with IDaaS.
Include the token returned by the challenge request in the Authorization header of this completion call.
async function completePasskeyAuthentication(challengeData) {
try {
// Prepare WebAuthn credential request options
const publicKeyCredentialRequestOptions = {
challenge: base64UrlToArrayBuffer(challengeData.fidoChallenge.challenge),
allowCredentials:
challengeData.fidoChallenge.allowCredentials?.map((credId) => ({
id: base64UrlToArrayBuffer(credId),
type: 'public-key',
})) || [],
timeout: challengeData.fidoChallenge.timeoutMillis,
userVerification: 'required',
rpId: window.location.hostname,
};
// Get the credential
const credential = await navigator.credentials.get({
publicKey: publicKeyCredentialRequestOptions,
});
if (!credential) {
throw new Error('Failed to get credential');
}
// Send to backend to complete authentication
const completionResponse = await fetch(`${baseUrl}/api/web/v1/authentication/users/authenticate/PASSKEY/complete`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${challengeData.token}`, // Token from challenge response
},
body: JSON.stringify({
applicationId: '6781549d-433c-44ea-a42f-4705c26f3245', // Unique identifier of the Identity as a Service Authentication API application,
fidoResponse: {
authenticatorData: arrayBufferToBase64Url(credential.response.authenticatorData),
clientDataJSON: arrayBufferToBase64Url(credential.response.clientDataJSON),
signature: arrayBufferToBase64Url(credential.response.signature),
userHandle: credential.response.userHandle ? arrayBufferToBase64Url(credential.response.userHandle) : null,
credentialId: credential.id,
},
}),
});
if (!completionResponse.ok) {
throw new Error('Failed to complete authentication');
}
const result = await completionResponse.json();
console.log('Authentication successful:', result);
return result;
} catch (error) {
console.error('WebAuthn authentication failed:', error);
throw error;
}
}
3. Backend Authentication Endpoints
Start Authentication
Call IDaaS API: POST /api/web/v2/authentication/users/authenticate/PASSKEY
This endpoint returns the challenge data and a token.
Complete Authentication
Call IDaaS API: POST /api/web/v1/authentication/users/authenticate/PASSKEY/complete
Include the assertion data received from the frontend to the above endpoint to complete the passkey authentication with
IDaaS. Make sure to include the Authorization header with the token returned from the challenge request.
Complete Example Implementation
HTML Interface
<!DOCTYPE html>
<html>
<head>
<title>Passkey Demo</title>
</head>
<body>
<div id="app">
<div id="registration-section">
<h2>Register Passkey</h2>
<input type="text" id="userId" placeholder="User ID" />
<button onclick="registerPasskey()">Register Passkey</button>
</div>
<div id="authentication-section">
<h2>Authenticate with Passkey</h2>
<input type="text" id="authUserId" placeholder="User ID (optional)" />
<button onclick="authenticatePasskey()">Authenticate</button>
</div>
<div id="status"></div>
</div>
<script src="passkey-implementation.js"></script>
</body>
</html>
typescript Implementation
// Main functions called by UI
async function registerPasskey() {
const userId = document.getElementById('userId').value;
const status = document.getElementById('status');
if (!userId) {
status.textContent = 'Please provide a User ID';
return;
}
try {
status.textContent = 'Starting registration...';
const result = await startPasskeyRegistration(userId);
status.textContent = 'Passkey registered successfully!';
} catch (error) {
status.textContent = `Registration failed: ${error.message}`;
}
}
async function authenticatePasskey() {
const userId = document.getElementById('authUserId').value || null;
const status = document.getElementById('status');
try {
status.textContent = 'Starting authentication...';
const result = await startPasskeyAuthentication(userId);
status.textContent = 'Authentication successful!';
} catch (error) {
status.textContent = `Authentication failed: ${error.message}`;
}
}
// Check support on page load
document.addEventListener('DOMContentLoaded', async () => {
const status = document.getElementById('status');
if (!isWebAuthnSupported()) {
status.textContent = 'WebAuthn is not supported in this browser';
return;
}
const platformSupported = await isPlatformAuthenticatorAvailable();
status.textContent = platformSupported ? 'Platform authenticator available' : 'Platform authenticator not available';
});
Error Handling
Common WebAuthn Errors
function handleWebAuthnError(error) {
switch (error.name) {
case 'NotAllowedError':
return 'User cancelled the operation or operation timed out';
case 'InvalidStateError':
return 'Authenticator is already registered for this user';
case 'NotSupportedError':
return 'Authenticator does not support the requested operation';
case 'SecurityError':
return 'The operation is not allowed in this context';
case 'UnknownError':
return 'An unknown error occurred';
default:
return error.message || 'An unexpected error occurred';
}
}