{"record":{"id":"97dd2b4d95fb84ca","repo":"n8n-io/n8n","slug":"997","errorCode":"997","errorMessage":"MFA code expired. Close the modal and enable MFA again","messagePattern":"MFA code expired\\. Close the modal and enable MFA again","errorType":"exception","errorClass":"BadRequestError","httpStatus":400,"severity":"error","filePath":"packages/cli/src/controllers/mfa.controller.ts","lineNumber":139,"sourceCode":"\t\tconst { id, mfaEnabled } = req.user;\n\n\t\tawait this.externalHooks.run('mfa.beforeSetup', [req.user]);\n\n\t\tconst { decryptedSecret: secret, decryptedRecoveryCodes: recoveryCodes } =\n\t\t\tawait this.mfaService.getSecretAndRecoveryCodes(id);\n\n\t\tif (!mfaCode) throw new BadRequestError('Token is required to enable MFA feature');\n\n\t\tif (mfaEnabled) throw new BadRequestError('MFA already enabled');\n\n\t\tif (!secret || !recoveryCodes.length) {\n\t\t\tthrow new BadRequestError('Cannot enable MFA without generating secret and recovery codes');\n\t\t}\n\n\t\tconst verified = this.mfaService.totp.verifySecret({ secret, mfaCode, window: 10 });\n\n\t\tif (!verified)\n\t\t\tthrow new BadRequestError('MFA code expired. Close the modal and enable MFA again', 997);\n\n\t\tconst updatedUser = await this.mfaService.enableMfa(id);\n\n\t\tthis.eventService.emit('user-mfa-enabled', {\n\t\t\tuser: {\n\t\t\t\tid: req.user.id,\n\t\t\t\temail: req.user.email,\n\t\t\t\tfirstName: req.user.firstName,\n\t\t\t\tlastName: req.user.lastName,\n\t\t\t\trole: req.user.role,\n\t\t\t},\n\t\t});\n\n\t\tthis.authService.issueCookie(res, updatedUser, verified, req.browserId);\n\t}\n\n\t@Post('/disable', {\n\t\tipRateLimit: true,","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/cli/src/controllers/mfa.controller.ts#L121-L157","documentation":"The TOTP code supplied to POST /rest/mfa/enable failed verification (`totp.verifySecret` returned false with a window of 10). The error carries hint code 997 so the frontend knows to tear down the enrollment modal and restart it. The wide window (10 steps) means this usually indicates a wrong secret rather than mere clock skew.","triggerScenarios":"POST /rest/mfa/enable where totp.verifySecret({ secret, mfaCode, window: 10 }) returns false.","commonSituations":"User scanned the wrong QR / typed the secret manually with a typo; authenticator seeded from a previous, rotated secret; code re-used after expiry.","solutions":["Re-scan the QR: GET /rest/mfa/qr to get a fresh secret, then re-add to the authenticator.","Re-enter the secret carefully if typed manually (watch base32 ambiguities).","Sync the device clock and generate a fresh code before submitting."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Only shape-check; TOTP validity cannot be confirmed client-side.\nif (!/^\\d{6}$/.test(mfaCode)) throw new Error('Enter a 6-digit code.');","typeGuard":null,"tryCatchPattern":"try {\n  await restApi.post('/mfa/enable', { mfaCode });\n} catch (e) {\n  if (e.response?.data?.hint?.code === 997 || /expired/i.test(e.response?.data?.message)) {\n    // restart enrollment: new QR, re-add to authenticator\n    await restartMfaEnrollment();\n  } else throw e;\n}","preventionTips":["Detect hint code 997 in the response and tear down the modal as the server expects.","Encourage QR scan over manual entry to avoid base32 typos.","If the user types the secret, normalize uppercase and strip spaces before comparing."],"tags":["mfa","totp","verification"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}