A standalone TypeScript library for mocking WebAuthn in browsers, particularly useful for Playwright testing and headless browser automation
npm install webauthn-authenticatorA standalone TypeScript library for mocking WebAuthn in browsers, particularly useful for Playwright testing and headless browser automation.
- Complete WebAuthn Mock: Replaces navigator.credentials.create and navigator.credentials.get with in-memory implementations
- ES256/P-256 Support: Full support for ECDSA with SHA-256 cryptographic operations
- Request Queue Management: New modular architecture with pending request control for advanced testing scenarios
- Test Control: Fine-grained control over request approval, rejection, and timeouts
- Playwright Integration: Designed to work seamlessly with Playwright test fixtures
- Modern TypeScript: Built with Vite, includes TypeScript definitions
- Browser Compatible: Runs in modern browsers with proper polyfills
The library has been refactored into a modular architecture:
- Authenticator: Main facade implementing navigator.credentials API
- RequestManager: Manages queued requests with test control capabilities
- CredentialStore: Handles credential storage and retrieval
- CryptoEngine: Performs all cryptographic operations
- PendingRequest: Individual request objects with approval/rejection control
``bash`
npm install
`bashStart development server with demo
npm run dev
$3
The project includes a demo application that showcases the WebAuthn authenticator in action. Visit the development server to see:
- Creating WebAuthn credentials
- Retrieving existing credentials
- Managing multiple credentials
- Different relying party ID handling
Usage
$3
The architecture provides fine-grained control over WebAuthn requests for advanced testing scenarios:
`typescript
import { Authenticator, RequestManager, CredentialStore, CryptoEngine, installWebAuthnMock } from 'webauthn-authenticator'// Create modules
const credentialStore = new CredentialStore()
const cryptoEngine = new CryptoEngine()
const requestManager = new RequestManager(credentialStore, cryptoEngine)
const authenticator = new Authenticator(requestManager)
// In your test setup (e.g., Playwright)
await context.exposeFunction('createCredential', (options) =>
authenticator.create(options)
)
await context.exposeFunction('getCredential', (options) =>
authenticator.get(options)
)
// Install the WebAuthn mock
await context.addInitScript(() => {
installWebAuthnMock({
exposedCreateCredFuncName: 'createCredential',
exposedGetCredFuncName: 'getCredential'
})
})
// In your test - control request flow
test('should create credential with user approval', async ({ page }) => {
// Start credential creation (won't complete until approved)
const createPromise = page.evaluate(() => {
return navigator.credentials.create({ publicKey: options })
})
// Get the pending request
const pendingRequest = await authenticator.requestManager.waitForNextRequest()
expect(pendingRequest.type).toBe('create')
// Approve the request
await pendingRequest.approve({ userVerification: true })
// Now the browser promise resolves
const credential = await createPromise
expect(credential).toBeDefined()
})
// Simulate user rejection
test('should handle user cancellation', async ({ page }) => {
const createPromise = page.evaluate(() => {
return navigator.credentials.create({ publicKey: options })
})
const request = await authenticator.requestManager.waitForNextRequest()
await request.reject(new DOMException('User cancelled', 'NotAllowedError'))
await expect(createPromise).rejects.toThrow('User cancelled')
})
`$3
`typescript
import { Authenticator, RequestManager, CredentialStore, CryptoEngine, installWebAuthnMock } from 'webauthn-authenticator'// Create the modular authenticator
const credentialStore = new CredentialStore()
const cryptoEngine = new CryptoEngine()
const requestManager = new RequestManager(credentialStore, cryptoEngine)
const authenticator = new Authenticator(requestManager)
// Expose functions to browser window
window.createCredential = authenticator.create.bind(authenticator)
window.getCredential = authenticator.get.bind(authenticator)
// Install the WebAuthn mock
installWebAuthnMock({
exposedCreateCredFuncName: 'createCredential',
exposedGetCredFuncName: 'getCredential'
})
// Now navigator.credentials.create() and navigator.credentials.get() work!
// But they create pending requests that need manual approval
`$3
`typescript
import { test as base } from '@playwright/test'
import { Authenticator } from 'webauthn-authenticator'const test = base.extend<{ authenticator: Authenticator }>({
authenticator: async ({}, use) => {
const authenticator = new Authenticator()
await use(authenticator)
},
context: async ({ context, authenticator }, use) => {
// Expose authenticator functions
await context.exposeFunction('createCredential',
authenticator.createPublicKeyCredential.bind(authenticator))
await context.exposeFunction('getCredential',
authenticator.getPublicKeyCredential.bind(authenticator))
// Add preload script
await context.addInitScript(() => {
// Install WebAuthn mock (include the installWebAuthnMock code here)
})
await use(context)
}
})
test('should create WebAuthn credential', async ({ page, authenticator }) => {
// Your test code here - navigator.credentials.create() will work!
})
`API Reference
$3
#### Methods
-
createPublicKeyCredential(options) - Creates a new WebAuthn credential
- getPublicKeyCredential(options) - Retrieves an existing credential
- cancelNextOperation() - Cancels the next create/get operation (throws NotAllowedError)#### Properties
-
credentials - Object containing all created credentials keyed by credential ID$3
-
installWebAuthnMock(options) - Installs the WebAuthn mock in browser
- deserializePublicKeyCredentialAttestion(credential) - Converts serialized attestation to browser format
- deserializePublicKeyCredentialAssertion(credential) - Converts serialized assertion to browser formatArchitecture
This library is based on the WebAuthn authenticator from the SendApp monorepo, specifically the
@0xsend/webauthn-authenticator package. It has been extracted and modernized as a standalone library.$3
1. Authenticator: Core class that manages credential lifecycle and cryptographic operations
2. Preload Script: Browser-side code that replaces the native WebAuthn API
3. Type Definitions: Complete TypeScript definitions for WebAuthn operations
4. Utilities: Helper functions for serialization and browser compatibility
$3
- Uses Node.js
crypto module for key generation and signing
- Implements ECDSA with P-256 curve (ES256)
- Generates proper CBOR-encoded attestation objects
- Supports signature counters and authenticator dataTesting
The project includes comprehensive tests:
- Unit Tests (Vitest): Test core authenticator functionality
- Integration Tests (Playwright): Test browser integration and WebAuthn API mocking
- Demo Tests: Validate the demo application works correctly
Building
`bash
Build library for distribution
npm run build:libBuild demo application
npm run build
``This project is derived from the SendApp monorepo and maintains compatibility with its WebAuthn implementation.
This is a workspace for developing a standalone version of the SendApp WebAuthn authenticator. When contributing:
1. Maintain compatibility with the original SendApp implementation
2. Follow TypeScript best practices
3. Include tests for new features
4. Update documentation as needed
1. Import Errors: Make sure all dependencies are installed and TypeScript is configured correctly
2. Crypto Warnings: The "externalized for browser compatibility" warning is expected for Node.js crypto usage
3. Test Timeouts: Ensure the development server is running for Playwright tests
The library requires modern browsers with support for:
- WebCrypto API
- ArrayBuffer/TypedArrays
- ES2020+ features# Test commit to trigger CI/CD with updated permissions