medusajs/medusa · error · MedusaError

Recovery code is invalid or already used

Error message

Recovery code is invalid or already used

What it means

Thrown by useAuthMfaRecoveryCode when the provider's verify call for the "recovery_code" method returns false — the code is unknown, malformed, or has already been consumed (recovery codes are single-use).

Source

Thrown at packages/modules/auth/src/services/auth-module.ts:678

    data: AuthTypes.UseAuthMfaRecoveryCodeDTO,
    @MedusaContext() sharedContext: Context = {}
  ): Promise<void> {
    return await this.useAuthMfaRecoveryCode_(data, sharedContext)
  }

  @InjectTransactionManager()
  protected async useAuthMfaRecoveryCode_(
    data: AuthTypes.UseAuthMfaRecoveryCodeDTO,
    @MedusaContext() sharedContext: Context = {}
  ): Promise<void> {
    const valid = await this.authMfaProviderService_.verify(
      "recovery_code",
      data,
      sharedContext
    )

    if (!valid) {
      throw new MedusaError(
        MedusaError.Types.NOT_ALLOWED,
        "Recovery code is invalid or already used"
      )
    }
  }

  @InjectManager()
  async requestAuthVerification(
    data: AuthTypes.RequestAuthVerificationDTO,
    @MedusaContext() sharedContext: Context = {}
  ): Promise<AuthTypes.RequestAuthVerificationResponse> {
    if (!data.code_provider) {
      throw new MedusaError(
        MedusaError.Types.INVALID_DATA,
        "Verification provider is required"
      )
    }

View on GitHub (pinned to 5e06e544a2)

Solutions

  1. After a failed attempt, show a fresh code entry (codes are single-use)
  2. Confirm the recovery codes were generated for the same auth identity and were not regenerated since
  3. Parse/clean user input (trim whitespace, preserve case) before submitting

Example fix

// before
await authModule.useAuthMfaRecoveryCode({ auth_identity_id: id, provider: 'email', body: { code } })
// after
const code = rawCode.trim()
try {
  const res = await authModule.useAuthMfaRecoveryCode({ auth_identity_id: id, provider: 'email', body: { code } })
} catch (e) { /* mark code consumed UI-side, prompt for next code */ }
Defensive patterns

Strategy: try-catch

Validate before calling

const code = rawCode.trim()
if (!code) throw new Error('Code required')

Type guard

null

Try / catch

try { await authModule.useAuthMfaRecoveryCode(input) } catch (e) { if (e.type === 'not_allowed') promptNextCode(); else throw e }

Prevention

When it happens

Trigger: Calling useAuthMfaRecoveryCode with a code that was already redeemed; typo'd or truncated code; re-submitting the same recovery code after a partially failed login flow; wrong auth identity.

Common situations: User retries login with the same recovery code after a later step failed; codes regenerated so old ones are invalid; client storing/displaying codes with whitespace loss or case changes.

Related errors


AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27). Data as JSON: /api/errors/3a8dae7e56a02a91. Report an issue: GitHub.