n8n-io/n8n · error · BadRequestError
If MFA enabled, mfaCode is required.
Error message
If MFA enabled, mfaCode is required.
What it means
The user resolving the reset has MFA enabled, so n8n requires the current TOTP code to authorize the password change. Submitting POST /change-password without an mfaCode for an mfaEnabled user is rejected with 400.
Source
Thrown at packages/cli/src/controllers/password-reset.controller.ts:209
/**
* Verify password reset token and update password.
*/
@Post('/change-password', {
skipAuth: true,
ipRateLimit: true,
})
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'] });
View on GitHub (pinned to 5ac6606e81)
Solutions
- Include a non-empty mfaCode in the request body alongside token and password.
- Make the frontend prompt for the authenticator code whenever the user has MFA enabled (e.g. branch on a resolve-token response that indicates mfaEnabled).
- Validate the payload shape before sending to avoid a round-trip.
Example fix
// before
// POST /change-password
// { "token": "...", "password": "newP@ss" }
// after
// POST /change-password
{
"token": "...",
"password": "newP@ss",
"mfaCode": "123456"
} Defensive patterns
Strategy: validation
Validate before calling
// Validate payload shape before POST /change-password when the user has MFA.
function isValidChangePasswordPayload(payload, userMfaEnabled) {
return Boolean(payload.token && payload.password) &&
(!userMfaEnabled || (typeof payload.mfaCode === 'string' && payload.mfaCode.trim() !== ''));
} Type guard
function hasRequiredMfaCode(payload, mfaEnabled) {
return !mfaEnabled || (typeof payload?.mfaCode === 'string' && payload.mfaCode.length > 0);
} Try / catch
try {
await api.post('/change-password', payload);
} catch (e) {
if (e.status === 400 && /mfaCode is required/.test(e.message)) {
// prompt for the authenticator code and resend with mfaCode
} else { throw e; }
} Prevention
- Branch the UI on the resolve-token response's mfaEnabled flag to render the MFA field.
- Validate non-empty mfaCode client-side before submit.
When it happens
Trigger: POST /change-password for a user with user.mfaEnabled === true where the payload's mfaCode field is absent, empty, or undefined.
Common situations: The client form does not render an MFA prompt before submit; a direct API call omitted the field; the frontend assumed MFA was off for the account.
Related errors
- Invalid MFA token.
- MFA not used during authentication
- 401
- Login is handled by ${currentAuthenticationMethod}. Please c
- forgotPassword.ldapUserPasswordResetUnavailable
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/fd76a8393a8fbaa8.
Report an issue: GitHub.