medusajs/medusa · error · MedusaError

Invalid token

Error message

Invalid token

What it means

Thrown by consumePasswordResetToken when no stored reset token matches the hash of the submitted jti. UNAUTHORIZED: the token is unknown — never issued, already consumed, or corrupted.

Source

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

    data: AuthTypes.ConsumePasswordResetTokenDTO,
    @MedusaContext() sharedContext: Context = {}
  ): Promise<AuthTypes.ConsumePasswordResetTokenResponse> {
    return await this.consumePasswordResetToken_(data, sharedContext)
  }

  @InjectTransactionManager()
  protected async consumePasswordResetToken_(
    data: AuthTypes.ConsumePasswordResetTokenDTO,
    @MedusaContext() sharedContext: Context = {}
  ): Promise<AuthTypes.ConsumePasswordResetTokenResponse> {
    const [resetToken] = await this.authPasswordResetTokenService_.list(
      { token_hash: this.hashVerificationToken_(data.jti) },
      {},
      sharedContext
    )

    if (!resetToken) {
      throw new MedusaError(MedusaError.Types.UNAUTHORIZED, "Invalid token")
    }

    if (new Date(resetToken.expires_at).getTime() <= Date.now()) {
      await this.authPasswordResetTokenService_.delete(
        resetToken.id,
        sharedContext
      )
      throw new MedusaError(MedusaError.Types.UNAUTHORIZED, "Invalid token")
    }

    const providerIdentity = await this.providerIdentityService_.retrieve(
      resetToken.provider_identity_id,
      {},
      sharedContext
    )

    if (
      providerIdentity.provider !== data.provider ||

View on GitHub (pinned to 5e06e544a2)

Solutions

  1. Treat as consumed/invalid: restart the reset flow with a new token request
  2. Ensure the token value is passed intact (no URL-encoding truncation)
  3. Verify environment/database consistency between issuance and consumption

Example fix

// before
await authModule.consumePasswordResetToken({ jti: token, entity_id, provider })
// after
const result = await authModule.consumePasswordResetToken({ jti: token, entity_id, provider }).catch((e) => {
  if (e.type === 'unauthorized') throw new Error('Reset link invalid or already used — request a new one')
  throw e
})
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

try { await authModule.consumePasswordResetToken(input) } catch (e) { if (e.type === 'unauthorized') redirect('/reset?expired=1'); throw e }

Prevention

When it happens

Trigger: Consuming a token twice (tokens are deleted after use); using a token from a different environment/database; malformed or truncated jti; token rows purged.

Common situations: User clicks an old reset link after already resetting; double-click on the confirm link; env drift between staging and production tokens.

Understand the failure class

Related errors


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