MCP OAuth Authorization Flow in OpenWork: Discovery, Callback Handling, and Token Exchange
OpenWork's MCP OAuth authorization flow implements a strict OAuth 2.0 Authorization Code grant with PKCE, requiring exact redirect URI matching and state validation through the EnterpriseMcpOAuthProvider class to securely exchange codes for tokens.
The different-ai/openwork repository provides a specialized Model-Context-Protocol (MCP) OAuth implementation designed for enterprise security workloads. This system wraps standard OAuth 2.0 primitives in type-safe TypeScript classes that enforce strict discovery binding, exact-match callback validation, and automated token persistence.
The Three-Stage MCP OAuth Flow
OpenWork structures the authorization process into three distinct stages, each implemented across specific provider and handler modules.
Stage 1: Discovery and Client Registration
The flow begins with the EnterpriseMcpOAuthProvider class loading the authorization server's discovery document. In dev/packages/enterprise-mcp-client/src/oauth-provider.ts, the discoveryState() method fetches the OpenID Connect configuration to locate endpoints dynamically.
If the provider advertises dynamic registration capabilities and the tenant supplies a clientMetadataUrl, OpenWork auto-registers the client. Otherwise, administrators must pre-register the client. The oauth-discovery-binding.ts module verifies that the discovered issuer matches the administrator-selected authorization server before proceeding, preventing issuer confusion attacks.
Stage 2: Authorization Request Construction
Once discovery completes, the provider constructs the authorization URL. The authorizeUrl property (populated in oauth-provider.ts) builds a URL containing:
client_idfrom registered client metadataredirect_urisupplied during provider instantiationcode_challengegenerated via PKCEstatenonce for CSRF protection
This URL directs the user agent to the authorization server. The OpenWork implementation specifically requires that the redirectUri provided to the EnterpriseMcpOAuthProvider constructor matches exactly what was registered with the identity provider.
Stage 3: Callback Validation and Token Exchange
After user authorization, the server redirects to the exact redirect_uri with code and state parameters. The callback handling implements strict validation logic in dev/packages/enterprise-mcp-mock-server/src/protocol/oauth-handler.ts:
if (callbackWithoutResponse.href !== redirectUri) {
throw new ProbeFailure(
"AUTH_USER_OR_WORKLOAD",
"oauth_authorization",
"Authorization callback did not exactly match the registered redirect URI"
);
}
The client-side provider validates that the returned state matches the original nonce, then exchanges the authorization code for tokens. The token request (sent to the discovered token_endpoint) includes the PKCE verifier, client credentials, and the original redirect URI. The server rejects any request where the redirect_uri in the POST body differs from the authorization request.
Strict Callback Handling and Security
OpenWork deliberately enforces exact-match redirect URI validation to prevent open-redirect vulnerabilities. Unlike implementations that allow wildcard or partial matches, the oauth-handler.ts validation requires byte-for-byte equality between the registered URI and the callback URL.
When validation fails, the system throws an EnterpriseMcpOAuthContractError with the specific code MCP_OAUTH_AUTHORIZATION_CALLBACK_FAILED, defined in dev/packages/enterprise-mcp-client/src/errors.ts. This error indicates potential CSRF attacks, configuration mismatches, or malicious redirect attempts.
The provider also stores the redirectUri at construction time and later asserts that the token request's redirect parameter matches exactly, preventing authorization code interception attacks.
Code Examples
Initializing the OAuth Provider
import { EnterpriseMcpOAuthProvider } from "@different-ai/openwork/packages/enterprise-mcp-client/src/oauth-provider";
const provider = new EnterpriseMcpOAuthProvider({
redirectUri: "https://myapp.example.com/mcp/callback",
connectionId: "conn-123",
persistence: myPersistenceAdapter,
flow: { kind: "connect", authorizationId: "signed-state-abc" },
clientName: "My OpenWork Integration",
clock: myClock,
lifecycle: myLifecycle,
authorizationTransactionTtlMs: 5 * 60_000,
expirationSkewMs: 30_000,
oauthConfiguration: {
requestedScopes: ["read", "write"],
},
});
await provider.discoveryState();
const authUrl = provider.authorizeUrl;
Validating and Processing the Callback
const callbackUrl = new URL(request.url);
// Validate state parameter
if (callbackUrl.searchParams.get("state") !== provider.state()) {
throw new Error("Invalid OAuth state – possible CSRF");
}
// Exchange code for tokens
const tokenResponse = await fetch(provider.clientMetadata.token_endpoint, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code: callbackUrl.searchParams.get("code") ?? "",
redirect_uri: provider.redirectUrl,
client_id: provider.clientMetadata.client_id,
code_verifier: pkceVerifier,
}),
});
const tokens = await tokenResponse.json();
await provider.persistence.tokens.save(tokens);
Refreshing Access Tokens
const refreshed = await provider.refreshAccessToken();
// Automatically handles refresh_token grant and PKCE verification
Summary
- Discovery binding in
oauth-discovery-binding.tsvalidates the authorization server identity before initiating flows. - Exact-match enforcement prevents open-redirect attacks by requiring the callback URL to match the registered
redirectUribyte-for-byte. - PKCE implementation protects against authorization code interception during the exchange phase.
- Typed error handling through
EnterpriseMcpOAuthContractErrorprovides specific codes likeMCP_OAUTH_AUTHORIZATION_CALLBACK_FAILEDfor debugging and monitoring. - Token persistence is handled automatically by the provider's configured persistence adapter, including expiration tracking and refresh logic.
Frequently Asked Questions
What is the MCP OAuth authorization flow?
The MCP OAuth authorization flow is OpenWork's implementation of the OAuth 2.0 Authorization Code grant specifically designed for Model-Context-Protocol connections. It follows a three-stage process—discovery, authorization, and token exchange—while enforcing strict security constraints like exact redirect URI matching and PKCE verification according to the implementation in oauth-provider.ts.
How does OpenWork prevent open-redirect attacks during OAuth callbacks?
OpenWork prevents open-redirect attacks by requiring exact string matching between the registered redirectUri and the incoming callback URL. The oauth-handler.ts validation logic throws a ProbeFailure if callbackWithoutResponse.href !== redirectUri, rejecting any wildcard patterns, query string variations, or path mutations that attackers might exploit to steal authorization codes.
What happens if the redirect URI doesn't match exactly?
If the redirect URI doesn't match exactly, the server returns an OAuth error such as invalid_grant or invalid_request, and the client-side EnterpriseMcpOAuthProvider throws an EnterpriseMcpOAuthContractError with code MCP_OAUTH_AUTHORIZATION_CALLBACK_FAILED. This strict validation occurs both in the callback handler and during the token exchange POST request.
Where is the token exchange logic implemented?
The token exchange logic is implemented in dev/packages/enterprise-mcp-mock-server/src/protocol/oauth-handler.ts for the server-side validation and grant processing, while the client-side initiation and persistence logic resides in dev/packages/enterprise-mcp-client/src/oauth-provider.ts. The server verifies the PKCE code verifier, client authentication, and redirect URI matching before issuing access tokens.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →