{"record":{"id":"2b3c6a173c6dc42f","repo":"medusajs/medusa","slug":"mfa-verification-code-is-required-to-disable-mfa","errorCode":null,"errorMessage":"MFA verification code is required to disable MFA","messagePattern":"MFA verification code is required to disable MFA","errorType":"exception","errorClass":"MedusaError","httpStatus":400,"severity":"error","filePath":"packages/modules/auth/src/services/auth-module.ts","lineNumber":534,"sourceCode":"  }\n\n  @InjectManager()\n  async disableAuthMfa(\n    data: AuthTypes.DisableAuthMfaDTO,\n    @MedusaContext() sharedContext: Context = {}\n  ): Promise<AuthTypes.AuthMfaDTO> {\n    const factor = await this.authMfaFactorService_.retrieve(\n      data.id,\n      {},\n      sharedContext\n    )\n\n    if (\n      factor.status === \"enabled\" &&\n      this.getMfaDisablePolicy_() === \"challenge\"\n    ) {\n      if (!data.method || !data.code) {\n        throw new MedusaError(\n          MedusaError.Types.INVALID_DATA,\n          \"MFA verification code is required to disable MFA\"\n        )\n      }\n\n      const valid = await this.authMfaProviderService_.verify(\n        data.method,\n        {\n          auth_identity_id: factor.auth_identity_id,\n          code: data.code,\n        },\n        sharedContext\n      )\n\n      if (!valid) {\n        throw new MedusaError(\n          MedusaError.Types.NOT_ALLOWED,\n          \"Invalid MFA verification code\"","sourceCodeStart":516,"sourceCodeEnd":552,"githubUrl":"https://github.com/medusajs/medusa/blob/5e06e544a296b9033f20f71f11c559f81a0e5739/packages/modules/auth/src/services/auth-module.ts#L516-L552","documentation":"When the MFA disable policy is 'challenge', disabling an enabled factor requires both a method and a fresh verification code. Omitting either throws INVALID_DATA before verification is attempted.","triggerScenarios":"disableAuthMfa({ id }) without method/code while factor.status === 'enabled' and the configured disable policy is 'challenge'.","commonSituations":"Admin UI 'remove MFA' button that doesn't prompt for a code; policy switched to 'challenge' after the UI was built; API consumers unaware of the policy config.","solutions":["Collect the current MFA code and pass method (e.g. 'totp') plus code in the disable payload","If appropriate for your deployment, change the MFA disable policy configuration away from 'challenge'"],"exampleFix":"// before\nawait authModuleService.disableAuthMfa({ id: factorId })\n// after\nawait authModuleService.disableAuthMfa({\n  id: factorId,\n  method: 'totp',\n  code: currentCode,\n})","handlingStrategy":"validation","validationCode":"if (policy === 'challenge' && factor.status === 'enabled' && (!method || !code)) {\n  throw new Error('collect MFA code before disabling')\n}","typeGuard":"const needsChallenge = (factor: { status: string }) => factor.status === 'enabled' && policy === 'challenge'","tryCatchPattern":null,"preventionTips":["Prompt for a verification code in the disable-MFA UI when policy requires it","Keep policy config documented for frontend teams"],"tags":["auth","mfa","policy","validation"],"backgroundTag":"missing-required-field","analyzedSha":"5e06e544a296b9033f20f71f11c559f81a0e5739","analyzedAt":"2026-08-27T07:24:39.599Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}