mcp.transport.oauth_proxy_mount

HTTP mount for an OAuthProxy (basic/authorization §Authorization Server Discovery + §Dynamic Client Registration; RFC 8414 / RFC 7591 / RFC 9728).

OAuthProxy (mcp.auth.oauth_proxy) implements the full client-facing OAuth surface as pure builders, but those builders were not reachable through any public transport API — a server author using the github()/google() presets had to hand-write every vibe.d route plus the upstream callback relay. mountOAuthProxy closes that gap: it registers, on a vibe.d URLRouter, the complete DCR-capable OAuth surface the proxy advertises:

* GET /.well-known/oauth-authorization-server — RFC 8414 AS metadata (proxy.metadataJson), advertising the proxy's own endpoints + PKCE S256. * GET /.well-known/oauth-protected-resource — RFC 9728 PRM document (proxy.resourceMetadata.toJson). * POST /register — RFC 7591 DCR, echoing the request's redirect_uris and handing back the fixed upstream client_id (proxy.register). * GET /authorize — gated on the confused-deputy consent MUST: persists the client's dynamic redirect_uri, state, PKCE code_challenge and scope under a freshly minted proxy state, then calls the GATED proxy.authorize. For an already-consented client it 302s to the upstream authorization endpoint; for an un-consented dynamically-registered client it instead renders the proxy's own consent screen (no upstream forward). * POST /consent — the consent-approval action reached from the consent screen's form submission: records user consent for the pending client (proxy.grantConsent) and resumes the upstream authorize 302 with the stored PKCE code_challenge + scope. A POST (not GET) so a state-changing grant cannot be auto-fired by link prefetch/preload. * the fixed callback path (default /auth/callback) — receives the upstream code + proxy state, looks up the stored client redirect_uri, and 302s the upstream code straight back to the client (transparent PKCE: the client's code_challenge was forwarded upstream, so the client redeems the relayed code with its own code_verifier). * POST /token — exchanges the (relayed upstream) code + the client's code_verifier at the upstream token endpoint using the fixed upstream credentials (proxy.tokenForm/proxy.tokenAuthHeader), relaying the upstream token response back to the client verbatim.

The pure relay helpers (buildClientCallbackRedirect, redirectUrisFrom, ProxyStateStore, consentScreenHtml) carry no HTTP state and are unit-tested directly; the mountOAuthProxy wiring threads them onto the router.

Members

Classes

ProxyStateStore
class ProxyStateStore

A thread-safe in-memory store mapping a proxy state to the client's pending-authorization details. Entries are consumed (single use) on lookup so a relayed callback cannot be replayed.

Functions

buildClientCallbackError
string buildClientCallbackError(string clientRedirectUri, string error, string errorDescription, string errorUri, string clientState)

Append an RFC 6749 §4.1.2.1 authorization error (and, when present, the client's original state) as query parameters to the client's dynamic redirect_uri, producing the Location the proxy 302s to when the upstream authorization server redirects back with an error instead of a code. error is mandatory; errorDescription and errorUri are appended only when non-empty. Symmetric to buildClientCallbackRedirect.

buildClientCallbackRedirect
string buildClientCallbackRedirect(string clientRedirectUri, string code, string clientState)

Append code (and, when present, the client's original state) as query parameters to the client's dynamic redirect_uri, producing the Location the proxy 302s to once the upstream callback fires. The client supplied the redirect_uri at /authorize; the proxy relays the upstream authorization code to it so the client can redeem it (with its own PKCE code_verifier) at the proxy /token endpoint.

consentScreenHtml
string consentScreenHtml(string clientRedirectUri, string consentPath, string proxyState)

Build the minimal HTML consent screen presented when a dynamically-registered client (identified by its clientRedirectUri) has not yet been approved to be forwarded to the upstream authorization server. The MCP authorization spec (§Security Considerations > Confused Deputy Problem) requires a proxy using a static upstream client_id to obtain user consent for EACH dynamically registered client before forwarding it upstream. The screen offers a single approval action: a <form method="POST"> targeting consentPath and carrying the opaque proxy state as a hidden field. Posting it records consent and resumes the upstream redirect. Using a form POST (rather than a hyperlink GET) means link prefetch/preload cannot auto-fire the state-changing grant and the opaque state is not carried in a URL that could leak via Referer/history/logs.

invalidRequestJson
Json invalidRequestJson(string description)

Build the RFC 6749 §5.2 invalid_request error document returned (with HTTP 400) when a client presents a redirect_uri that is not registered or uses a disallowed scheme. The offending code is never relayed.

mountOAuthProxy
void mountOAuthProxy(URLRouter router, OAuthProxy proxy)

Mount the full client-facing OAuth surface of an OAuthProxy onto a vibe.d URLRouter. Registers the AS-metadata + PRM well-known documents, the RFC 7591 /register endpoint, the /authorize -> upstream redirect (persisting the client's dynamic redirect URI), the fixed callback that relays the upstream code back to the client, and the /token endpoint that exchanges the relayed code at the upstream with the fixed credentials. Paths are derived from the proxy's own configured endpoints so they line up exactly with the AS metadata it publishes.

redirectUrisFrom
string[] redirectUrisFrom(Json body_)

Extract the redirect_uris array from a parsed RFC 7591 DCR request body, returning an empty array when the field is absent or malformed. The number of URIs collected is capped at maxRedirectUrisPerRegistration so an unauthenticated POST /register carrying an oversized array cannot force an unbounded allocation here before the proxy's own registration cap applies.

Structs

ProxyAuthState
struct ProxyAuthState

The per-authorization state the proxy persists between /authorize and the upstream callback: the client's dynamic redirect_uri and the client's own state, keyed by a freshly minted opaque proxy state that is the only state value sent upstream. The client's PKCE code_challenge and scope are also retained so the upstream authorize redirect can be (re)built after a consent-approval round-trip (confused-deputy mitigation).