---
title: "@playstack/mfa"
description: "Passkey-first MFA, TOTP, recovery codes, recent-auth proofs, and trusted-device decisions."
tags: ["package","identity","mfa","passkeys","security","free"]
---

{/* package-access:start */}

> **Free.** MIT licensed. Check preview availability before installing. See [package access](/docs/packages#access-policy).

{/* package-access:end */}

`@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

```sh
npm install @playstack/mfa @playstack/core @playstack/crypto @playstack/devices @playstack/events
```

Install `@simplewebauthn/server` only when using the optional Node adapter.

## Compose the service

```ts
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.

{/* package-reference:start */}

## 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](/docs/getting-started). For API lookup and partial-example conventions, see [Reading the reference](/docs/packages#reading-the-reference). Provider failures, lifecycle requirements and application responsibilities remain described in the guide above; types alone do not establish production safety.

{/* package-reference:end */}
