fix(auth): bind cached OAuth credentials to issuer and protected resource - #1110
Draft
tsarlandie-oai wants to merge 1 commit into
Draft
Conversation
tsarlandie-oai
force-pushed
the
codex/oauth-credential-resource-binding
branch
from
July 31, 2026 23:44
db186e1 to
475e45c
Compare
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
OAuth access and refresh tokens belong to both the authorization server that
issued them and the protected resource for which they were requested.
rmcpalready sends OAuth resource indicators, but cached credentials do not retain
that resource and issuer checks are not applied consistently across every
restore, access, and refresh path.
Consequently, changing authorization servers, sharing a credential store, or
using the same authorization server with different protected resources can
reuse credentials outside their original boundary. Downstream clients should
not need to reimplement this invariant around the SDK.
What changed
authorization-code, refresh-token, and client-credentials grants.
restoring, returning, or refreshing cached tokens.
OAuthState::{get_stored_credentials,set_stored_credentials}helpers forapplications that import or export cached credentials.
endpoints change, and reject stale token endpoints or mismatched client
identities before transmitting refresh credentials.
discarding tokens belonging to another issuer or resource.
credential-store writes.
Backward compatibility
Existing serialized credential records remain deserializable through
#[serde(default)]. Legacy records without a resource binding are migratedwithout another sign-in when both conditions hold:
The SDK immediately refreshes the legacy credentials for the current protected
resource and exposes only the newly resource-bound access token. It never
treats a missing issuer or resource as a wildcard.
Reauthorization is required only when the issuer cannot be verified, no refresh
token exists, or the authorization server permanently rejects migration with
invalid_grant,invalid_target, orinvalid_scope. Temporary failurespreserve the original credentials for retry. Migration never expands the
originally granted scopes, and failed imports preserve existing credentials and
OAuth client state.
StoredCredentialsis already#[non_exhaustive], so adding its new optionalresourcefield does not break external struct construction;StoredCredentials::newremains unchanged.Regression coverage
Tests exercise issuer and resource isolation across initialization, credential
access, direct refresh, imported credentials, authorization-code exchange,
client-credentials grants, and JWT client authentication. They also cover
transparent legacy migration, absent or mismatched issuers, refresh-token
rotation, permanent versus transient refresh failures, issuer changes sharing a
token endpoint, stale OAuth clients, scope preservation, and resource changes
during both HTTP requests and asynchronous credential storage.
Validation
cargo test --offline -p rmcp --features auth --lib transport::auth::tests—224 OAuth tests passed.
cargo test --offline -p rmcp --features auth-client-credentials-jwt --lib transport::auth::tests—231 OAuth/JWT tests passed.
cargo test --offline -p rmcp --features auth,client,transport-io— package,integration, and documentation tests passed.
cargo test --offline -p rmcp --all-features --lib --quiet— 549 testspassed.
cargo clippy --offline -p rmcp --all-targets --all-features -- -D warnings.rustfmt --check --edition 2024 crates/rmcp/src/transport/auth.rs.git diff --check.Breaking changes
None. Existing serialized credentials, public constructors, issuerless
authorization servers with already resource-bound credentials, and previously
supported client-registration flows remain compatible.