Repository navigation
Server TokenHandler rejects the jwt-bearer (ID-JAG) grant from public CIMD clients, which the enterprise-managed authorization extension permits #3598
Description
Activity
@seidnerj j are you planning to fix this yourself? If not, I've been looking at it.
The only blocker I found is the
if not client_info.client_secretcheck in TokenHandler. ClientAuthenticator already letsnoneclients through, so the check is all that stops a CIMD client.The approach I tried: allow a secretless client when its client_id is a CIMD URL (https with a path), and keep refusing every other public client. The grant is still gated by the registered grant_types, and DCR still refuses to register it.
One question on intended behavior: is a URL-shape check on client_id the right way to detect a CIMD client here, or would you rather the provider decide?
I have a working version with tests on a branch if it's useful.
AI disclosure: I used an AI assistant while working on this and reviewed the change myself.
Hey Sanjay,
Thx for digging into this! I already have a fix on a branch and will open a PR shortly.
On your question, I went with a provider hook (is_metadata_document_client) rather than a URL-shape check on client_id. I think whether a client came from a metadata document is the provider's call - a provider may resolve CIMD clients differently, or register https-URL client_ids some other way, and a shape check can't tell those apart.
I also added a check I think a secretless client needs: the ID-JAG's client_id claim has to match the requesting client, otherwise a public client could redeem an assertion that was issued to someone else.
Would be happy to have your review on the PR.
AI disclosure: I used an AI assistant while working on this and reviewed the change myself.
Some context on why this matters for me, in case it helps with triage.
I'm building MCP servers that support enterprise-managed authorization, where clients identify themselves with a Client ID Metadata Document and never get a client secret. With the current TokenHandler, every one of those clients is refused on the jwt-bearer grant, so the ID-JAG flow from the extension can't work for them unless the server works around the SDK's handler.
Repro is short: a CIMD client (https client_id, jwt-bearer in its grant_types, token_endpoint_auth_method none) posts grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer with a valid ID-JAG and no client_secret. It gets 400 unauthorized_client, "The JWT bearer grant requires a confidential client", before the provider ever sees the assertion.
The fix is in #3606, which the bot closed since I'm not assigned here. Happy to take this if a maintainer assigns it to me.
AI disclosure: I used an AI assistant while working on this and reviewed the change myself.
The built-in authorization server authenticates clients by shared secret only and doesn't currently support clients identified by a Client ID Metadata Document, so a client with no secret can't use the ID-JAG grant against it. You were right that this is the SDK's own policy rather than something SEP-990 requires, so #3622 clarifies the docs, the code comments and the client's error message to say so. Server-side support for those clients is tracked in the older issue #1801, which is where this use case belongs. Until then, a deployment that needs a different policy can swap the
/tokenroute thatcreate_auth_routesreturns for its own.
Problem
With
identity_assertion_enabled=True,TokenHandlerrejects everyurn:ietf:params:oauth:grant-type:jwt-bearerrequest from a client that has no storedclient_secret:A client identified by a Client ID Metadata Document (CIMD) cannot hold a shared secret (CIMD forbids
client_secret_*), so the only methods open to it arenoneandprivate_key_jwt. SinceClientAuthenticatordoes not implementprivate_key_jwt, a CIMD client can never complete the ID-JAG exchange against an SDK-based authorization server.This blocks real clients. At least one widely deployed MCP client documents its enterprise-managed authorization token request as carrying only
grant_type,assertion,client_id(a CIMD URL),scopeandresource, with no client secret and no client assertion.What the extension spec says
modelcontextprotocol/ext-auth,specification/stable/enterprise-managed-authorization.mdx, section 5 (Access Token Request):Its example request has no client authentication at all:
So for a CIMD client, client authentication at this step is optional. The current check is stricter than the spec and turns away the case the spec calls out by name.
Expected
A CIMD client with
token_endpoint_auth_method="none"can exchange a valid ID-JAG issued for itsclient_id. Pre-registered clients keep today's behavior (they must authenticate as registered).Related
nonemissing fromtoken_endpoint_auth_methods_supported)nonehandling inClientAuthenticator)AI disclosure: I used an AI coding assistant to draft this issue; I have reviewed it and can answer for it.