prf-passkey
v1.0.0
Published
A functional library for WebAuthn Pseudo-Random Function (PRF) key derivation with monadic patterns
Maintainers
Readme
PRF Passkey
A functional TypeScript library for WebAuthn Pseudo-Random Function (PRF) extension with robust error handling using neverthrow for deterministic key derivation using passkeys.
Features
- 🔐 Deterministic Key Derivation: Generate consistent cryptographic keys from passkeys
- 🛡️ Type-Safe: Full TypeScript support with comprehensive type definitions
- 🧩 Robust Error Handling: Built on neverthrow for reliable monadic error handling
- 📦 Multiple Formats: ESM and CommonJS builds included
- 🔧 Battle-Tested: Uses SimpleWebAuthn for encoding/decoding utilities
- 🌐 Modern WebAuthn: Built on the latest WebAuthn PRF extension
Installation
npm install prf-passkeyQuick Start
import {
registerPasskey,
authenticateAndDeriveKey,
textToSalt,
randomChallenge,
randomUserId
} from 'prf-passkey';
// Configure your app
const config = {
rpName: 'My App',
rpId: 'localhost',
userVerification: 'required'
};
// Register a passkey and derive a pseudorandom value
const result = await registerPasskey(config, {
userId: randomUserId(),
userName: '[email protected]',
userDisplayName: 'User Name',
challenge: randomChallenge(),
salt: textToSalt('my-salt-v1')
})();
// Handle the result
if (result.isOk()) {
console.log('Pseudorandom value:', result.value.keyHex);
} else {
console.error('Failed:', result.error.message);
}Core Concepts
This library has a single, focused purpose: extracting pseudorandom values from WebAuthn passkeys using the PRF extension.
What is PRF?
The Pseudo-Random Function (PRF) extension allows passkeys to generate deterministic pseudorandom values based on a salt input. This enables:
- Deterministic key derivation
- Consistent encryption keys across sessions
- Secure pseudorandom number generation
Result Type
The library uses neverthrow's Result<T, E> type for robust error handling:
if (result.isOk()) {
// Access result.value
} else {
// Handle result.error
}API Reference
Core Functions
registerPasskey(config, options)
Register a new passkey and get a pseudorandom value during registration.
interface RegistrationOptions {
userId: Uint8Array;
userName: string;
userDisplayName: string;
challenge: Uint8Array;
salt: Uint8Array; // Input for PRF
}
// Returns: Result<RegistrationResult, Error>
interface RegistrationResult {
credentialId: ArrayBuffer;
encodedId: string;
derivedKey: ArrayBuffer | null; // PRF output
keyHex: string | null; // PRF output as hex
}authenticateAndDeriveKey(config, options)
Authenticate with an existing passkey and get a pseudorandom value.
interface AuthenticationOptions {
credentialId: Uint8Array;
challenge: Uint8Array;
salt: Uint8Array; // Input for PRF
}
// Returns: Result<AuthenticationResult, Error>
interface AuthenticationResult {
derivedKey: ArrayBuffer; // PRF output
keyHex: string; // PRF output as hex
}Utility Functions
textToSalt(text)- Convert text to salt bytes for PRFrandomChallenge()- Generate WebAuthn challengerandomUserId()- Generate user IDformatKeyAsHex()- Convert ArrayBuffer to hex string
Examples
Get Pseudorandom Value from New Passkey
import { registerPasskey, textToSalt, randomChallenge, randomUserId } from 'prf-passkey';
const config = { rpName: 'My App', rpId: 'localhost' };
const result = await registerPasskey(config, {
userId: randomUserId(),
userName: '[email protected]',
userDisplayName: 'User Name',
challenge: randomChallenge(),
salt: textToSalt('my-application-salt-v1') // PRF input
})();
if (result.isOk()) {
console.log('Pseudorandom value:', result.value.keyHex);
// Store credential ID for future use
localStorage.setItem('credentialId', result.value.encodedId);
}Get Pseudorandom Value from Existing Passkey
import { authenticateAndDeriveKey, base64urlToUint8Array } from 'prf-passkey';
// Retrieve stored credential ID
const encodedCredentialId = localStorage.getItem('credentialId');
const credentialIdResult = base64urlToUint8Array(encodedCredentialId);
if (credentialIdResult.isOk()) {
const result = await authenticateAndDeriveKey(config, {
credentialId: credentialIdResult.value,
challenge: randomChallenge(),
salt: textToSalt('my-application-salt-v1') // Same salt = same pseudorandom value
})();
if (result.isOk()) {
console.log('Same pseudorandom value:', result.value.keyHex);
}
}Different Salts = Different Values
// Different salts produce different pseudorandom values from the same passkey
const encryptionSalt = textToSalt('encryption-v1');
const signingSalt = textToSalt('signing-v1');
const encryptionResult = await authenticateAndDeriveKey(config, {
credentialId,
challenge: randomChallenge(),
salt: encryptionSalt
})();
const signingResult = await authenticateAndDeriveKey(config, {
credentialId,
challenge: randomChallenge(),
salt: signingSalt // Different salt
})();
// These will be different values
console.log('Encryption PRF:', encryptionResult.value?.keyHex);
console.log('Signing PRF:', signingResult.value?.keyHex);Browser Demo
Run the interactive browser demo:
npm run build
python -m http.server 8000 # or any local serverOpen http://localhost:8000/examples/browser-demo.html
Requirements
- Modern browser with WebAuthn support:
- Chrome 108+
- Safari 16+
- Firefox 113+
- Device with biometric authentication or security key
- HTTPS connection (except localhost)
Quality & Trust
🔒 Security
- CodeQL Analysis: Automated security scanning on every commit
- Dependency Auditing: Regular security audits of all dependencies
- Security Policy: Responsible disclosure process for vulnerabilities
- No Secrets: Library never stores or logs sensitive data
✅ Testing
- 96%+ Code Coverage: Comprehensive test suite with high coverage
- Cross-Platform CI: Tested on Ubuntu, Windows, and macOS
- Multi-Node Support: Compatible with Node.js 18, 20, and 22
- 52 Test Cases: Unit, integration, and error handling tests
🏗️ Build Quality
- TypeScript First: Full type safety with comprehensive definitions
- ESLint + Security Rules: Automated code quality and security checks
- Automated Releases: Consistent builds and releases via GitHub Actions
- Dependency Management: Automated updates via Dependabot
📦 Production Ready
- Battle-Tested Dependencies: Built on neverthrow and SimpleWebAuthn
- Multiple Formats: ESM and CommonJS builds included
- Tree Shakeable: Optimized for modern bundlers
- Zero Runtime Dependencies: Minimal bundle size impact
Security Considerations
- Always use HTTPS in production
- Store credential IDs securely (not in localStorage)
- Use unique salts for different key purposes
- Consider salt versioning for key rotation
- Validate all inputs on the server side
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
License
MIT License - see LICENSE.md for details.
