npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

webauthn-emulator

v1.0.2

Published

[![CI Status](https://github.com/niranjan94/webauthn-emulator/actions/workflows/ci.yml/badge.svg)](https://github.com/niranjan94/webauthn-emulator/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://ope

Readme

WebAuthn Emulator

CI Status License: MIT Ask DeepWiki

English | 日本語

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:

Usage

npm install 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.

Note: All WebAuthn emulator methods are asynchronous and return Promises. Make sure to use await or .then() when calling these methods.

import WebAuthnEmulator from "webauthn-emulator";

const emulator = new WebAuthnEmulator();
const origin = "https://example.com";

await emulator.create(origin, creationOptions);
await 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.

await emulator.createJSON(origin, creationOptionsJSON);
await 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)
  • Supports ES256, RS256, and EdDSA algorithms
  • Emulates a USB-connected CTAP2 device
  • AAGUID is NID-AUTH-3141592
  • Increments the Sign Counter by 1 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:

import WebAuthnEmulator, { AuthenticatorEmulator } from "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 credentials
  • authenticatorGetAssertion (CTAP2): Retrieving credentials
  • authenticatorGetInfo (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:

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));

Example with WebAuthn.io

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.

// 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", await emulator.getAuthenticatorInfo());

// Create passkey using WebAuthn API Emulator
const creationOptions = await webauthnIO.getRegistrationOptions(user);
const creationCredential = await emulator.createJSON(origin, creationOptions);
await webauthnIO.getRegistrationVerification(user, creationCredential);

// Verify authentication with webauthn.io
const requestOptions = await webauthnIO.getAuthenticationOptions();
const requestCredential = await emulator.getJSON(origin, requestOptions);
await webauthnIO.getAuthenticationVerification(requestCredential);

Automated Testing with Playwright

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:

import WebAuthnEmulator, {
  BrowserInjection,
  type PublicKeyCredentialCreationOptionsJSON,
  type PublicKeyCredentialRequestOptionsJSON,
} from "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 = await emulator.createJSON(origin, optionsJSON, relatedOrigins);
      return response;
    },
  );

  await page.exposeFunction(
    BrowserInjection.WebAuthnEmulatorGet,
    async (optionsJSON: PublicKeyCredentialRequestOptionsJSON) => {
      const response = await emulator.getJSON(origin, optionsJSON, relatedOrigins);
      return response;
    },
  );

  await page.exposeFunction(
    BrowserInjection.WebAuthnEmulatorSignalUnknownCredential,
    async (options: UnknownCredentialOptionsJSON) => {
      await emulator.signalUnknownCredential(options);
    },
  );

  await page.exposeFunction(
    BrowserInjection.WebAuthnEmulatorSignalAllAcceptedCredentials,
    async (options: AllAcceptedCredentialsOptionsJSON) => {
      await emulator.signalAllAcceptedCredentials(options);
    },
  );

  await page.exposeFunction(
    BrowserInjection.WebAuthnEmulatorSignalCurrentUserDetails,
    async (options: CurrentUserDetailsOptionsJSON) => {
      await 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.getJSON
  • window.webAuthnEmulatorCreate: Exposed Function for WebAuthnEmulator.createJSON
  • window.webAuthnEmulatorSignalUnknownCredential: Exposed Function for WebAuthnEmulator.signalUnknownCredential
  • window.webAuthnEmulatorSignalAllAcceptedCredentials: Exposed Function for WebAuthnEmulator.signalAllAcceptedCredentials
  • window.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.

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.webAuthnEmulatorGet
  • Overrides the definition of navigator.credentials.create to call window.webAuthnEmulatorCreate
  • Adds the definition of PublicKeyCredential.signalUnknownCredential 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.

Custom Credential Storage

By default, the AuthenticatorEmulator stores credentials in memory using PasskeysCredentialsMemoryRepository. For testing scenarios that require persistent storage or database-backed credential management, you can implement the PasskeysCredentialsRepository interface.

Repository Interface

All repository methods are asynchronous and return Promises, allowing you to use database operations:

export interface PasskeysCredentialsRepository {
  saveCredential(credential: PasskeyDiscoverableCredential): Promise<void>;
  deleteCredential(credential: PasskeyDiscoverableCredential): Promise<void>;
  loadCredentials(): Promise<PasskeyDiscoverableCredential[]>;

  /**
   * Execute operations within a transaction.
   * All operations within the transaction function are guaranteed to be atomic.
   * This is critical for sign count updates to prevent race conditions.
   */
  transaction<T>(fn: (repo: PasskeysCredentialsRepository) => Promise<T>): Promise<T>;
}

Transaction Support

The transaction method is essential for atomic operations, particularly for sign counter updates during authentication. The sign counter is a critical security feature in WebAuthn that helps detect cloned credentials. Without atomic updates, concurrent authentication requests could result in race conditions where the sign counter is not properly incremented.

Example of a race condition without transactions:

  1. Request A reads signCount = 5
  2. Request B reads signCount = 5 (before A saves)
  3. Request A saves signCount = 6
  4. Request B saves signCount = 6 (❌ should be 7!)

With transactions, the emulator ensures:

  1. Request A: transaction starts → reads 5 → saves 6 → transaction commits
  2. Request B: waits → transaction starts → reads 6 → saves 7 → transaction commits

Database-Backed Repository Example

Here's an example of implementing a PostgreSQL-backed credential repository:

import { PasskeysCredentialsRepository, PasskeyDiscoverableCredential } from "webauthn-emulator";
import { Pool } from "pg";

class PostgresCredentialsRepository implements PasskeysCredentialsRepository {
  constructor(private pool: Pool) {}

  async saveCredential(credential: PasskeyDiscoverableCredential): Promise<void> {
    const serialized = JSON.stringify(credential);
    const id = this.getCredentialId(credential);

    await this.pool.query(
      `INSERT INTO credentials (id, data) VALUES ($1, $2)
       ON CONFLICT (id) DO UPDATE SET data = $2`,
      [id, serialized]
    );
  }

  async deleteCredential(credential: PasskeyDiscoverableCredential): Promise<void> {
    const id = this.getCredentialId(credential);
    await this.pool.query('DELETE FROM credentials WHERE id = $1', [id]);
  }

  async loadCredentials(): Promise<PasskeyDiscoverableCredential[]> {
    const result = await this.pool.query('SELECT data FROM credentials');
    return result.rows.map(row => JSON.parse(row.data));
  }

  async transaction<T>(fn: (repo: PasskeysCredentialsRepository) => Promise<T>): Promise<T> {
    const client = await this.pool.connect();

    try {
      await client.query('BEGIN');

      // Create a transactional repository using the same client
      const txRepo = new PostgresTransactionalRepository(client);
      const result = await fn(txRepo);

      await client.query('COMMIT');
      return result;
    } catch (error) {
      await client.query('ROLLBACK');
      throw error;
    } finally {
      client.release();
    }
  }

  private getCredentialId(credential: PasskeyDiscoverableCredential): string {
    // Generate unique ID from RP ID and user ID
    return `${credential.publicKeyCredentialSource.rpId.value}:${Buffer.from(credential.user.id).toString('base64')}`;
  }
}

// Use custom repository with AuthenticatorEmulator
const repository = new PostgresCredentialsRepository(pool);
const authenticator = new AuthenticatorEmulator({
  credentialsRepository: repository
});
const emulator = new WebAuthnEmulator(authenticator);

Built-in Repository Options

The library provides two built-in repository implementations:

  1. PasskeysCredentialsMemoryRepository (default): Stores credentials in memory
  2. PasskeysCredentialsFileRepository: Stores credentials as JSON files on disk
import { AuthenticatorEmulator, PasskeysCredentialsFileRepository } from "webauthn-emulator";

// Use file-based storage
const repository = new PasskeysCredentialsFileRepository("./credentials");
const authenticator = new AuthenticatorEmulator({
  credentialsRepository: repository
});

Both built-in repositories implement transaction locking using promise-based queuing to ensure atomic operations, even without database-level transaction support.

License

This project is licensed under the terms of the MIT license. See LICENSE.md for more info.