different-ai/openwork · error · EnterpriseMcpOAuthContractError
MCP_OAUTH_AUTHORIZATION_MISSING
MCP_OAUTH_AUTHORIZATION_MISSING
Error message
The OAuth authorization transaction is missing or was already consumed.
What it means
Thrown from codeVerifier when the signed authorization id is present but no matching transaction can be loaded from the authorizations persistence. The transaction was either never persisted, already consumed by a completed exchange, or deleted/expired-and-invalidated. This protects against replaying a used or nonexistent authorization.
Source
Thrown at packages/enterprise-mcp-client/src/oauth-provider.ts:456
codeVerifier,
expiresAt,
clientRegistrationRevision: this.loadedClient?.revision,
})
}
async codeVerifier(): Promise<string> {
if (this.flow.kind !== "callback") {
throw new EnterpriseMcpOAuthContractError(
"MCP_OAUTH_AUTHORIZATION_ID_REQUIRED",
"The OAuth callback is missing its signed authorization transaction id.",
)
}
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 !== undefinedView on GitHub (pinned to 2b7df46e8a)
Solutions
- Treat as replay/stale callback: restart the connect flow to mint a new transaction.
- Use a shared, durable persistence backend for authorizations when the flow spans processes or restarts.
- Deduplicate callbacks (single-use state handling) so a double callback does not race the first exchange.
Example fix
// before — in-memory store lost on restart
new InMemoryAuthorizationsStore()
// after
new RedisAuthorizationsStore(redis, { ttlMs: authTransactionTtlMs }) Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-check the transaction before exchange
const tx = await persistence.authorizations.load({ context, id: authorizationId })
const usable = Boolean(tx) && tx.handle.expiresAt > Date.now() Try / catch
try { const verifier = await provider.codeVerifier() }
catch (e) {
if (e instanceof EnterpriseMcpOAuthContractError && e.code === "MCP_OAUTH_AUTHORIZATION_MISSING") {
return restartConnectFlow() // consumed/replay/lost record
}
throw e
} Prevention
- Mark callbacks as single-use and ignore duplicate callback hits
- Use a durable, shared persistence backend for authorizations across restarts
- Keep the same persistence instance configured for the entire flow lifetime
When it happens
Trigger: codeVerifier() loads from persistence.authorizations and gets undefined — e.g. a second callback with the same state (replay), TTL cleanup removed the record, or a different persistence backend than the one used at begin().
Common situations: User refreshing the callback page after token exchange already consumed the transaction; sharing one persistence instance per-process while the flow crosses processes; server restart clearing an in-memory authorizations store mid-flow.
Related errors
- MCP_OAUTH_PERSISTENCE_INVALID
- MCP_OAUTH_AUTHORIZATION_EXPIRED
- `${t("providers.no_oauth_prefix")} ${resolved}. ${t("provide
- `${t("providers.not_oauth_flow_prefix")} ${resolved}.`
- t("providers.oauth_method_required")
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/c3457a8e745cd172.
Report an issue: GitHub.