A functional library for WebAuthn Pseudo-Random Function (PRF) key derivation with monadic patterns
npm install 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.
- 🔐 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
``bash`
npm install prf-passkey
`typescript
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: 'user@example.com',
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);
}
`
This library has a single, focused purpose: extracting pseudorandom values from WebAuthn passkeys using the PRF extension.
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
The library uses neverthrow's Result type for robust error handling:
`typescript`
if (result.isOk()) {
// Access result.value
} else {
// Handle result.error
}
#### registerPasskey(config, options)
Register a new passkey and get a pseudorandom value during registration.
`typescript
interface RegistrationOptions {
userId: Uint8Array;
userName: string;
userDisplayName: string;
challenge: Uint8Array;
salt: Uint8Array; // Input for PRF
}
// Returns: Result
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.
`typescript
interface AuthenticationOptions {
credentialId: Uint8Array;
challenge: Uint8Array;
salt: Uint8Array; // Input for PRF
}
// Returns: Result
interface AuthenticationResult {
derivedKey: ArrayBuffer; // PRF output
keyHex: string; // PRF output as hex
}
`
- textToSalt(text) - Convert text to salt bytes for PRFrandomChallenge()
- - Generate WebAuthn challengerandomUserId()
- - Generate user IDformatKeyAsHex()
- - Convert ArrayBuffer to hex string
`typescript
import { registerPasskey, textToSalt, randomChallenge, randomUserId } from 'prf-passkey';
const config = { rpName: 'My App', rpId: 'localhost' };
const result = await registerPasskey(config, {
userId: randomUserId(),
userName: 'user@example.com',
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);
}
`
`typescript
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);
}
}
`
`typescript
// 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);
`
Run the interactive browser demo:
`bash`
npm run build
python -m http.server 8000 # or any local server
Open http://localhost:8000/examples/browser-demo.html`
- Modern browser with WebAuthn support:
- Chrome 108+
- Safari 16+
- Firefox 113+
- Device with biometric authentication or security key
- HTTPS connection (except localhost)
- 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
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests
5. Submit a pull request
MIT License - see LICENSE.md for details.