Conversation
…ience IdentityAssertionGrantProvider passed authorizationServerUrl.ToString() as the RFC 8693 audience. Uri.ToString() appends a trailing slash to an empty path, so the common case (https://auth.example.com, whose metadata advertises the issuer without a slash) sent an audience that did not match the issuer. Use the issuer from the authorization server metadata already fetched in step 1, falling back to the configured URL when the metadata omits it. Fixes modelcontextprotocol#1617 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Before making the discovered
The new theory test actually makes the mismatch observable: for an authorization server URL of Because this PR changes the audience from the caller-selected AS URL to metadata-controlled input, I would validate the issuer first and make the mismatched-issuer case fail rather than assert it as supported behavior. AI-assisted review; checked the current head and both metadata-discovery implementations before posting. |
Fixes #1617
Problem
IdentityAssertionGrantProvidersendsauthorizationServerUrl.ToString()as theaudienceof the RFC 8693 token exchange at the IdP. That value isn't guaranteed to match the MCP authorization server's issuer identifier, and in the most common setup it doesn't:new Uri("https://auth.example.com").ToString()returns"https://auth.example.com/", while the metadata advertises"issuer": "https://auth.example.com". An IdP that compares the audience against the issuer exactly will reject the exchange.Fix
Use the
issuerfrom the authorization server metadata that step 1 of the flow already fetches (mcpAuthMetadata).Issuer.OriginalStringis used so the value is sent exactly as published, sinceUri.ToString()would reintroduce the same trailing-slash normalization. This matches howClientOAuthProvidercompares issuers, and the direction taken by the Python SDK (override_audience_with_issuer, modelcontextprotocol/python-sdk#1721).If the metadata doesn't include an
issuer, it falls back to the previous behavior, so nothing that works today changes.Scope is limited to the audience, as described in the issue.
resourceis left as is.Tests
Added
IdentityAssertionGrantProvider_UsesDiscoveredIssuerAsJagAudience(theory), which reads theaudienceform field sent to the IdP:https://auth.example.comhttps://auth.example.comhttps://auth.example.comhttps://auth.example.com/https://auth.example.com/tenanthttps://auth.example.com/tenanthttps://auth.example.comhttps://auth.example.com/(fallback)The first two cases fail on
main(actual:https://auth.example.com/) and pass with this change.IdentityAssertionGrantTestsand the AspNetCoreOAuthintegration tests pass locally on net10.0, net9.0, net8.0 and net472. The fullModelContextProtocol.Testssuite also passes on net10.0 (2399 tests, 6 skipped because they need an OpenAI key).🤖 Generated with Claude Code