@playstack/mfa
Passkey-first MFA, TOTP, recovery codes, recent-auth proofs, and trusted-device decisions.
Free. MIT licensed. Check preview availability before installing. See package access.
@playstack/mfa provides passkey-first step-up authentication, TOTP, recovery codes, recent-auth proofs, trusted-device decisions, and account policy without taking session ownership from @playstack/auth.
Install
npm install @playstack/mfa @playstack/core @playstack/crypto @playstack/devices @playstack/eventsInstall @simplewebauthn/server only when using the optional Node adapter.
Compose the service
import { createMfa } from '@playstack/mfa'
import { simpleWebAuthnPasskeys } from '@playstack/mfa/webauthn'
export const mfa = createMfa({
persistence,
crypto: applicationCrypto,
webCrypto: globalThis.crypto,
recoveryHasher: passwordGradeRecoveryHasher,
passkeys: simpleWebAuthnPasskeys(),
limiter: mfaAttemptLimiter,
events,
clock,
ids,
issuer: 'Example App',
rpId: 'app.example.com',
rpName: 'Example App',
origins: ['https://app.example.com'],
trustedDevices: devices,
})Configuration reference
MfaOptions property | Required | Purpose |
|---|---|---|
persistence | Yes | Transactional credentials, challenges, recovery sets, and replay locks. |
crypto | Yes | Encryption plus purpose-bound recent-auth signing and verification. |
webCrypto | Yes | TOTP HMAC and secure random bytes. |
recoveryHasher | Yes | Password-grade recovery-code hash and verify bridge. |
passkeys | Yes | WebAuthn options and ceremony verification bridge. |
limiter | Yes | Fail-closed attempt limiter by method and user. |
events, clock, ids | Yes | Typed security events and deterministic primitives. |
issuer | Yes | TOTP issuer, maximum 200 characters. |
rpId, rpName | Yes | WebAuthn relying-party ID and display name. |
origins | Yes | One or more exact HTTPS WebAuthn origins. |
trustedDevices | No | Device trust lookup and grant bridge. |
challengeTtlMs | No | Positive WebAuthn challenge lifetime; defaults to five minutes. |
recentAuthTtl | No | Signed proof lifetime; defaults to 5m. |
recoveryCodeCount | No | Codes per set; defaults to 10 and cannot exceed 50. |
maxTrustedDeviceTtlMs | No | Trust-grant ceiling; defaults to 30 days. |
Passkey bridge
PasskeyAdapter builds registration/authentication options and verifies both ceremonies against the service-supplied challenge, exact origins, RP ID, credential public key, and sign count. The service owns challenge lifetime and single use, credential uniqueness, discovery, and persisted sign counts.
The included simpleWebAuthnPasskeys adapter defaults user verification to required, prefers discoverable credentials, requests no attestation, hashes internal user IDs into fixed-size handles, and omits the allowlist for discoverable sign-in. Use userVerification: 'preferred' only if another factor provides verification.
Other bridge contracts
| Contract | Key members |
|---|---|
MfaPersistence | Transactional credentials, single-use challenges, recovery sets, and locked recovery-code consumption. |
MfaCrypto | Random tokens, encryption, and purpose-bound sign/verify; compatible with @playstack/crypto. |
RecoveryCodeHasher | hash(code) and verify(hash, code) using a password-grade algorithm. |
MfaAttemptLimiter | consume(method, userId) returning allowed and optional retryAt. |
TrustedDeviceManager | isTrusted and trust; implemented structurally by @playstack/devices. |
Security behavior
- TOTP uses HMAC-SHA1, a 30-second period, six digits, a one-step window, encrypted secrets, and counter replay prevention.
- Recovery codes are displayed once, activated only after acknowledgement, consumed atomically, and retained after use.
- Regenerating codes does not invalidate the previous set until the replacement is acknowledged.
- Sign-count regressions are persisted for detection but do not hard-lock synced passkeys.
MfaService implements auth’s StepUpVerifier. Register auth’s MFA-change handler for both enrollment and disable events so credential changes revoke existing sessions atomically. Route mfa.verification.locked to security notifications as a transactional event that bypasses preferences.
Persistence and testing
@playstack/mfa/prisma provides locked PostgreSQL persistence. Its managed,
security-critical fragment is non-ejectable and declares its reverse User
relations as model extensions. playstack database sync composes those fields
into the selected managed auth artifact and validates the complete Prisma
relation graph before writing. If an application owns the User model, it must
supply the extension target explicitly. Applications still own migrations.
@playstack/mfa/testing provides in-memory persistence and deterministic bridge
fakes.
Boundary
MFA does not identify devices, issue sessions, choose account policy, or send lockout notifications. The Workers-safe core accepts any passkey adapter; Node-only WebAuthn support remains an optional subpath.
API entry points and requirements
Reference snapshot: @playstack/mfa@0.1.0-beta.1. Import only the entry point your runtime needs. Paths below are relative to the installed package; use Go to Definition in your editor to inspect exact parameters, return types and overloads. Do not import the declaration-file paths directly.
| Public entry point | Declaration file |
|---|---|
@playstack/mfa | ./dist/index.d.ts |
@playstack/mfa/errors | ./dist/errors.d.ts |
@playstack/mfa/prisma | ./dist/prisma.d.ts |
@playstack/mfa/testing | ./dist/testing.d.ts |
@playstack/mfa/webauthn | ./dist/webauthn.node.d.ts |
@playstack/mfa/playstack.artifacts.json | No TypeScript declaration (asset or metadata export). |
@playstack/mfa/package.json | No TypeScript declaration (asset or metadata export). |
Node.js engine requirement: >=20. This is not a claim that every entry point works in browsers or Workers.
Peer dependencies
Keep existing framework versions that satisfy these ranges. Install optional peers only when using the corresponding adapter. The package manager resolves ordinary dependencies separately.
| Peer | Compatible range | When needed |
|---|---|---|
@playstack/crypto | 0.1.0-beta.1 | Required by the package. |
@playstack/devices | 0.1.0-beta.1 | Optional; only for the entry points that use it. |
@simplewebauthn/server | ^13.3.2 | Optional; only for the entry points that use it. |
For a complete first program, start with Getting started. For API lookup and partial-example conventions, see Reading the reference. Provider failures, lifecycle requirements and application responsibilities remain described in the guide above; types alone do not establish production safety.