# Biometric step-up accepted without secondary OTP for single-device

- **Pattern:** `ab-001467` (`finserv-session-security.step-up-auth.biometric-step-up-single-device`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001467
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001467)

## Why it matters

Forcing an OTP step-up on top of a WebAuthn biometric verification on a user's own trusted single device creates friction without adding meaningful security — both factors already verify possession of the same device. NIST 800-63B AAL2 recognizes biometric verification on a bound authenticator as a sufficient second factor when the device is enrolled and trusted. Requiring an additional OTP in this context defeats the purpose of trusted-device enrollment and trains users to disable or circumvent step-up prompts. Correctly tiered step-up — biometric-sufficient on trusted single devices, OTP-required elsewhere — maintains security while reducing friction for compliant users.

## Severity rationale

Low because the failure mode is usability friction rather than a security gap — requiring excessive OTPs on trusted biometric devices drives step-up bypass attempts rather than enabling direct attack.

## Remediation

Implement device trust logic in `src/lib/stepUpAuth.ts` that branches on single-device trusted status:

```js
export async function performStepUp(device: Device): Promise<boolean> {
  if (device.trusted && device.type === 'single-device') {
    // Enrolled trusted device — biometric alone is sufficient
    return verifyBiometric();
  }
  // Multi-device or untrusted — require OTP; biometric is optional bonus
  return (await verifyOTP()) && (await optionallyVerifyBiometric());
}
```

Pair this with a device trust registration flow that stores enrolled device records (fingerprint hash, enrollment date, user confirmation) in a `user_devices` table. Untrusted or newly seen devices always fall through to the multi-factor path regardless of biometric availability.

## Detection

- **ID:** `biometric-step-up-single-device`
- **Severity:** `low`
- **What to look for:** Count all biometric authentication implementations (WebAuthn, FaceID, TouchID). Quote the actual API calls found. Check for device trust logic that distinguishes single-device from multi-device contexts. Enumerate trusted device registration mechanisms.
- **Pass criteria:** On at least 1 recognized/trusted single device type, biometric step-up is sufficient without requiring an additional OTP. At least 1 device trust mechanism exists. Report the count even on pass (e.g., "1 WebAuthn implementation, 1 device trust table, biometric-only step-up for trusted devices").
- **Fail criteria:** Biometric step-up not available (0 implementations), or biometric always requires secondary OTP even on trusted single devices.
- **Skip (N/A) when:** The application does not support biometric authentication or does not have a single-device mode — cite the actual device support found.
- **Detail on fail:** `"0 biometric implementations found — all step-up requires OTP/SMS"` or `"Biometric exists but always requires OTP — trusted device bypass not implemented"`
- **Remediation:** Add biometric step-up for trusted single-device environments (in `src/lib/stepUpAuth.ts`):
  ```js
  // lib/stepUpAuth.ts
  export async function performStepUp(device: Device) {
    if (device.trusted && device.type === 'single-device') {
      // Single trusted device — biometric sufficient
      return await verifyBiometric();
    } else {
      // Multi-device or untrusted — require OTP + optional biometric
      return await verifyOTP() && await optionallyVerifyBiometric();
    }
  }
  ```

## External references

- cwe CWE-287
- owasp A07
- nist SP-800-63B §4.2

Taxons: access-control, cryptography-and-secrets

HTML version: https://auditbuffet.com/patterns/ab-001467
