webauthn-authenticator
v0.0.2-alpha.1
Published
A standalone TypeScript library for mocking WebAuthn in browsers, particularly useful for Playwright testing and headless browser automation
Maintainers
Readme
WebAuthn Authenticator Standalone
A standalone TypeScript library for mocking WebAuthn in browsers, particularly useful for Playwright testing and headless browser automation.
Features
- Complete WebAuthn Mock: Replaces
navigator.credentials.createandnavigator.credentials.getwith 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
Architecture
The library has been refactored into a modular architecture:
- Authenticator: Main facade implementing
navigator.credentialsAPI - 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
Quick Start
Installation
npm installDevelopment
# Start development server with demo
npm run dev
# Run unit tests
npm test
# Run Playwright tests
npm run playwright
# Build the library
npm run build:libDemo Application
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
Request-Based API
The architecture provides fine-grained control over WebAuthn requests for advanced testing scenarios:
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')
})Browser Integration
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 approvalPlaywright Test Integration
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
Authenticator Class
Methods
createPublicKeyCredential(options)- Creates a new WebAuthn credentialgetPublicKeyCredential(options)- Retrieves an existing credentialcancelNextOperation()- Cancels the next create/get operation (throws NotAllowedError)
Properties
credentials- Object containing all created credentials keyed by credential ID
Utility Functions
installWebAuthnMock(options)- Installs the WebAuthn mock in browserdeserializePublicKeyCredentialAttestion(credential)- Converts serialized attestation to browser formatdeserializePublicKeyCredentialAssertion(credential)- Converts serialized assertion to browser format
Architecture
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.
Key Components
- Authenticator: Core class that manages credential lifecycle and cryptographic operations
- Preload Script: Browser-side code that replaces the native WebAuthn API
- Type Definitions: Complete TypeScript definitions for WebAuthn operations
- Utilities: Helper functions for serialization and browser compatibility
Cryptographic Implementation
- Uses Node.js
cryptomodule for key generation and signing - Implements ECDSA with P-256 curve (ES256)
- Generates proper CBOR-encoded attestation objects
- Supports signature counters and authenticator data
Testing
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
# Build library for distribution
npm run build:lib
# Build demo application
npm run buildLicense
This project is derived from the SendApp monorepo and maintains compatibility with its WebAuthn implementation.
Contributing
This is a workspace for developing a standalone version of the SendApp WebAuthn authenticator. When contributing:
- Maintain compatibility with the original SendApp implementation
- Follow TypeScript best practices
- Include tests for new features
- Update documentation as needed
Troubleshooting
Common Issues
- Import Errors: Make sure all dependencies are installed and TypeScript is configured correctly
- Crypto Warnings: The "externalized for browser compatibility" warning is expected for Node.js crypto usage
- Test Timeouts: Ensure the development server is running for Playwright tests
Browser Compatibility
The library requires modern browsers with support for:
- WebCrypto API
- ArrayBuffer/TypedArrays
- ES2020+ features# Test commit to trigger CI/CD with updated permissions
