@@ -997,9 +997,9 @@ def exchange_refresh_token(as_metadata:, client_info:, refresh_token:, resource:
997997 post_to_token_endpoint ( as_metadata : as_metadata , client_info : client_info , form : form )
998998 end
999999
1000- # Submits a form-encoded request to the token endpoint, applying
1001- # the client authentication method advertised in `client_information` and
1002- # adding `client_id` (and `client_secret` when not using HTTP Basic) .
1000+ # Submits a form-encoded token request using the authentication method
1001+ # stored in `client_information`. The method determines whether client
1002+ # credentials belong in the form body, a Basic header, or a JWT assertion .
10031003 def post_to_token_endpoint ( as_metadata :, client_info :, form :)
10041004 client_id = client_info_required_value ( client_info , "client_id" )
10051005 unless client_id
@@ -1010,17 +1010,13 @@ def post_to_token_endpoint(as_metadata:, client_info:, form:)
10101010 client_secret = client_info_required_value ( client_info , "client_secret" )
10111011 token_endpoint_auth_method = client_info_value ( client_info , "token_endpoint_auth_method" )
10121012
1013- # Each branch carries the client identity in exactly one place. Sending
1014- # it twice reads as two client authentication methods to a strict
1015- # authorization server (RFC 6749 Section 2.3), which rejects the request.
1013+ # Apply one client authentication method per request (RFC 6749 Section 2.3).
10161014 headers = { }
10171015 form = case token_endpoint_auth_method
10181016 when "private_key_jwt"
1019- # RFC 7523 Section 2.2 JWT client assertion for the `private_key_jwt` method of
1020- # the `io.modelcontextprotocol/oauth-client-credentials` extension (SEP-1046).
1021- # The client identity travels in the assertion's `iss`/`sub` claims, so `client_id` is
1022- # omitted from the body per RFC 7521 Section 4.2 (the `client_assertion` conveys the client identity).
1023- # The audience is the issuer identifier that `ensure_issuer_matches!` already byte-validated.
1017+ # The assertion identifies the client through its `iss` and `sub`
1018+ # claims, so the body needs no separate `client_id` (RFC 7521 Section 4.2).
1019+ # Use the issuer already checked by `ensure_issuer_matches!` as the audience.
10241020 unless @provider . respond_to? ( :client_assertion )
10251021 raise AuthorizationError ,
10261022 "token_endpoint_auth_method is private_key_jwt but the provider does not " \
@@ -1032,24 +1028,22 @@ def post_to_token_endpoint(as_metadata:, client_info:, form:)
10321028 "client_assertion" => @provider . client_assertion ( audience : as_metadata [ "issuer" ] ) ,
10331029 )
10341030 when "client_secret_post"
1035- # RFC 6749 Section 2.3.1: both credentials travel in the request body.
1031+ # Send the client ID and available secret in the form body.
10361032 body = form . merge ( "client_id" => client_id )
10371033 body [ "client_secret" ] = client_secret if client_secret
10381034 body
10391035 else
10401036 if client_secret && token_endpoint_auth_method != "none"
1041- # RFC 6749 Section 2.3.1 recommends Basic for confidential clients and
1042- # both Python and TypeScript SDKs default here when
1043- # the authentication method is not explicitly stored. The Basic username
1044- # is already the client identifier, so a body `client_id` adds nothing;
1045- # Section 3.2.1 makes it a MAY, and servers that read it as a second
1046- # authentication method reject the request with `invalid_request`.
1037+ # Basic is also the fallback when a secret is present but no method
1038+ # is stored. A body `client_id` is optional (RFC 6749 Section 3.2.1);
1039+ # omit it because some servers treat it alongside Basic as a second
1040+ # authentication method and reject the request with `invalid_request`.
10471041 headers [ "Authorization" ] = "Basic " + basic_auth_credentials ( client_id , client_secret )
10481042 form
10491043 else
1050- # Public client, whether by an explicit `none` or the absence of a
1051- # secret. `client_id` is the only thing identifying it, and the
1052- # authorization_code grant requires it (RFC 6749 Section 3.2.1).
1044+ # With `none` or no secret, identify the client using `client_id`.
1045+ # This is required for unauthenticated authorization-code exchanges
1046+ # (RFC 6749 Section 3.2.1).
10531047 form . merge ( "client_id" => client_id )
10541048 end
10551049 end
0 commit comments