[](https://github.com/Nikkei/nid-webauthn-emulator/actions/workflows/ci.yml) [](https://ope
npm install nid-webauthn-emulator


NID WebAuthn Emulator is a library that provides both a FIDO2/CTAP Authenticator emulator and a WebAuthn API emulator built on top of it. Each component is implemented according to the WebAuthn API and CTAP specifications respectively. This module runs on Node.js and is designed for local integration testing of Passkeys.
For detailed specifications of each emulator, please refer to the following:
- FIDO2/CTAP Authenticator Emulator Detailed Specification for Developers
- WebAuthn API Emulator Detailed Specification for Developers
``bash`
npm install nid-webauthn-emulator
Basic usage involves creating a WebAuthnEmulator class and using the create and get methods to emulate navigator.credentials.create and navigator.credentials.get from the WebAuthn API.
`TypeScript
import WebAuthnEmulator from "nid-webauthn-emulator";
const emulator = new WebAuthnEmulator();
const origin = "https://example.com";
emulator.create(origin, creationOptions);
emulator.get(origin, requestOptions);
`
You can also use the createJSON and getJSON methods to perform emulation with JSON data based on the WebAuthn API specification.
`TypeScript`
emulator.createJSON(origin, creationOptionsJSON);
emulator.getJSON(origin, requestOptionsJSON);
These JSON specifications are defined as standard specification data in the following:
-
-
The WebAuthnEmulator class emulates the following FIDO2/CTAP Authenticator by default:
- Automatically performs User Verification (sets the uv flag)NID-AUTH-3141592
- Supports ES256, RS256, and EdDSA algorithms
- Emulates a USB-connected CTAP2 device
- AAGUID is 1
- Increments the Sign Counter by during authentication
These settings can be changed by creating an AuthenticatorEmulator class and passing it to the WebAuthnEmulator class to modify the Authenticator behavior as follows:
`TypeScript
import WebAuthnEmulator, { AuthenticatorEmulator } from "nid-webauthn-emulator";
const authenticator = new AuthenticatorEmulator({
algorithmIdentifiers: ["ES256"],
verifications: {
userVerified: false,
userPresent: false,
},
signCounterIncrement: 0,
});
const webAuthnEmulator = new WebAuthnEmulator(authenticator);
`
AuthenticatorEmulator implements the following commands from the FIDO2/CTAP specification:
- authenticatorMakeCredential (CTAP2): Creating credentialsauthenticatorGetAssertion
- (CTAP2): Retrieving credentialsauthenticatorGetInfo
- (CTAP2): Getting authenticator information
These are typically not used directly, but are called internally by the WebAuthnEmulator class according to the CTAP protocol as follows:
`TypeScript`
const authenticatorRequest = packMakeCredentialRequest({
clientDataHash: createHash("sha256").update(clientDataJSON).digest(),
rp: options.publicKey.rp,
user: options.publicKey.user,
pubKeyCredParams: options.publicKey.pubKeyCredParams,
excludeList: options.publicKey.excludeCredentials,
options: {
rk: options.publicKey.authenticatorSelection?.requireResidentKey,
uv: options.publicKey.authenticatorSelection?.userVerification !== "discouraged",
},
});
const authenticatorResponse = unpackMakeCredentialResponse(this.authenticator.command(authenticatorRequest));
Here's an example of usage with webauthn.io, a well-known WebAuthn demo site. You can find working test code examples in the integration tests.
`TypeScript
// Initialize the Origin and WebAuthn API emulator
// Here we use https://webauthn.io as the Origin
const origin = "https://webauthn.io";
const emulator = new WebAuthnEmulator();
const webauthnIO = await WebAuthnIO.create();
const user = webauthnIO.getUser();
// Display Authenticator information
console.log("Authenticator Information", emulator.getAuthenticatorInfo());
// Create passkey using WebAuthn API Emulator
const creationOptions = await webauthnIO.getRegistrationOptions(user);
const creationCredential = emulator.createJSON(origin, creationOptions);
await webauthnIO.getRegistrationVerification(user, creationCredential);
// Verify authentication with webauthn.io
const requestOptions = await webauthnIO.getAuthenticationOptions();
const requestCredential = emulator.getJSON(origin, requestOptions);
await webauthnIO.getAuthenticationVerification(requestCredential);
`
This library is intended for use in Passkeys E2E testing, particularly with Playwright. For Playwright testing, you can easily use the WebAuthn API emulator with the utility class BrowserInjection. Here's how to use it:
`TypeScript
import WebAuthnEmulator, {
BrowserInjection,
type PublicKeyCredentialCreationOptionsJSON,
type PublicKeyCredentialRequestOptionsJSON,
} from "nid-webauthn-emulator";
async function startWebAuthnEmulator(page: Page, origin: string, debug = false, relatedOrigins: string[] = []) {
const emulator = new WebAuthnEmulator();
await page.exposeFunction(
BrowserInjection.WebAuthnEmulatorCreate,
async (optionsJSON: PublicKeyCredentialCreationOptionsJSON) => {
const response = emulator.createJSON(origin, optionsJSON, relatedOrigins);
return response;
},
);
await page.exposeFunction(
BrowserInjection.WebAuthnEmulatorGet,
async (optionsJSON: PublicKeyCredentialRequestOptionsJSON) => {
const response = emulator.getJSON(origin, optionsJSON, relatedOrigins);
return response;
},
);
await page.exposeFunction(
BrowserInjection.WebAuthnEmulatorSignalUnknownCredential,
async (options: UnknownCredentialOptionsJSON) => {
emulator.signalUnknownCredential(options);
},
);
await page.exposeFunction(
BrowserInjection.WebAuthnEmulatorSignalAllAcceptedCredentials,
async (options: AllAcceptedCredentialsOptionsJSON) => {
emulator.signalAllAcceptedCredentials(options);
},
);
await page.exposeFunction(
BrowserInjection.WebAuthnEmulatorSignalCurrentUserDetails,
async (options: CurrentUserDetailsOptionsJSON) => {
emulator.signalCurrentUserDetails(options);
},
);
}
test.describe("Passkeys Tests", { tag: ["@daily"] }, () => {
test("Passkeys login test", async ({ page }) => {
// Exposed functions defined once per page initially
// Related origins can be specified as needed
const relatedOrigins = ["https://sub.example.com", "https://alt.example.com"];
await startWebAuthnEmulator(page, env, true, relatedOrigins);
await page.goto("https://example.com/passkeys/login");
// Start hooking Passkeys WebAuthn API
// Must be executed after page navigation
await page.evaluate(BrowserInjection.HookWebAuthnApis);
});
});
`
startWebAuthnEmulator uses Playwright's exposeFunction to inject the WebAuthnEmulator's createJSON and getJSON methods into the browser context. This makes the WebAuthnEmulator class's get and create APIs available under the window object in the Playwright test context.
- window.webAuthnEmulatorGet: Exposed Function for WebAuthnEmulator.getJSONwindow.webAuthnEmulatorCreate
- : Exposed Function for WebAuthnEmulator.createJSONwindow.webAuthnEmulatorSignalUnknownCredential
- : Exposed Function for WebAuthnEmulator.signalUnknownCredentialwindow.webAuthnEmulatorSignalAllAcceptedCredentials
- : Exposed Function for WebAuthnEmulator.signalAllAcceptedCredentialswindow.webAuthnEmulatorSignalCurrentUserDetails
- : Exposed Function for WebAuthnEmulator.signalCurrentUserDetails
Additionally, the startWebAuthnEmulator function supports a relatedOrigins parameter. This allows requests from different origins to use the same RP ID. This is useful when using Passkeys in multi-domain environments (such as example.com and sub.example.com). The value of relatedOrigins is the same as the content of /.well-known/webauthn hosted on the domain specified by the RP ID.
These are defined globally per Page, so they need to be defined only once per Page instance.
Next, to hook WebAuthn APIs like navigator.credentials.get, evaluate BrowserInjection.HookWebAuthnApis in the test context.
`TypeScript`
await page.evaluate(BrowserInjection.HookWebAuthnApis);
BrowserInjection.HookWebAuthnApis is a serialized string of a JavaScript function that, when evaluated, performs the following operations:
- Overrides the definition of navigator.credentials.get to call window.webAuthnEmulatorGetnavigator.credentials.create
- Overrides the definition of to call window.webAuthnEmulatorCreatePublicKeyCredential.signalUnknownCredential
- Adds the definition of to call window.webAuthnEmulatorSignalUnknownCredential
This ensures that the WebAuthnEmulator methods defined earlier with exposeFunction are executed when navigator.credentials.get and navigator.credentials.create are called. These processes include serialization and deserialization of data for communication between the test context and Playwright context.
If you want to swap out WebAuthn APIs in Vitest/Jest, you can mock PublicKeyCredential / navigator.credentials using createPasskeysEmulator from src/test-utils/unit-test.ts.
`TypeScript
import { createPasskeysEmulator } from "nid-webauthn-emulator";
const { interface: methods, addPasskey } = createPasskeysEmulator({
origin: "http://localhost",
rpId: "localhost",
autofill: true, // returns false from isConditionalMediationAvailable when set to false
// specify creationException / requestException to inject a DOMException
});
params?.existingPasskeys?.forEach((passkey) => {
addPasskey(passkey); // add pre-registered passkeys before the test starts
});
// remove existing implementations and inject the emulator
const undefinedProperty = { value: undefined, configurable: true };
Object.defineProperty(window, "PublicKeyCredential", undefinedProperty);
Object.defineProperty(window.navigator, "credentials", undefinedProperty);
vi.spyOn(window, "PublicKeyCredential", "get").mockReturnValue(methods.publicKeyCredentials);
vi.spyOn(window.navigator, "credentials", "get").mockReturnValue(methods.credentialsContainer);
``
MIT License
Copyright (C) 2024 Nikkei Inc.