Ultimate

X_MFA_SECRET_INVALID

Means

a TOTP secret that is not RFC 4648 base32, so it decodes to zero bytes

Typical cause

enrolTotp is handed an imported secret outside the alphabet, or totpCode is called with one. verifyTotp does not throw it — an unreadable stored secret reads as a non-verdict there, the same rule verifyAgainst follows for a password hash Bun cannot read, so a login checking a broken row gets a credential refusal rather than a 500

Fix

issue a fresh one and store it: const { secret } = enrolTotp(auth, { account: user.email }). An imported secret must be base32 (A-Z and 2-7; padding, spaces and dashes are ignored) and must decode to at least one byte. Zero bytes is not a decode that failed closed: an HMAC keyed with zero bytes is a valid HMAC, so every unreadable secret in the table used to verify against one shared code stream that needed no secret to compute

Listed under Auth and sessions

This page as markdown