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.
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.
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.
finserv-session-security.step-up-auth.biometric-step-up-single-devicelow"0 biometric implementations found — all step-up requires OTP/SMS" or "Biometric exists but always requires OTP — trusted device bypass not implemented"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();
}
}