different-ai/openwork · error · EnterpriseMcpOAuthContractError

MCP_OAUTH_AUTHORIZATION_EXPIRED

MCP_OAUTH_AUTHORIZATION_EXPIRED

Error message

The OAuth authorization transaction has expired; start the connection again.

What it means

Thrown from codeVerifier when the loaded transaction's expiresAt is at or before now plus the expiration skew window. The PKCE transaction has a bounded TTL (authorizationTransactionTtlMs); if the user takes too long to complete authorization at the identity provider, the library invalidates the record and requires starting over. This prevents stale PKCE secrets from being exchanged.

Source

Thrown at packages/enterprise-mcp-client/src/oauth-provider.ts:467

      )
    }
    const transaction = await this.persistence.authorizations.load({
      context: this.context(),
      id: this.flow.authorizationId,
    })
    if (!transaction) {
      throw new EnterpriseMcpOAuthContractError(
        "MCP_OAUTH_AUTHORIZATION_MISSING",
        "The OAuth authorization transaction is missing or was already consumed.",
      )
    }
    if (transaction.handle.expiresAt <= this.clock.now() + this.expirationSkewMs) {
      await this.persistence.authorizations.invalidate({
        context: this.context(),
        id: this.flow.authorizationId,
        reason: "expired",
      })
      throw new EnterpriseMcpOAuthContractError(
        "MCP_OAUTH_AUTHORIZATION_EXPIRED",
        "The OAuth authorization transaction has expired; start the connection again.",
      )
    }
    const clientRevision = this.loadedClient?.revision
    if (
      transaction.handle.clientRegistrationRevision !== undefined
      && transaction.handle.clientRegistrationRevision !== clientRevision
    ) {
      throw new EnterpriseMcpOAuthContractError(
        "MCP_OAUTH_AUTHORIZATION_CLIENT_CHANGED",
        "The OAuth client registration changed after authorization started.",
      )
    }
    this.authorizationHandle = transaction.handle
    return transaction.codeVerifier
  }

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Restart the connect flow to mint a fresh transaction.
  2. Increase authorizationTransactionTtlMs if your IdP login flow legitimately takes longer.
  3. Detect this error in the callback handler and automatically re-initiate the authorization redirect.

Example fix

// before
new EnterpriseMcpOAuthProvider({ authorizationTransactionTtlMs: 60_000 })
// after — allow slow IdP logins
new EnterpriseMcpOAuthProvider({ authorizationTransactionTtlMs: 10 * 60_000 })
Defensive patterns

Strategy: try-catch

Validate before calling

const tx = await persistence.authorizations.load({ context, id: authorizationId })
const expired = tx !== undefined && tx.handle.expiresAt <= Date.now()

Try / catch

try { const verifier = await provider.codeVerifier() }
catch (e) {
  if (e instanceof EnterpriseMcpOAuthContractError && e.code === "MCP_OAUTH_AUTHORIZATION_EXPIRED") {
    return redirectToAuthorization() // silently restart the connect flow
  }
  throw e
}

Prevention

When it happens

Trigger: codeVerifier() after the transaction TTL elapsed — e.g. user left the authorization page open and returned later; transaction.expiresAt <= clock.now() + expirationSkewMs.

Common situations: Long user delays at the IdP login/consent screens; very short authorizationTransactionTtlMs configuration; the callback arriving hours later (user bookmarked the redirect URL).

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/d5389285b35c7e7a. Report an issue: GitHub.