Skip to content

Dorky-Robot/prf-passkey

Repository files navigation

PRF Passkey

Discord CI/CD codecov npm version npm downloads TypeScript License: MIT Security: CodeQL

A functional TypeScript library for WebAuthn Pseudo-Random Function (PRF) extension with robust error handling. Provides deterministic key derivation from passkeys using monadic error handling with neverthrow.

Features

  • πŸ” Deterministic Key Derivation: Generate consistent cryptographic keys from passkeys
  • πŸ›‘οΈ Type-Safe: Full TypeScript support with comprehensive type definitions
  • 🧩 Monadic Error Handling: Built on neverthrow for reliable functional error handling
  • πŸ“¦ Universal Support: ESM and CommonJS builds included
  • πŸ”§ Battle-Tested: Uses SimpleWebAuthn for WebAuthn encoding/decoding
  • 🌐 Modern WebAuthn: Built on the latest WebAuthn PRF extension specification

Installation

Install the package from npm:

npm install prf-passkey

Or with yarn:

yarn add prf-passkey

Quick 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: '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);
}

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 PRF
  • randomChallenge() - Generate WebAuthn challenge
  • randomUserId() - Generate user ID
  • formatKeyAsHex() - 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: '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);
}

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 server

Open 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

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests
  5. Submit a pull request

License

MIT License - see LICENSE.md for details.

About

Functional TypeScript library for WebAuthn PRF extension. Deterministic key derivation from passkeys with monadic error handling (neverthrow). ESM + CJS, type-safe, zero-config.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors