Skip to content

fix: use resource URL (not authorization server) as OAuth base_url; forward RFC 9207 iss - #13

Open
tprunk wants to merge 1 commit into
hyper-mcp-rs:mainfrom
tprunk:fix/oauth-resource-and-iss
Open

fix: use resource URL (not authorization server) as OAuth base_url; forward RFC 9207 iss#13
tprunk wants to merge 1 commit into
hyper-mcp-rs:mainfrom
tprunk:fix/oauth-resource-and-iss

Conversation

@tprunk

@tprunk tprunk commented Aug 10, 2026

Copy link
Copy Markdown

Summary

Two related OAuth bugs that together break authentication against any
server whose resource and authorization server share an origin and that
implements RFC 9207 (e.g. mcp.cloudflare.com):

  • new_oauth_state passed discovery.authorization_server (the issuer
    URL) as rmcp's base_url. rmcp's AuthorizationManager treats
    base_url as the resource server URL — it re-fetches and validates
    Protected Resource Metadata against it (SEP-985) and uses it as the
    RFC 8707 resource parameter on authorize/token/refresh requests.
    Passing the issuer URL instead makes rmcp's own PRM re-fetch mismatch
    against the resource field the server correctly advertises, e.g.:

    Protected resource metadata resource mismatch: expected
    'https://mcp.cloudflare.com/', got 'https://mcp.cloudflare.com/mcp'
    

    It also meant the resource parameter sent in actual OAuth requests
    pointed at the authorization server instead of the resource server —
    wrong per RFC 8707 independent of the mismatch bug.

  • Separately, CallbackServer dropped the RFC 9207 iss query
    parameter, and run_interactive_flow called rmcp's issuer-blind
    handle_callback instead of handle_callback_with_issuer. Any
    authorization server that advertises
    authorization_response_iss_parameter_supported: true and sends
    iss on redirect (Cloudflare does) then fails token exchange with:

    Authorization server response missing required issuer: expected
    https://mcp.cloudflare.com
    

Changes

  • src/auth/mod.rs: pass discovery.resource instead of
    discovery.authorization_server to OAuthState::new; forward
    code.iss via handle_callback_with_issuer.
  • src/auth/callback.rs: capture iss from the callback query string
    and carry it on AuthCode.
  • src/auth/discovery.rs: update doc comments on OAuthDiscovery to
    match the corrected field usage; resource is no longer dead code.
  • Tests: mock_discovery's resource field now points at the mock
    server instead of a fake example.com URL, since set_credentials/
    refresh now actually dereference it; added two regression tests (see
    below).

Test plan

  • cargo test — 122 passed, 0 failed (no regression)
  • cargo test — 2 passed, 0 failed (new regression tests, one per
    bug: start_authorization_succeeds_when_resource_and_authorization_server_share_origin
    reproduces Cloudflare's PRM shape and fails with the original
    resource-mismatch error when the base_url fix is reverted;
    callback_with_iss_param_is_captured asserts the RFC 9207 iss
    param survives the local callback round-trip instead of being
    silently dropped)
  • Manual end-to-end run against https://mcp.cloudflare.com/mcp:
    full OAuth flow (browser consent, PKCE, iss-validated code
    exchange) completes and a subsequent initialize request returns
    a valid MCP response, where main previously failed at the PRM
    validation step before a browser was even opened.

…orward RFC 9207 iss

new_oauth_state passed discovery.authorization_server as rmcp's base_url,
but rmcp treats base_url as the resource server URL for both SEP-985 PRM
validation and the RFC 8707 resource parameter. Servers whose resource
and authorization server share an origin (e.g. mcp.cloudflare.com) then
fail with "Protected resource metadata resource mismatch" before a
browser is even opened.

Separately, CallbackServer dropped the RFC 9207 iss query parameter and
run_interactive_flow called rmcp's issuer-blind handle_callback. Any
authorization server that requires iss back at token-exchange time
(Cloudflare's does) then fails with "response missing required issuer".

Adds regression tests for both: a Cloudflare-shaped mock PRM server for
the base_url bug, and a callback-server test asserting iss is captured.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant