n8n-io/n8n · error · BadRequestError
Invalid MFA token.
Error message
Invalid MFA token.
What it means
The submitted MFA code failed TOTP verification against the user's decrypted secret. mfaService.totp.verifySecret({ secret, mfaCode }) returned false, so n8n rejects the password change with 400 'Invalid MFA token.'.
Source
Thrown at packages/cli/src/controllers/password-reset.controller.ts:215
})
async changePassword(
req: AuthlessRequest,
res: Response,
@Body payload: ChangePasswordRequestDto,
) {
const { token, password, mfaCode } = payload;
const user = await this.authService.resolvePasswordResetToken(token);
if (!user) throw new NotFoundError('');
if (user.mfaEnabled) {
if (!mfaCode) throw new BadRequestError('If MFA enabled, mfaCode is required.');
const { decryptedSecret: secret } = await this.mfaService.getSecretAndRecoveryCodes(user.id);
const validToken = this.mfaService.totp.verifySecret({ secret, mfaCode });
if (!validToken) throw new BadRequestError('Invalid MFA token.');
}
const passwordHash = await this.passwordUtility.hash(password);
await this.userService.update(user.id, { password: passwordHash });
this.logger.info('User password updated successfully', { userId: user.id });
this.authService.issueCookie(res, user, user.mfaEnabled, req.browserId);
this.eventService.emit('user-updated', { user, fieldsChanged: ['password'] });
// if this user used to be an LDAP user
const ldapIdentity = user?.authIdentities?.find((i) => i.providerType === 'ldap');
if (ldapIdentity) {
this.eventService.emit('user-signed-up', {
user,
userType: 'email',View on GitHub (pinned to 5ac6606e81)
Solutions
- Enter a freshly generated code immediately and resubmit.
- Sync the authenticator device's clock (TOTP is time-based).
- If the device is lost, follow the recovery-code / admin-disabled-MFA path instead of guessing.
Defensive patterns
Strategy: retry
Validate before calling
// No deterministic pre-check; just ensure the code is fresh (<25s old) before submit.
function isCodeFresh(generatedAtMs, maxAgeMs = 25000) { return Date.now() - generatedAtMs < maxAgeMs; } Try / catch
try {
await api.post('/change-password', { token, password, mfaCode });
} catch (e) {
if (e.status === 400 && /Invalid MFA token/.test(e.message)) {
// re-prompt for a fresh code; do not retry the same code
} else { throw e; }
} Prevention
- Generate and submit the code within one TOTP window.
- Keep authenticator device clocks synced.
When it happens
Trigger: POST /change-password for an mfaEnabled user where mfaCode is provided but does not match a valid TOTP window for the stored secret.
Common situations: The 30-second TOTP window elapsed between generating and submitting the code; the user read the code from the wrong authenticator device; clock skew between the client device and server; the secret was re-enrolled since the code was generated.
Related errors
- If MFA enabled, mfaCode is required.
- MFA not used during authentication
- 401
- 997
- MFA secret could not be verified
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/7a0135395b54da80.
Report an issue: GitHub.