Skip to content

fix(auth): use the discovered issuer as the ID-JAG token-exchange audience - #1890

Open
mauri0686 wants to merge 1 commit into
modelcontextprotocol:mainfrom
mauri0686:fix/1617-jag-audience-issuer
Open

mauri0686 wants to merge 1 commit into
modelcontextprotocol:mainfrom
mauri0686:fix/1617-jag-audience-issuer

Conversation

@mauri0686

@mauri0686 mauri0686 commented Sep 24, 2026 •

Copy link
Copy Markdown

Fixes #1617

Problem

IdentityAssertionGrantProvider sends authorizationServerUrl.ToString() as the audience of 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 issuer from the authorization server metadata that step 1 of the flow already fetches (mcpAuthMetadata). Issuer.OriginalString is used so the value is sent exactly as published, since Uri.ToString() would reintroduce the same trailing-slash normalization. This matches how ClientOAuthProvider compares 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. resource is left as is.

Tests

Added IdentityAssertionGrantProvider_UsesDiscoveredIssuerAsJagAudience (theory), which reads the audience form field sent to the IdP:

configured URL advertised issuer expected audience
https://auth.example.com https://auth.example.com https://auth.example.com
https://auth.example.com/ https://auth.example.com/tenant https://auth.example.com/tenant
https://auth.example.com (missing) https://auth.example.com/ (fallback)

The first two cases fail on main (actual: https://auth.example.com/) and pass with this change. IdentityAssertionGrantTests and the AspNetCore OAuth integration tests pass locally on net10.0, net9.0, net8.0 and net472. The full ModelContextProtocol.Tests suite also passes on net10.0 (2399 tests, 6 skipped because they need an OpenAI key).

🤖 Generated with Claude Code

…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>

This branch has not been deployed

No deployments
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.

Identity Assertion Grant uses raw authorization server URL as token-exchange audience

1 participant