Support OAuth client_credentials grant in OAuth client#399
Open
koic wants to merge 1 commit into
Open
Conversation
## Motivation and Context The MCP authorization specification and SEP-1046 allow confidential clients to obtain tokens through the OAuth 2.1 `client_credentials` grant: a machine-to-machine flow with no user, no browser redirect, and no authorization code. The Ruby SDK previously supported only the interactive authorization-code flow, so it could not authenticate against a server that requires `client_credentials`. This makes the `auth/client-credentials-basic` conformance scenario pass and is removed from the expected failures list. Following the TypeScript SDK (`ClientCredentialsProvider`) and the Python SDK (`ClientCredentialsOAuthProvider`), the grant gets its own provider class rather than overloading the authorization-code provider. The change adds: - `MCP::Client::OAuth::ClientCredentialsProvider`, a dedicated provider for the grant. `client_id` and `client_secret` are mandatory (the grant is for confidential clients), `token_endpoint_auth_method` is `client_secret_basic` (default) or `client_secret_post` -- `none` is rejected because an unauthenticated `client_credentials` request is meaningless. It has no redirect arguments, so a credentials-only client never supplies the redirect URI / handlers that grant has no use for. - `MCP::Client::OAuth::StorageBackedProvider`, an internal module that shares the token / client-information persistence between the two provider classes so `Flow` can treat any provider uniformly. - `Provider#authorization_flow` (`:authorization_code`) and `ClientCredentialsProvider#authorization_flow` (`:client_credentials`). `Flow#run!` dispatches on this rather than inspecting `client_metadata[:grant_types]`, which is protocol metadata for the authorization server, not an SDK control signal. - `run_client_credentials!` shares the same PRM / AS metadata discovery and Communication Security checks as the interactive flow, then exchanges `grant_type=client_credentials` at the token endpoint via the existing `post_to_token_endpoint` (which already applies `client_secret_basic`). There is no PKCE, redirect, or `offline_access` augmentation, because the grant does not issue a refresh token (OAuth 2.1 Section 4.3.3). - `conformance/client.rb` builds a `ClientCredentialsProvider` for the `auth/client-credentials-*` scenarios, reusing the harness-injected `client_id` / `client_secret`. ## How Has This Been Tested? New `ClientCredentialsProvider` tests cover: credentials are stored as client information; `authorization_flow` is `:client_credentials`; `client_secret_post` is accepted; missing `client_id` / `client_secret` and `token_endpoint_auth_method: "none"` are rejected; and the token helpers delegate to storage. New `Flow` tests cover: a `client_credentials` provider exchanges `grant_type=client_credentials` with HTTP Basic auth, saves the token, and never contacts the authorization or registration endpoint; the requested scope is taken from PRM `scopes_supported`; and a standard `Provider` runs the authorization-code grant regardless of what its `client_metadata[:grant_types]` lists. A `Provider` test asserts `authorization_flow` is `:authorization_code`. Conformance: `auth/client-credentials-basic` now passes 7/7 with no failures. ## Breaking Changes None. `ClientCredentialsProvider` is a new class; the existing `Provider` keeps its required `redirect_uri` / `redirect_handler` / `callback_handler` arguments and its behaviour is unchanged. Existing interactive clients are unaffected.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation and Context
The MCP authorization specification and SEP-1046 allow confidential clients to obtain tokens through the OAuth 2.1
client_credentialsgrant: a machine-to-machine flow with no user, no browser redirect, and no authorization code. The Ruby SDK previously supported only the interactive authorization-code flow, so it could not authenticate against a server that requiresclient_credentials. This makes theauth/client-credentials-basicconformance scenario pass and is removed from the expected failures list.Following the TypeScript SDK (
ClientCredentialsProvider) and the Python SDK (ClientCredentialsOAuthProvider), the grant gets its own provider class rather than overloading the authorization-code provider. The change adds:MCP::Client::OAuth::ClientCredentialsProvider, a dedicated provider for the grant.client_idandclient_secretare mandatory (the grant is for confidential clients),token_endpoint_auth_methodisclient_secret_basic(default) orclient_secret_post--noneis rejected because an unauthenticatedclient_credentialsrequest is meaningless. It has no redirect arguments, so a credentials-only client never supplies the redirect URI / handlers that grant has no use for.MCP::Client::OAuth::StorageBackedProvider, an internal module that shares the token / client-information persistence between the two provider classes soFlowcan treat any provider uniformly.Provider#authorization_flow(:authorization_code) andClientCredentialsProvider#authorization_flow(:client_credentials).Flow#run!dispatches on this rather than inspectingclient_metadata[:grant_types], which is protocol metadata for the authorization server, not an SDK control signal.run_client_credentials!shares the same PRM / AS metadata discovery and Communication Security checks as the interactive flow, then exchangesgrant_type=client_credentialsat the token endpoint via the existingpost_to_token_endpoint(which already appliesclient_secret_basic). There is no PKCE, redirect, oroffline_accessaugmentation, because the grant does not issue a refresh token (OAuth 2.1 Section 4.3.3).conformance/client.rbbuilds aClientCredentialsProviderfor theauth/client-credentials-*scenarios, reusing the harness-injectedclient_id/client_secret.How Has This Been Tested?
New
ClientCredentialsProvidertests cover: credentials are stored as client information;authorization_flowis:client_credentials;client_secret_postis accepted; missingclient_id/client_secretandtoken_endpoint_auth_method: "none"are rejected; and the token helpers delegate to storage.New
Flowtests cover: aclient_credentialsprovider exchangesgrant_type=client_credentialswith HTTP Basic auth, saves the token, and never contacts the authorization or registration endpoint; the requested scope is taken from PRMscopes_supported; and a standardProviderruns the authorization-code grant regardless of what itsclient_metadata[:grant_types]lists. AProvidertest assertsauthorization_flowis:authorization_code.Conformance:
auth/client-credentials-basicnow passes 7/7 with no failures.Breaking Changes
None.
ClientCredentialsProvideris a new class; the existingProviderkeeps its requiredredirect_uri/redirect_handler/callback_handlerarguments and its behaviour is unchanged. Existing interactive clients are unaffected.Types of changes
Checklist