Conversation
The enterprise-managed authorization extension (section 5) lets a client that is not pre-registered use its Client ID Metadata Document URL as client_id and present an ID-JAG without client authentication. The token handler rejected every secretless client for this grant. Add OAuthAuthorizationServerProvider.is_metadata_document_client (default False). When it returns True for a secretless client, the handler accepts the grant if the ID-JAG's client_id claim names the requesting client, and otherwise answers invalid_grant. Advertise none in token_endpoint_auth_methods_supported when identity assertion is enabled. Fixes modelcontextprotocol#3598
|
This PR has been closed automatically. This repo only keeps pull requests open when they come from a maintainer, or from a contributor a maintainer has assigned to the linked issue, and you aren't currently assigned to #3598. If a maintainer assigns you to #3598, this PR reopens on its own and there's nothing more you need to do here. Assignment is a maintainer call based on capacity; comments that only ask to be assigned don't factor in. What does help is engaging on the issue itself by confirming the repro, explaining why it matters for your use case, or describing the approach you'd take. You're welcome to keep pushing commits here (just avoid force-pushing, since GitHub can't reopen a rewritten branch), but that on its own won't get the PR reviewed or the issue assigned, and realistically most auto-closed PRs stay closed. There's no need to open a new PR either way. CONTRIBUTING.md has the full reasoning, but in short:
Maintainers: reopen, remove |
Fixes #3598
Summary
The identity-assertion branch of
TokenHandlerrejects every client without a storedclient_secret. The enterprise-managed authorization extension (section 5) lets a client that is not pre-registered use its Client ID Metadata Document URL asclient_idand authenticate optionally withprivate_key_jwt, so a public CIMD client presenting only the ID-JAG is a valid request.This change relaxes the check for that one case only:
is_metadata_document_client(client), decides what counts as a CIMD client. It defaults toFalse, so servers that do not support CIMD see no behavior change.client_idclaim to equal the requesting client'sclient_id, and answersinvalid_grantotherwise (including a malformed assertion or a missing claim), before the provider hook runs.jwt-beareringrant_types(unchanged), and a CIMD client must still list the grant type in its metadata.Why this is safe
The authority for this grant is the ID-JAG, not the client credential.
exchange_identity_assertionalready has to verify the assertion's signature,iss,aud,resource,expandjti, and bind itsclient_idclaim to the client. The handler's extra check readsclient_idunverified, which is safe because a mismatch can only cause rejection; the provider still verifies the signature before issuing anything. It guarantees an ID-JAG issued to one client cannot be redeemed by another. A stolen ID-JAG gives at most a single, short-lived redemption for the named client and resource, similar to a bearer authorization code for a public PKCE client. Confidential clients keep proving possession of their secret.Changes
server/auth/provider.py: addis_metadata_document_client(defaultFalse); update theexchange_identity_assertiondocstring, which no longer promises a confidential client.server/auth/handlers/token.py: allow a secretless client when the provider reports it is a CIMD client and the assertion'sclient_idclaim names it; otherwise keepunauthorized_client.server/auth/routes.py: advertisenoneintoken_endpoint_auth_methods_supportedonly when identity assertion is enabled. This touches the same line as fix: include "none" in token_endpoint_auth_methods_supported metadata #2261, which addsnoneunconditionally; whichever lands second needs a trivial rebase.docs/client/identity-assertion.md: one sentence on the exception.Tests (
tests/server/auth/test_identity_assertion.py)client_idclaim, or malformed:invalid_grant, provider hook not called.grant_types:unsupported_grant_type.unauthorized_client(existing test, now presenting a matching assertion so only the CIMD check can reject it).invalid_client. Correct secret: 200 (existing test).identity_assertion_enabled=False: CIMD client getsunsupported_grant_type.grant_types: still rejected (existing test).noneonly when identity assertion is enabled; the default provider's hook returnsFalse.The new-behavior tests fail on
mainand pass with this change../scripts/testpasses with 100% coverage;pyrightandruffare clean.AI disclosure: I used an AI coding assistant to draft this change and its tests; I have reviewed them and can answer for them.