Skip to content

[Client] Close the remaining OAuth and redirect gaps before 0.10.0 - #727

Merged
chr-hertel merged 24 commits into
modelcontextprotocol:mainfrom
chr-hertel:client-auth-before-tag
Nov 10, 2026
Merged

chr-hertel merged 24 commits into
modelcontextprotocol:mainfrom
chr-hertel:client-auth-before-tag

Conversation

@chr-hertel

@chr-hertel chr-hertel commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

Follow-up to #669 and #660 before tagging 0.10.0 - one commit per point:

  • key stored tokens by grant and client ID, and record on AccessToken the client it was issued to - options b) and c) of [Client] Key stored OAuth tokens by client, not only by issuer and resource #719, so CredentialStorageInterface stays as it is and a replaced dynamic registration can't hand its token to the next one
  • refuse an authorization server or endpoint in a private network (e.g. 169.254.169.254) that a remote server names, unless setExpectedIssuer() allows it
  • turn off redirects on a Symfony client passed to HttpTransport without auth, and name the HTTPlug clients that still follow them in the docs
  • note in the changelog what a redirect exposed before 0.10.0
  • a protected resource document for another resource no longer stops the next location
  • refresh a token shortly before it expires instead of waiting for the 401
  • a pending authorization on SSE resumption and a failing tell()/seek() no longer end up as transport or stream errors
  • credit bbdu3 as @author of the classes from [Client] Support completing OAuth authorization in a later request #660

Closes #719

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

Uncaught refresh-lock runtime failures can abort requests with still-valid access tokens and escape the PSR-18 client contract.

2 open issues
What changed in this PR

This PR hardens the PHP SDK’s OAuth client and HTTP transport before 0.10.0, following up on client authentication and deferred authorization support.

Changes:

  • Isolates stored tokens by grant and configured client ID, and adds proactive refresh.
  • Rejects private-network OAuth IP addresses in untrusted metadata and disables redirects on supplied Symfony clients.
  • Improves metadata discovery fallback, pending authorization propagation, and request-body replay handling.
File Description
tests/​Unit/​Server/​Transport/​Http/​OAuth/​SecureUrlTest.php Tests private-network IP detection.
tests/​Unit/​Client/​Transport/​HttpTransportTest.php Tests pending authorization and redirect handling.
tests/​Unit/​Client/​Auth/​OAuthAuthenticatorTest.php Updates token keys and tests isolation, refresh, and issuer restrictions.
tests/​Unit/​Client/​Auth/​MetadataDiscoveryTest.php Tests fallback after unrelated resource metadata.
tests/​Unit/​Client/​Auth/​AuthorizationServerMetadataTest.php Tests private-network endpoint restrictions.
tests/​Unit/​Client/​Auth/​AuthenticatingHttpClientTest.php Tests failed stream positioning during replay.
src/​Server/​Transport/​Http/​OAuth/​SecureUrl.php Adds private-network IP classification.
src/​Client/​Transport/​HttpTransport.php Disables supplied-client redirects and propagates pending authorization.
src/​Client/​Auth/​OAuthAuthenticator.php Adds token isolation, proactive refresh, and private-issuer checks.
src/​Client/​Auth/​MetadataDiscovery.php Continues discovery past unrelated resource documents.
src/​Client/​Auth/​CredentialStorageInterface.php Documents the composite token storage key.
src/​Client/​Auth/​AuthorizationServerMetadata.php Rejects private endpoints advertised by public issuers.
src/​Client/​Auth/​AuthenticatingHttpClient.php Returns rejected responses when replay positioning fails.
docs/​client/​transports.md Clarifies redirect controls and adapter behavior.
docs/​client/​authorization.md Documents token isolation, refresh, and network restrictions.
CHANGELOG.md Describes redirect exposure and credential-rotation guidance.

Review effort: Balanced

Comment thread src/Client/Auth/OAuthAuthenticator.php Resolved

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.

🔵 Needs a closer look

Stored-token authentication can abort on unguarded registration-read failures, and metadata validation rejects private-network token endpoints even when their issuer is explicitly trusted.

No open issues 🎉

2 previously missed issues

In code that hasn't changed since last review

Medium severity Allow trusted issuers to opt into private-network token endpoints

src/​Client/​Auth/​AuthorizationServerMetadata.php:93

The setExpectedIssuer() opt-in described in the PR and authorization guide cannot allow a trusted issuer such as https://auth.example.com whose token endpoint is https://10.0.0.5/token: this check unconditionally rejects the metadata before discovery can apply that setting. Carry the explicitly trusted issuer through endpoint validation so callers can opt into this private-network deployment while retaining rejection by default.

Medium severity Guard client registration lookup and preserve token fallback

src/​Client/​Auth/​OAuthAuthenticator.php:491

When a stored token is reused, $this->client is null, so this lookup reads the credential backend before entering the try block. If getClientRegistration() throws Mcp\Exception\RuntimeException, authenticate() aborts instead of sending the still-valid token; move the lookup inside the guarded block so the refresh fallback also covers registration-read failures.

2 resolved since last review

Review effort: Balanced

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.

🔵 Needs a closer look

Dynamically registered clients still share a token key, allowing one registration’s token to be reused or refreshed with another’s credentials.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Key tokens by the actual dynamically registered client ID

src/​Client/​Auth/​OAuthAuthenticator.php:826

Dynamically registered clients still share a token key because neither configuration field contains their assigned client ID. When a deferred flow for registration A completes after registration B replaces it, a later authenticator can reuse A's token and attempt to refresh it with B's credentials. Include the actual registration's client ID in the key and use that identity consistently for token lookup, refresh and deferred completion.

Review effort: Balanced

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.

🟢 Approval recommended

No active review issues are reported for the OAuth and HTTP transport hardening changes.

No open issues 🎉

Review effort: Balanced

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.

🔵 Needs a closer look

Human assessment is needed for a short-lived-token edge case that can trigger unnecessary refreshes before every request, adding overhead and risking token-endpoint rate limits.

No open issues 🎉

Review effort: Balanced

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.

🟢 Approval recommended

No active review issues were identified for the OAuth client and HTTP transport hardening changes.

No open issues 🎉

Review effort: Balanced

@chr-hertel
chr-hertel merged commit f236a9f into modelcontextprotocol:main Nov 10, 2026
30 checks passed
@chr-hertel
chr-hertel deleted the client-auth-before-tag branch November 10, 2026 12:10
@chr-hertel chr-hertel added Client Issues & PRs related to the Client component auth Issues and PRs related to Authentication / OAuth breaking change Breaking the Backwards Compatibility Promise labels Nov 10, 2026
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 breaking change Breaking the Backwards Compatibility Promise Client Issues & PRs related to the Client component

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Client] Key stored OAuth tokens by client, not only by issuer and resource

2 participants