Short answer
Let the user try again with a new challenge and the current code. If every code fails, check that the factorId belongs to the QR code the user scanned, that enroll ran only once, and that the phone clock is set automatically. Unverified factors from abandoned enrollments can be removed with unenroll.
Rather not fix it yourself? App Fix Ticket starts with a fixed-price diagnosis, then a fixed price for the fix before any fix work.
Why the code is rejected, and the fix
01
The code was typed wrong or too late
“If not, you should repeat steps 2 and 3.” Supabase docs, Multi-Factor Authentication (TOTP)
Authenticator codes change every few seconds. Show a short message, clear the field, and run challenge and verify again with the new code.
const { data: challenge } = await supabase.auth.mfa.challenge({ factorId }); const { error } = await supabase.auth.mfa.verify({ factorId, challengeId: challenge.id, code, }); if (error?.code === 'mfa_verification_failed') { showMessage('That code didn’t work. Enter the newest code from the app.'); }02
enroll ran twice during setup
A screen that calls mfa.enroll on every render can show one QR code and keep the factorId of another. Call enroll once and keep its QR code and ID together.
03
The phone clock is off
TOTP codes depend on the time. A phone with a manually set clock produces codes the server won’t accept. Ask the user to turn on automatic date and time.
04
The user has several factors
Also check: after a few abandoned setups, a user can have more than one TOTP factor. Use listFactors, verify against the one marked verified, and unenroll the rest.
Checked against the vendor’s docs on : Supabase docs: Auth error codes; Supabase docs: Multi-Factor Authentication (TOTP).
Rather hand it over?
Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.
- from $450App Fix TicketOne reproducible bug in an existing mobile or web app, diagnosed and fixed.See scope and start
- from $450AI-Built App RescueA Lovable, Bolt or Cursor app checked end to end in 3 days, then its worst problems fixed at a fixed price.No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.See scope and start
“Very responsive, attentive to fixing bugs, and completed the project as needed! Thank you!”
Related: Supabase captcha_failed · Supabase same_password · Supabase AuthSessionMissingError · Supabase sms_send_failed