Troubleshooting
assertEnabled() throws
Set mfa.enabled (MFA_ENABLED) to true. QMFAService refuses to process challenges while MFA is disabled.
Challenge verification always fails
Check mfa.challenge.maxAttempts — once a challenge exceeds its attempt limit it can no longer be verified even with the correct code. Request a new challenge instead.
TOTP codes rejected despite the correct code
Confirm mfa.totp.stepSeconds and mfa.totp.digits match the authenticator app's expectations, and that server and client clocks are in sync within mfa.totp.window steps.
Delivery factor never sends a code
email, sms, and whatsapp factors are registered by default only when missing — a custom factor registered for the same method name replaces the built-in one. Verify registerFactor() was called with the intended method before requestChallenge().
Challenges disappear after a restart
The default challenge store is in-memory. Use a repository-backed store if challenges must survive process restarts.