Comprehensive error handling with detailed error codes and abort service for managing concurrent WebAuthn operations. These utilities provide detailed error information and control over WebAuthn ceremony lifecycle.
Custom Error class that provides nuanced error details for WebAuthn operations, helping developers understand and handle specific failure scenarios.
/**
* A custom Error used to return a more nuanced error detailing _why_ one of the eight documented
* errors in the spec was raised after calling `navigator.credentials.create()` or
* `navigator.credentials.get()`:
*
* - `AbortError`
* - `ConstraintError`
* - `InvalidStateError`
* - `NotAllowedError`
* - `NotSupportedError`
* - `SecurityError`
* - `TypeError`
* - `UnknownError`
*/
class WebAuthnError extends Error {
code: WebAuthnErrorCode;
constructor(options: {
message: string;
code: WebAuthnErrorCode;
cause: Error;
name?: string;
});
}Usage Examples:
import { startRegistration, WebAuthnError } from "@simplewebauthn/browser";
try {
const response = await startRegistration({ optionsJSON });
} catch (error) {
if (error instanceof WebAuthnError) {
console.log(`WebAuthn Error: ${error.code}`);
console.log(`Message: ${error.message}`);
console.log(`Original error:`, error.cause);
// Handle specific error types
switch (error.code) {
case 'ERROR_CEREMONY_ABORTED':
console.log("User cancelled the operation");
break;
case 'ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED':
console.log("This authenticator is already registered");
break;
case 'ERROR_INVALID_DOMAIN':
console.log("WebAuthn not allowed on this domain");
break;
default:
console.log("Unexpected WebAuthn error occurred");
}
} else {
console.error("Non-WebAuthn error:", error);
}
}Detailed error codes that provide specific information about WebAuthn failures.
type WebAuthnErrorCode =
| 'ERROR_CEREMONY_ABORTED'
| 'ERROR_INVALID_DOMAIN'
| 'ERROR_INVALID_RP_ID'
| 'ERROR_INVALID_USER_ID_LENGTH'
| 'ERROR_MALFORMED_PUBKEYCREDPARAMS'
| 'ERROR_AUTHENTICATOR_GENERAL_ERROR'
| 'ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT'
| 'ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT'
| 'ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED'
| 'ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG'
| 'ERROR_AUTO_REGISTER_USER_VERIFICATION_FAILURE'
| 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY';Error Code Meanings:
ERROR_CEREMONY_ABORTED: User cancelled the WebAuthn operationERROR_INVALID_DOMAIN: WebAuthn not allowed on the current domainERROR_INVALID_RP_ID: Relying Party ID is invalid or mismatchedERROR_INVALID_USER_ID_LENGTH: User ID exceeds maximum allowed lengthERROR_MALFORMED_PUBKEYCREDPARAMS: Invalid public key credential parametersERROR_AUTHENTICATOR_GENERAL_ERROR: General authenticator hardware/software errorERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT: Authenticator doesn't support resident/discoverable credentialsERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT: Authenticator can't perform required user verificationERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED: Attempting to register an already-registered authenticatorERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG: No supported cryptographic algorithmsERROR_AUTO_REGISTER_USER_VERIFICATION_FAILURE: Auto-registration failed user verificationERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY: Error details are in the cause propertyService singleton to ensure only a single WebAuthn ceremony is active at a time, preventing conflicts between concurrent operations.
/**
* A service singleton to help ensure that only a single WebAuthn ceremony is active at a time.
*
* Users of **@simplewebauthn/browser** shouldn't typically need to use this, but it can help e.g.
* developers building projects that use client-side routing to better control the behavior of
* their UX in response to router navigation events.
*/
interface WebAuthnAbortService {
/**
* Prepare an abort signal that will help support multiple auth attempts without needing to
* reload the page. This is automatically called whenever `startRegistration()` and
* `startAuthentication()` are called.
*/
createNewAbortSignal(): AbortSignal;
/**
* Manually cancel any active WebAuthn registration or authentication attempt.
*/
cancelCeremony(): void;
}
// Exported as a singleton instance
const WebAuthnAbortService: WebAuthnAbortService;Usage Examples:
import { WebAuthnAbortService, startRegistration } from "@simplewebauthn/browser";
// Manual ceremony cancellation (advanced usage)
function setupCancelButton() {
const cancelButton = document.getElementById('cancel-webauthn');
cancelButton.addEventListener('click', () => {
WebAuthnAbortService.cancelCeremony();
console.log("WebAuthn ceremony cancelled");
});
}
// Router navigation cleanup (SPA usage)
function onRouteChange() {
// Cancel any ongoing WebAuthn operations when navigating
WebAuthnAbortService.cancelCeremony();
}
// The service is used automatically by startRegistration/startAuthentication
try {
// This automatically calls WebAuthnAbortService.createNewAbortSignal()
const response = await startRegistration({ optionsJSON });
} catch (error) {
if (error.name === 'AbortError') {
console.log("WebAuthn operation was aborted");
}
}
// Advanced: Manual abort signal creation
const abortSignal = WebAuthnAbortService.createNewAbortSignal();
console.log("New abort signal created for WebAuthn operation");import { startRegistration, startAuthentication, WebAuthnError } from "@simplewebauthn/browser";
async function handleWebAuthnRegistration(options) {
try {
const response = await startRegistration({ optionsJSON: options });
return { success: true, data: response };
} catch (error) {
if (error instanceof WebAuthnError) {
return {
success: false,
error: error.code,
message: error.message
};
}
return {
success: false,
error: 'UNKNOWN_ERROR',
message: error.message
};
}
}function getErrorMessage(error: WebAuthnError): string {
switch (error.code) {
case 'ERROR_CEREMONY_ABORTED':
return "Authentication was cancelled. Please try again.";
case 'ERROR_INVALID_DOMAIN':
return "Authentication is not allowed on this website.";
case 'ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED':
return "This security key has already been registered.";
case 'ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT':
return "Your security key doesn't support the required verification method.";
case 'ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG':
return "Your security key doesn't support the required encryption methods.";
default:
return "An unexpected error occurred during authentication.";
}
}
// Usage
try {
await startRegistration({ optionsJSON });
} catch (error) {
if (error instanceof WebAuthnError) {
showUserError(getErrorMessage(error));
}
}async function retryableWebAuthnOperation(operationFn, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operationFn();
} catch (error) {
if (error instanceof WebAuthnError) {
// Don't retry certain errors
const nonRetryableErrors = [
'ERROR_INVALID_DOMAIN',
'ERROR_INVALID_RP_ID',
'ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED',
];
if (nonRetryableErrors.includes(error.code)) {
throw error; // Don't retry these
}
if (attempt === maxRetries) {
throw error; // Final attempt failed
}
console.log(`Attempt ${attempt} failed: ${error.code}, retrying...`);
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
} else {
throw error; // Non-WebAuthn errors aren't retryable
}
}
}
}
// Usage
try {
const response = await retryableWebAuthnOperation(() =>
startRegistration({ optionsJSON })
);
} catch (error) {
console.error("All retry attempts failed:", error);
}The abort service integrates seamlessly with the main WebAuthn functions:
import { startRegistration, startAuthentication, WebAuthnAbortService } from "@simplewebauthn/browser";
// Both functions automatically use the abort service
const registrationPromise = startRegistration({ optionsJSON: regOptions });
const authenticationPromise = startAuthentication({ optionsJSON: authOptions });
// If you start a new operation, the previous one is automatically cancelled
// This prevents conflicts and ensures only one ceremony runs at a time
// Manual cancellation is available for advanced use cases
document.getElementById('cancel').addEventListener('click', () => {
WebAuthnAbortService.cancelCeremony();
});This service is particularly useful in single-page applications where users might navigate between pages or trigger multiple authentication attempts.