Biometric step-up accepted without secondary OTP for single-device
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:
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):// 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 — Improper Authentication
- owasp:2021 · A07
- nist:rev4 · SP-800-63B §4.2 — Authenticator Assurance Level 2 — biometric authenticator requirements
Taxons
History
- 2026-04-18·v1.0.0·Initial import from finserv-session-security·automated