auth: refresh_token() requests offline_access that was never granted, so refreshes fail with invalid_scope
Les mainteneurs répondent en général sous 3 jours
Évaluation
- Difficulté
- 2/5
- Temps estimé
- 1-3 heures
- Accessibilité débutants
- 65/100
- Type d'issue
- Bug
- Clarté
- Clairement spécifiée
- Activité
- Active
- Stack technique
- rust
- Domaine
- authentication
Piste de recherche
Commence dans crates/rmcp/src/transport/auth.rs à refresh_token(), là où add_offline_access_if_supported est appelée sur les granted_scopes stockés avant la construction de la requête de rafraîchissement, puis examine la fonction auxiliaire elle-même. Lis ensuite le test refresh_token_adds_offline_access_when_as_supports_it, qui attend actuellement offline_access dans le rafraîchissement. C'est terminé lorsque les requêtes de rafraîchissement n'envoient que les granted_scopes (ou omettent scope), que ce test est réécrit en conséquence et que le comportement du chemin d'autorisation de #897 reste inchangé.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
Summary
Since #897, AuthorizationManager::refresh_token() adds offline_access to the refresh request whenever the authorization server lists it in scopes_supported, even if the stored grant doesn't include it. An authorization server that didn't grant offline_access refuses that refresh with invalid_scope. The client then has to re-authorize every time the access token expires.
Where
crates/rmcp/src/transport/auth.rs L2309-L2313:
let mut refresh_scopes = stored_credentials.granted_scopes.clone();
self.add_offline_access_if_supported(&mut refresh_scopes);
let requested_scopes = refresh_scopes.clone();
for scope in refresh_scopes {
refresh_request = refresh_request.add_scope(Scope::new(scope));
The test added in #897, refresh_token_adds_offline_access_when_as_supports_it, expects a refresh of a grant holding only read to send offline_access read.
Why this breaks
RFC 6749 §6: the refresh request's scope "MUST NOT include any scope not originally granted by the resource owner". The authorization server is right to answer invalid_scope.
Requesting offline_access at authorization time doesn't guarantee it's granted. OIDC Core §11 requires prompt=consent alongside offline_access "unless other conditions for processing the request permitting offline access … are in place", and rmcp adds the scope without the prompt. node-oidc-provider, for one, drops offline_access from the grant by default. An OP can still issue a refresh token without the scope (node-oidc-provider allows this through its issueRefreshToken hook), and SEP-2207's own "No guarantee" item allows for that. Every refresh rmcp then sends is rejected:
POST /token
grant_type=refresh_token&refresh_token=…&scope=openid+profile+email+offline_access
HTTP/1.1 400 Bad Request
{"error":"invalid_scope","error_description":"refresh token missing requested scope","scope":"offline_access"}
SEP-2207, which #676/#897 implement, only covers authorization requests. Client guidance item 2: the client "MAY add the offline_access scope to the list of scopes from the resource server before making authorization requests to the Authorization Server". It doesn't extend that to refresh requests.
Reproduction
- Run an authorization server that strips
offline_accesswithoutprompt=consent(node-oidc-provider 9.x does by default), still issues refresh tokens to the client (for node-oidc-provider, anissueRefreshTokenthat returnstruefor clients allowed therefresh_tokengrant), and listsoffline_accessinscopes_supported. - Authorize with rmcp. The token response's
scopedoesn't includeoffline_access, but a refresh token is issued. - Let the access token expire, or call
refresh_token(). The token endpoint returnsinvalid_scopeforoffline_access.
Seen in the wild with Codex CLI 0.155.0 and 0.161.0 (codex-mcp-client, rmcp 3.3.0). Restricting Codex's configured scopes doesn't help: the login requests only the configured scopes, but every refresh still gets offline_access added. Still present on main (08e021153ef0).
Suggested fix
Don't add offline_access on the refresh path. Send the stored granted_scopes, or omit scope entirely, which RFC 6749 §6 defines as "equal to the scope originally granted". If the grant does include offline_access, it's already in granted_scopes, so nothing is lost. #897's scope-upgrade change is a re-authorization, so SEP-2207 does cover it and it can stay.
- Langage dominant
- Rust
- Étoiles
- 4.1k
- Forks
- 680
- Merge moyen
- 4 j 13 h
- PR mergées (30 j)
- 36
Préparer son environnement
Lance le conteneur de développement du projet dans votre navigateur, avec votre propre compte GitHub.
- Aucun Dockerfile ni fichier Docker Compose
- Aucun modèle de pull request
- Lire le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de modelcontextprotocol/rust-sdk
-
docs: ClientHandler list_changed callbacks don't mention Peer::listen (2026-07-28)Peut-être pris @DaleSeo l’a pris il y a 2 jours. Ouverteenhancement P2 ready for work T-documentation
modelcontextprotocol/rust-sdk#1394 · 1 commentaire · 1 personne assignée ·
Les mainteneurs répondent en général sous 3 jours
-
Support the Skills extension (SEP-2640)Peut-être pris @DaleSeo l’a pris il y a 3 jours. Ouverteenhancement P2 ready for work T-enhancement T-handler T-model T-service
modelcontextprotocol/rust-sdk#1391 · 1 commentaire · 1 personne assignée ·
Les mainteneurs répondent en général sous 3 jours
-
reqwest Streamable HTTP client matches response Content-Type by case-sensitive prefixPeut-être pris @DaleSeo l’a pris il y a 3 jours. Ouvertebug P1 ready for work T-transport
modelcontextprotocol/rust-sdk#1389 · 1 commentaire · 1 personne assignée ·
Les mainteneurs répondent en général sous 3 jours
-
Streamable HTTP server: Accept and Content-Type are matched case-sensitively and by substring/prefixPeut-être pris @DaleSeo l’a pris il y a 6 jours. Ouvertebug P1 ready for work T-transport
modelcontextprotocol/rust-sdk#1381 · 1 commentaire · 1 personne assignée ·
Les mainteneurs répondent en général sous 3 jours
-
outputSchema is generated for the deserialize contract; tool results are serialisedPeut-être pris @connor4312 l’a pris il y a 9 jours. Ouvertebug P2 ready for work T-handler
modelcontextprotocol/rust-sdk#1374 · 2 commentaires · 1 personne assignée ·
Les mainteneurs répondent en général sous 3 jours
Toutes les issues de modelcontextprotocol/rust-sdk
Issues similaires
-
accounts bug decoding
Difficulté 2/5 1-3 heures Accessibilité débutants 84/100
wboayue/rust-ibapi#1088 ·
Les mainteneurs répondent en général sous 1 jour
-
enhancement
Difficulté 2/5 1-3 heures Accessibilité débutants 68/100
Les mainteneurs répondent en général sous 1 jour
-
`mise doctor` Shows Not Installed Lazy Tools as ProblemsPeut-être pris @jdx l’a pris aujourd’hui. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 67/100
Les mainteneurs répondent en général sous 1 jour
-
area:kernel needs-triage
Difficulté 2/5 1-3 heures Accessibilité débutants 65/100
bherbruck/solvecraft#7179 · 1 commentaire ·
-
priority: medium
Difficulté 2/5 1-3 heures Accessibilité débutants 61/100
storytold/photocraft#9704 · 3 commentaires ·
Les mainteneurs répondent en général sous 1 jour