{"record":{"id":"7a0135395b54da80","repo":"n8n-io/n8n","slug":"invalid-mfa-token","errorCode":null,"errorMessage":"Invalid MFA token.","messagePattern":"Invalid MFA token\\.","errorType":"exception","errorClass":"BadRequestError","httpStatus":400,"severity":"error","filePath":"packages/cli/src/controllers/password-reset.controller.ts","lineNumber":215,"sourceCode":"\t})\n\tasync changePassword(\n\t\treq: AuthlessRequest,\n\t\tres: Response,\n\t\t@Body payload: ChangePasswordRequestDto,\n\t) {\n\t\tconst { token, password, mfaCode } = payload;\n\n\t\tconst user = await this.authService.resolvePasswordResetToken(token);\n\t\tif (!user) throw new NotFoundError('');\n\n\t\tif (user.mfaEnabled) {\n\t\t\tif (!mfaCode) throw new BadRequestError('If MFA enabled, mfaCode is required.');\n\n\t\t\tconst { decryptedSecret: secret } = await this.mfaService.getSecretAndRecoveryCodes(user.id);\n\n\t\t\tconst validToken = this.mfaService.totp.verifySecret({ secret, mfaCode });\n\n\t\t\tif (!validToken) throw new BadRequestError('Invalid MFA token.');\n\t\t}\n\n\t\tconst passwordHash = await this.passwordUtility.hash(password);\n\n\t\tawait this.userService.update(user.id, { password: passwordHash });\n\n\t\tthis.logger.info('User password updated successfully', { userId: user.id });\n\n\t\tthis.authService.issueCookie(res, user, user.mfaEnabled, req.browserId);\n\n\t\tthis.eventService.emit('user-updated', { user, fieldsChanged: ['password'] });\n\n\t\t// if this user used to be an LDAP user\n\t\tconst ldapIdentity = user?.authIdentities?.find((i) => i.providerType === 'ldap');\n\t\tif (ldapIdentity) {\n\t\t\tthis.eventService.emit('user-signed-up', {\n\t\t\t\tuser,\n\t\t\t\tuserType: 'email',","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/cli/src/controllers/password-reset.controller.ts#L197-L233","documentation":"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.'.","triggerScenarios":"POST /change-password for an mfaEnabled user where mfaCode is provided but does not match a valid TOTP window for the stored secret.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// No deterministic pre-check; just ensure the code is fresh (<25s old) before submit.\nfunction isCodeFresh(generatedAtMs, maxAgeMs = 25000) { return Date.now() - generatedAtMs < maxAgeMs; }","typeGuard":null,"tryCatchPattern":"try {\n  await api.post('/change-password', { token, password, mfaCode });\n} catch (e) {\n  if (e.status === 400 && /Invalid MFA token/.test(e.message)) {\n    // re-prompt for a fresh code; do not retry the same code\n  } else { throw e; }\n}","preventionTips":["Generate and submit the code within one TOTP window.","Keep authenticator device clocks synced."],"tags":["mfa","totp","auth","password-reset"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}