FIDO2 APIs for Browser Applications
Ideem's FIDO2 Authenticator interfaces enable seamless integration with an existing Relying Party (RP) infrastructure. The Relying Party is a critical component in the WebAuthn framework, typically representing the server that interacts with users’ authenticators to handle the registration and authentication processes. The Relying Party is responsible for securely storing users' credential IDs, public keys, and managing authentication challenges.
Through these interfaces, you can determine whether a user has already been enrolled (i.e., their device has been cryptographically bound to an account). The interfaces provide the ability to either create an enrollment if none exists or verify an existing enrollment. FIDO2 WebAuthn functions also support re-authentication for scenarios that require step-up authentication, such as sensitive transactions or multi-factor authentication.
The following sections describe the core API methods used to interact with WebAuthn, detailing how to retrieve enrollment status, create new credentials, and verify users.
Relying Party Overview
A Relying Party (RP) is a system or service that requests the user's authentication via WebAuthn. In the FIDO2/WebAuthn architecture, the RP plays the crucial role of initiating the registration and authentication process, sending appropriate challenges to the user’s authenticator, and verifying the signed responses.
When a user registers with a Relying Party, the RP generates a challenge and sends it to the authenticator. Upon successful registration, the RP stores the credential ID and the associated public key for future verification of the user's identity. Similarly, during the authentication process, the RP sends another challenge, which the user’s authenticator signs and returns. The RP then verifies the signature using the public key associated with the credential ID.
Ideem's interfaces abstract much of this complexity, allowing developers to focus on integrating FIDO2 authentication into their applications by simply interacting with the webauthnCreate, webauthnRetrieve, and webauthnGet methods.
Interface Definitions
Note that the following interface definitions presuppose that you have already imported the FIDO2Client class from the ZSM Client SDK (@ideem/zsm-client-sdk). If you have not yet done so, please refer to the Web Framework Setup documentation for instructions on how to import the ZSM Client SDK.
Webauthn Retrieve FIDO2Client.webauthnRetrieve() (Check For Existing Credentials)
The webauthnRetrieve method of the FIDO2Client class retrieves the attestation data generated during the WebAuthn creation process. This method allows the Relying Party to check if a user has previously been enrolled, meaning that their device has a stored credential ID linked to their account.
Parameters
| Parameter Name | Data Type | Description |
|---|---|---|
userIdentifier | String | The unique identifier for the user. This is typically the user's email address or a UUID. |
Returns
| Parameter Name | Data Type | Description |
|---|---|---|
response | PublicKeyCredential | The retrieved WebAuthn credential containing attestation data*. |
The attestation data returned contains cryptographic information needed to verify the user’s identity and ensure that the credential is securely bound to the device.
Usage
JavaScript
FIDO2Client.webauthnRetrieve()
.then(response => {
console.log("Retrieved attestation data", response);
// Process attestation data
})
.catch(error => {
console.error("Error retrieving attestation data:", error);
});
TypeScript
FIDO2Client.webauthnRetrieve()
.then((response:PublicKeyCredential) => {
console.log("Retrieved attestation data", response);
// Process attestation data
})
.catch((error: any) => {
console.error("Error retrieving attestation data:", error);
});
Webauthn Create FIDO2Client.webauthnCreate() (Create New Credential/Enroll)
The webauthnCreate method initiates the creation of a new WebAuthn credential by leveraging the PublicKeyCredentialCreationOptions. This method is used during user registration to create a public-private key pair, which is cryptographically bound to the user and device.
Once the credential is created, the Relying Party securely stores the credential ID and public key for future authentication. The client-side device will keep the private key securely stored within the authenticator.
The process follows the FIDO2 standard, ensuring that the public-private key pair is securely created and attested.
Parameters
| Parameter Name | Data Type | Description |
|---|---|---|
create_options | PublicKeyCredentialCreationOptions | A JSON object representing the creation options for WebAuthn credentials. |
Returns
| Parameter Name | Data Type | Description |
|---|---|---|
credential | PublicKeyCredential | The created WebAuthn credential object. |
Usage
JavaScript
const publicKeyOptions = {
challenge: new Uint8Array([117, 61, 252, 231, 191, 241, ...]),
rp: { id: "acme.com", name: "ACME Corporation" },
user: {
id: new Uint8Array([79, 252, 83, 72, 214, 7, 89, 26]),
name: "jamiedoe",
displayName: "Jamie Doe"
},
pubKeyCredParams: [{ type: "public-key", alg: -7 }]
};
FIDO2Client.webauthnCreate(publicKeyOptions)
.then(credential => {
console.log("Credential created successfully", credential);
// Handle successful creation, access credential response
})
.catch(error => {
console.error("Error creating credential:", error);
});
TypeScript
const publicKeyOptions:PublicKeyCredentialCreationOptions = {
challenge: new Uint8Array([117, 61, 252, 231, 191, 241, ...]),
rp: { id: "acme.com", name: "ACME Corporation" },
user: {
id: new Uint8Array([79, 252, 83, 72, 214, 7, 89, 26]),
name: "jamiedoe",
displayName: "Jamie Doe"
},
pubKeyCredParams: [{ type: "public-key", alg: -7 }]
};
FIDO2Client.webauthnCreate(publicKeyOptions)
.then((credential:PublicKeyCredential) => {
const response = credential.response as AuthenticatorAttestationResponse;
// Access attestationObject ArrayBuffer
const attestationObj:ArrayBuffer = response.attestationObject;
// Access client JSON
const clientJSON:PublicKeyCredential = response.clientDataJSON;
console.log("Credential created successfully", credential);
})
.catch((error: any) => {
console.error("Error creating credential:", error);
});
Webauthn Get FIDO2Client.webauthnGet() (Authenticate/Verify User)
The webauthnGet method retrieves a credential object based on the PublicKeyCredentialRequestOptions. This method is used to authenticate the user by having their authenticator sign the provided challenge. The signed challenge is then sent back to the Relying Party, which verifies the signature using the previously stored public key.
This method is central to the authentication process, ensuring that only the correct device and user (who possess the private key) can successfully sign the challenge and prove their identity.
Parameters
| Parameter Name | Data Type | Description |
|---|---|---|
get_options | PublicKeyCredentialRequestOptions | A JSON object representing the options for retrieving WebAuthn credentials. |
Returns
The promise resolves with the following parameters:
| Parameter Name | Data Type | Description |
|---|---|---|
assertion | PublicKeyCredential | The WebAuthn credential assertion object. |
Usage
JavaScript
const requestOptions = {
challenge: new Uint8Array([117, 61, 252, 231, 191, 241, ...]),
allowCredentials: [{ type: "public-key", id: new Uint8Array([79, 252, 83, 72, 214, 7, 89, 26]) }],
userVerification: "preferred"
};
FIDO2Client.webauthnGet(requestOptions)
.then(assertion => {
console.log("Assertion obtained successfully", assertion);
// Handle successful assertion
})
.catch(error => {
console.error("Error obtaining assertion:", error);
});
TypeScript
const requestOptions: PublicKeyCredentialRequestOptions = {
challenge: new Uint8Array([117, 61, 252, 231, 191, 241, ...]),
allowCredentials: [{ type: "public-key", id: new Uint8Array([79, 252, 83, 72, 214, 7, 89, 26]) }],
userVerification: "preferred"
};
FIDO2Client.webauthnGet(requestOptions)
.then((assertion: PublicKeyCredential) => {
console.log("Assertion obtained successfully", assertion);
// Handle successful assertion
})
.catch((error: any) => {
console.error("Error obtaining assertion:", error);
});
Unenroll FIDO2Client.webauthnDelete() (Remove user's enrolled credentials from the device)
The webauthnDelete method of the FIDO2Client class removes the specified, enrolled user's credentials from the device the user is actively logged in with. This effectively deletes any credentials associated with the user, preventing future authentication using those credentials, until the user re-enrolls on said device.
Parameters
| Parameter Name | Data Type | Description |
|---|---|---|
userIdentifier | String | The unique identifier for the user. This is typically the user's email address or a UUID. |
Returns
| Parameter Name | Data Type | Description |
|---|---|---|
response | Promise | Returns a promise containing the results of the unenrollment. This can include any one of the following: |
true (boolean) : The user's credentials were successfully removed from the device | ||
false (boolean): The user was not enrolled and/or had no valid local credential set | ||
error ( Error ): Unable to unenroll |
Usage
JavaScript
const deleteResult = await FIDO2Client.webauthnDelete(userIdentifier);
if (deleteResult instanceof Error) throw(deleteResult); // Error Condition
if (!deleteResult) console.warn("User not enrolled"); // Unenrollment Failed
if (!!deleteResult) console.log("User successfully unenrolled"); // Successful Unenrollment
TypeScript
const deleteResult:boolean|Error = await FIDO2Client.webauthnDelete(userIdentifier);
if (deleteResult instanceof Error) throw(deleteResult); // Error Condition
if (deleteResult === false) console.warn("User not enrolled"); // Unenrollment Failed
if (deleteResult === true) console.log("User successfully unenrolled"); // Successful Unenrollment
Event: FIDO2ClientReady
The FIDO2ClientReady event is dispatched when the FIDO2Client is ready to be used. This event is emitted from the worker thread to the main thread's window object. This event can be listened for - to ensure that the client is fully initialized before attempting to call methods contained within it - and the listener can, upon its firing, be used to trigger any necessary actions or UI updates in your application.
Details
In some implementations, the FIDO2Client may take a short time (typically measured in tens of milliseconds, depending on client system specifications) to initialize, especially if it is being loaded from a remote source or if it is being loaded JIT (Just-In-Time) immediately preceding its use.
As such, a listener for this event can be added to the window object to ensure that the FIDO2Client is fully initialized before attempting to call methods contained within it.
Payload
| Parameter | Type | Description |
|---|---|---|
detail | Boolean (true) | The detail object inside the event will always contain the value true, indicating that the FIDO2Client is ready to be used, but since the event is only emitted once it's already ready, this is a formality and can be ignored. |
Usage
JavaScript
window.addEventListener('FIDO2ClientReady', function (event) {
console.info("FIDO2ClientReady event received!");
// Perform any additional actions that involve the FIDO2Client, assured that it is ready to be used
});
TypeScript
window.addEventListener('FIDO2ClientReady', function (event: CustomEvent) {
console.info("FIDO2ClientReady event received!");
// Perform any additional actions that involve the FIDO2Client, assured that it is ready to be used
});
Note...
The FIDO2ClientReady event is a custom event and is not part of the standard DOM events. It is specific to the FIDO2Client implementation and should be handled accordingly. In most implementations, this event listener should not be necessary, as the FIDO2Client is typically initialized during the page load and will be long-since ready prior to its use. However, in some specific cases, this event may be useful to ensure that the client is fully initialized before attempting to call methods contained within it.
Example Code
This example demonstrates the full process of using Ideem’s FIDO2 WebAuthn API for registration, authentication, and how to customize the Relying Party. The WebAuthnClient can either use the default Relying Party provided by the API or allow you to pass in a custom Relying Party if you want more control over how credential IDs, tokens, or user data are handled.
Example of Default Flow: Registration and Authentication
In this example, we use the default Relying Party provided by the API:
const client = new WebAuthnClient();
// Sign in with username and password
FIDO2Client.signIn('jamiedoe@example.com', 'password123')
.then(() => {
console.log("Signed in successfully");
// Start the WebAuthn registration process
return FIDO2Client.webauthnCreate('jamiedoe@example.com');
})
.then((response) => {
console.log("WebAuthn registration complete", response);
// Now authenticate the user using the WebAuthn credential
return FIDO2Client.webauthnGet('jamiedoe@example.com');
})
.then((data) => {
console.log("WebAuthn authentication successful", data);
})
.catch((error) => {
console.error("Error during registration/authentication", error);
});
In this flow:
- The user signs in using their email and password.
- If they don’t have a WebAuthn credential, they go through the
webauthnCreateprocess to register. - Once registered, they can authenticate using
webauthnGetin future logins.
Custom Relying Party Example
If you need more control over how credentials are stored, retrieved, or verified (for example, if you manage your own storage for credential_id and public keys), you can create a custom Relying Party class and pass it into the WebAuthnClient. This gives you full control over how the server interactions are managed.
Here’s an example of a custom Relying Party:
class CustomRelyingParty {
constructor(host) {
this.host = host;
this.state = new RelyingPartyState();
}
login(username, password) {
const loginPayload = {
email: username,
password: password,
};
return fetch(`${this.host}/custom-api/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(loginPayload),
})
.then(response => response.json())
.then(data => {
this.state = new RelyingPartyState(data);
return data;
});
}
startRegistration() {
const payload = { user_id: this.state.userID };
return fetch(`${this.host}/custom-api/webauthn/registration/start`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.state.token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
.then(response => response.json());
}
finishRegistration(credential) {
const payload = {
user_id: this.state.userID,
credential,
};
return fetch(`${this.host}/custom-api/webauthn/registration/finish`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.state.token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
.then(response => response.json());
}
// Implement other methods (startAuthentication, finishAuthentication, etc.) similarly
}
// Use the custom Relying Party in the WebAuthnClient
const customRelyingParty = new CustomRelyingParty("https://your-custom-host.com");
const client = new WebAuthnClient(customRelyingParty);
// Custom sign in flow
FIDO2Client.signIn('janedoe@example.com', 'securePassword!')
.then(() => {
console.log("Signed in using custom Relying Party");
// Start the registration process using the custom Relying Party
return FIDO2Client.webauthnCreate('janedoe@example.com');
})
.then((response) => {
console.log("Custom WebAuthn registration complete", response);
// Now authenticate the user using the custom Relying Party
return FIDO2Client.webauthnGet('janedoe@example.com');
})
.then((data) => {
console.log("Custom WebAuthn authentication successful", data);
})
.catch((error) => {
console.error("Error during registration/authentication", error);
});
Explanation
-
CustomRelyingParty Class:
- This class mimics the behavior of the default
RelyingPartyclass, but it uses a custom API endpoint (/custom-api/...) for managing authentication and WebAuthn interactions. - It maintains the same methods as the default Relying Party, such as
login,startRegistration,finishRegistration, and more.
- This class mimics the behavior of the default
-
Usage:
- You instantiate your custom Relying Party and pass it into the
WebAuthnClientconstructor. This allows theWebAuthnClientto interact with your custom API. - From here, the WebAuthnClient manages the WebAuthn process, while the custom Relying Party manages the server interactions.
- You instantiate your custom Relying Party and pass it into the
-
Flexibility:
- By implementing a custom Relying Party, you can tailor the behavior to match your own backend, authentication flows, and storage mechanisms.
- For example, if you store
credential_idin a database or use different APIs for managing public key credentials, you can adapt the Relying Party accordingly.
-
Key Considerations:
- Ensure that the custom Relying Party's API endpoints handle the WebAuthn protocol correctly, following FIDO2 standards.
- Security is paramount when handling credentials and tokens, so the custom Relying Party should use secure communication protocols (e.g., HTTPS) and implement proper token management (e.g., JWT expiration handling).
Summary
By default, the WebAuthnClient uses a built-in Relying Party to manage credential storage and verification. However, if you need more control or have a custom backend infrastructure, you can pass in your own Relying Party implementation, giving you flexibility to integrate FIDO2 authentication into any system. This modular approach ensures that developers can easily adopt WebAuthn while maintaining full control over server-side processes.