Skip to content

[Client] Add client ID metadata documents and a console authorization handler - #668

Draft
chr-hertel wants to merge 4 commits into
modelcontextprotocol:mainfrom
chr-hertel:client-auth-identity
Draft

chr-hertel wants to merge 4 commits into
modelcontextprotocol:mainfrom
chr-hertel:client-auth-identity

Conversation

@chr-hertel

Copy link
Copy Markdown
Member

Fourth slice of the client authorization in chr-hertel#53. Builds on #666, so please only look at the last commit until that one is merged.

  • setClientMetadataUrl() uses a client ID metadata document as the client id where the authorization server supports it, otherwise the client registers as before. The URL needs https & a path, same check as the TypeScript SDK.
  • ConsoleAuthorizationHandler for a remote shell or a container without a browser. It wants the whole redirected URL and refuses a bare code, which would skip the state & iss checks.

auth/basic-cimd leaves both conformance baselines with this. Machine grants come next.

@chr-hertel chr-hertel added Client Issues & PRs related to the Client component auth Issues and PRs related to Authentication / OAuth labels Nov 5, 2026
@chr-hertel chr-hertel added this to the 1.0.0 milestone Nov 5, 2026
@chr-hertel
chr-hertel requested a balanced review from Copilot November 8, 2026 11:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The client ID URL guard accepts malformed URLs and forbidden components, which can cause authorization failures and need stricter validation with regression tests.

1 open issue
What changed in this PR

This extends the PHP SDK’s client authorization work from #666 with client ID metadata documents and a console handler for environments without a browser.

Changes:

  • Adds metadata-document URLs as client IDs, with registration fallback when unsupported.
  • Adds console authorization using the full redirected URL to preserve response checks.
  • Expands authorization documentation and tests, including conformance coverage and Keycloak integration.
File Description
tests/​Unit/​Client/​Transport/​HttpTransportTest.php Tests credentials and authorization failure handling.
tests/​Unit/​Client/​Auth/​TokenEndpointTest.php Tests token authentication and error responses.
tests/​Unit/​Client/​Auth/​TokenEndpointAuthMethodTest.php Tests authentication-method negotiation.
tests/​Unit/​Client/​Auth/​ProtectedResourceMetadataTest.php Tests resource URL boundaries.
tests/​Unit/​Client/​Auth/​PkceTest.php Tests PKCE generation and reconstruction.
tests/​Unit/​Client/​Auth/​OAuthTest.php Tests builder settings and metadata URL validation.
tests/​Unit/​Client/​Auth/​MetadataDiscoveryTest.php Tests discovery locations and issuer checks.
tests/​Unit/​Client/​Auth/​FileCredentialStorageTest.php Tests credential persistence and permissions.
tests/​Unit/​Client/​Auth/​ClientRegistrationTest.php Tests registration requests and responses.
tests/​Unit/​Client/​Auth/​AuthorizationServerMetadataTest.php Tests metadata parsing and endpoint validation.
tests/​Unit/​Client/​Auth/​AuthorizationHandlerTest.php Tests loopback and console handlers.
tests/​Unit/​Client/​Auth/​AuthorizationChallengeTest.php Tests Bearer challenge parsing.
tests/​Unit/​Client/​Auth/​AuthenticatingHttpClientTest.php Tests retries, body replay and response cleanup.
tests/​Unit/​Client/​Auth/​AccessTokenTest.php Tests token expiry, scopes and serialization.
tests/​Integration/​Fixture/​oauth/​resource-server.php Provides a protected MCP test server.
tests/​Integration/​Fixture/​oauth/​authorization-server.php Provides registration and PKCE test endpoints.
tests/​Integration/​ClientOAuthTest.php Exercises authorization over real HTTP.
tests/​E2E/​server.php Validates Keycloak tokens before serving MCP.
tests/​E2E/​run.sh Runs the container-based authorization suite.
tests/​E2E/​README.md Documents the end-to-end setup.
tests/​E2E/​nginx.conf Routes requests to the protected server.
tests/​E2E/​keycloak/​realm.json Defines the test realm and token audience.
tests/​E2E/​docker-compose.yml Configures Keycloak and MCP containers.
tests/​E2E/​client.php Checks protected access and credential reuse.
tests/​E2E/​BrowserAuthorizationHandler.php Automates login for end-to-end tests.
tests/​Conformance/​conformance-baseline-2026-07-28.yml Removes covered authorization exceptions.
tests/​Conformance/​conformance-baseline-2025-11-25.yml Removes covered cases; retains machine-grant exceptions.
tests/​Conformance/​client.php Configures OAuth and metadata-document scenarios.
src/​Exception/​AuthorizationException.php Defines client authorization failures.
src/​Client/​Transport/​HttpTransport.php Integrates authentication and refusal handling.
src/​Client/​Auth/​TokenEndpointAuthMethod.php Selects token endpoint authentication methods.
src/​Client/​Auth/​TokenEndpoint.php Exchanges credentials for access tokens.
src/​Client/​Auth/​ProtectedResourceMetadata.php Parses resource metadata and checks URL coverage.
src/​Client/​Auth/​Pkce.php Generates PKCE verifiers and challenges.
src/​Client/​Auth/​OAuthConfiguration.php Holds application authorization settings.
src/​Client/​Auth/​OAuth.php Builds OAuth authentication, including metadata URLs.
src/​Client/​Auth/​MetadataDiscovery.php Discovers resource and authorization metadata.
src/​Client/​Auth/​LoopbackAuthorizationHandler.php Opens a browser and receives callbacks.
src/​Client/​Auth/​InMemoryCredentialStorage.php Stores credentials for the process lifetime.
src/​Client/​Auth/​HeadlessAuthorizationHandler.php Reads redirects without interactive login.
src/​Client/​Auth/​Grant.php Defines supported OAuth grant types.
src/​Client/​Auth/​FileCredentialStorage.php Persists credentials in private files.
src/​Client/​Auth/​CredentialStorageInterface.php Defines credential storage operations.
src/​Client/​Auth/​ConsoleAuthorizationHandler.php Prompts for the full redirected URL.
src/​Client/​Auth/​ClientRegistration.php Represents client identity and credentials.
src/​Client/​Auth/​ClientRegistrar.php Performs dynamic client registration.
src/​Client/​Auth/​ClientMetadata.php Serializes client registration metadata.
src/​Client/​Auth/​BearerToken.php Attaches an existing bearer token.
src/​Client/​Auth/​AuthorizationServerMetadata.php Parses authorization server capabilities.
src/​Client/​Auth/​AuthorizationHandlerInterface.php Defines authorization interaction handlers.
src/​Client/​Auth/​AuthorizationChallenge.php Extracts Bearer challenge parameters.
src/​Client/​Auth/​AuthenticatorInterface.php Defines authentication and challenge handling.
src/​Client/​Auth/​AuthenticatingHttpClient.php Adds authentication and bounded retries.
src/​Client/​Auth/​AccessToken.php Represents tokens, expiry and granted scopes.
src/​Client.php Documents authorization exceptions on client operations.
mkdocs.yml Adds authorization to documentation navigation.
Makefile Adds the end-to-end test target.
examples/​client/​README.md Introduces the OAuth client example.
examples/​client/​oauth_client.php Demonstrates authorization with stored credentials.
docs/​examples.md Links the OAuth example.
docs/​client/​transports.md Documents transport authentication options.
docs/​client/​index.md Links the authorization guide.
docs/​client/​errors.md Explains authorization exception handling.
docs/​client/​authorization.md Documents OAuth setup, handlers and safeguards.
CHANGELOG.md Records authorization features and exception changes.
.gitignore Excludes credentials and generated test artifacts.
.github/​workflows/​pipeline.yaml Adds the authorization end-to-end job.

Review effort: Balanced

Comment thread src/Client/Auth/OAuth.php Resolved
The HTTP client gets decorated, so the transport keeps speaking plain PSR-18:
AuthenticatingHttpClient authenticates every request and retries the ones the
authenticator can act on. BearerToken sends a token you already have.

A 401 or 403 now fails with an AuthorizationException that names the challenge,
which connect() wraps in a ConnectionException without a retry.
Finds and validates the documents that say who guards an MCP server: protected
resource metadata (RFC 9728) and authorization server metadata (RFC 8414 and
OpenID Connect), plus the WWW-Authenticate challenge that points at them.

All of it is @internal, since nothing uses it until the authenticator lands.
Endpoints have to pass SecureUrl, and a document naming another issuer than it
was fetched for is discarded.
Pass OAuth::forApplication('My App')->build() as `auth` and a client answers a
401 with discovery, client registration, the authorization code flow with PKCE
and a retry. It refreshes tokens, handles step-up challenges and refuses what a
hostile server could send.

Client id metadata documents, the console handler, client credentials,
private_key_jwt and cross-app access follow in their own changes, so their
conformance scenarios stay in the baselines.
… handler

setClientMetadataUrl() uses a published document as the client id where the
authorization server supports it, and otherwise registers as before. The URL has
to be https with a path.

ConsoleAuthorizationHandler prints the authorization URL and reads back the
redirected URL, for a shell without a browser. It refuses a bare code, which
would skip the state and iss checks.
@chr-hertel
chr-hertel force-pushed the client-auth-identity branch from 8f43219 to dbdffb7 Compare November 8, 2026 12:09

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

auth Issues and PRs related to Authentication / OAuth Client Issues & PRs related to the Client component

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants