MFA & step-up
TOTP + recovery codes, email/SMS OTP factors, WebAuthn as a second factor, and step-up MFA gated by a recent-verification window.
Qeet ID ships a complete multi-factor system: TOTP with recovery codes, email/SMS OTP factors, WebAuthn as a second factor, and step-up MFA that gates sensitive actions behind a recent verification.
All implemented
TOTP, OTP factors, WebAuthn-2FA, and step-up are live. Any factor verification
refreshes a recent-MFA window that a server-side RequireRecentMFA
middleware checks before sensitive operations.
Factors
| Factor | Endpoints | Notes |
|---|---|---|
| TOTP (RFC 6238) | /v1/mfa/totp/* | 6-digit authenticator codes; enroll → confirm → verify. |
| Recovery codes | /v1/mfa/recovery-codes* | bcrypt-hashed single-use codes; regenerable. |
| Email / SMS OTP | /v1/mfa/otp/factors* | Per-factor challenge + confirm via real senders. |
| WebAuthn 2FA | /v1/mfa/webauthn/* | Reuses the user's registered passkeys as a second factor. |
TOTP
Three-step enrollment, then verification at sign-in.
Start enrollment
Returns the shared secret + provisioning URI to render as a QR code.
/v1/mfa/totp/enroll/startBegin TOTP enrollmentConfirm
The user enters the first 6-digit code to prove the authenticator is synced.
/v1/mfa/totp/enroll/confirmConfirm enrollmentVerify at sign-in
/v1/mfa/totp/verifyVerify a TOTP codeRemove TOTP with DELETE /v1/mfa/totp (this is a sensitive action — see
Step-up).
Recovery codes
Issued at enrollment, single-use, bcrypt-hashed. Users can regenerate the set (another step-up-gated action).
/v1/mfa/recovery-codesList remaining recovery codes/v1/mfa/recovery-codes/regenerateRegenerate the setEmail / SMS OTP factors
Register a contact factor, then challenge + confirm it as a second step.
/v1/mfa/otp/factorsRegister an OTP factor (email/SMS)/v1/mfa/otp/factors/{id}/challengeSend a code/v1/mfa/otp/factors/{id}/confirmConfirm enrollment/v1/mfa/otp/verifyVerify an OTP at sign-inSMS is lower assurance
Prefer TOTP or WebAuthn as the primary second factor; SMS is vulnerable to SIM-swap. SMS/email delivery also depends on a configured sending domain / Twilio account.
WebAuthn as a second factor
Step-up with a registered passkey. Unlike passwordless login, this asserts the known user and issues no new token — it only proves presence to refresh the MFA window.
/v1/mfa/webauthn/challengeGet assertion options/v1/mfa/webauthn/verifyVerify the assertionStep-up MFA
Any successful factor verification refreshes a recent-verification window. The
RequireRecentMFA middleware gates sensitive endpoints (e.g. disabling MFA,
regenerating recovery codes) and returns an MFA challenge if the window has lapsed.
Check whether the user currently satisfies the window:
/v1/mfa/step-up/statusIs a recent MFA present?curl https://api.qeetid.com/v1/mfa/step-up/status \
-H "Authorization: Bearer $ACCESS_TOKEN"
# → { "recent": false } → prompt the user to re-verify a factorThe pattern: call step-up/status before a sensitive action; if not
recent, run any factor's verify endpoint to refresh the window, then retry. The
server enforces this independently — clients can't bypass it.