Retry OAuth token refresh without resource when the authorization server rejects it (Entra AADSTS9010010) - #1883
Open
1aifanatic wants to merge 1 commit into
Conversation
…rejects it Microsoft Entra ID v2.0 rejects the RFC 8707 resource parameter on refresh_token grants (AADSTS9010010), so clients silently lost their session every time the access token expired (~1 hour) and were forced back through interactive authorization. The MCP authorization spec requires the resource parameter on token requests, so the refresh still sends it. When the authorization server rejects that refresh with HTTP 400 and an error other than invalid_grant, the client now retries the refresh once without the resource before falling back to re-authorization. invalid_grant (a dead refresh token) is not retried because dropping the resource cannot help. Conformant authorization servers see no change. Fixes modelcontextprotocol#1587 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011p29dMDsLnn6KDFmsz2PGr
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.
Fixes #1587
Problem
Microsoft Entra ID v2.0 rejects the RFC 8707
resourceparameter onrefresh_tokengrants withAADSTS9010010("The resource parameter provided in the request doesn't match with the requested scopes").ClientOAuthProvider.RefreshTokensAsyncalways sendsresourceand returnsnullon any failure. So for an Entra-protected MCP server, the silent refresh fails every time the access token expires (about every hour), and the client goes back through interactive authorization.Why not just drop
resourceon refreshThe MCP authorization spec ("Resource Parameter Implementation") requires clients to send
resourceon token requests, and RFC 8707 §2.2 defines it for refresh grants. The Python SDK hit the same bug (modelcontextprotocol/python-sdk#2578). Several PRs there that removedresourcefrom refresh were closed, and review pushed toward keeping the spec-required behavior and adding a targeted fallback (modelcontextprotocol/python-sdk#2853). This PR takes the same approach, so the SDKs behave the same way.Change
resource, exactly as before.invalid_grant, the refresh is retried once withoutresource. Only if that also fails does the client fall back to re-authorization, as it does today.invalid_grantmeans the refresh token itself is dead, so it's never retried. Droppingresourcecan't help there.resource.Tests
TestOAuthServergets aRejectRefreshWithResourceErrorswitch that simulates Entra (it rejectsrefresh_tokengrants that includeresourceand accepts ones that don't). It also records theresourcevalue of each refresh request.CanAuthenticate_WithTokenRefresh's middleware, which forces a 401, moved into a shared helper, and these tests use it:CanAuthenticate_WithTokenRefresh(existing, now stricter): a conformant server gets exactly one refresh request, it carriesresource, and there's no second authorization-code exchange.CanAuthenticate_WithTokenRefresh_WhenAuthServerRejectsResourceOnRefresh: the refresh requests are[resource, null], the refresh succeeds, and there's no interactive re-auth.TokenRefresh_RejectedWithInvalidGrant_IsNotRetriedWithoutResource: there's a single refresh request carryingresource, then the client falls back to a new authorization-code flow.To check the new test can fail, I reverted only the
ClientOAuthProviderchange and kept the test infrastructure. The Entra scenario test failed and the other two still passed.Local results on Windows (.NET SDK 10.0.302). The solution
dotnet buildis clean (warnings are errors).ModelContextProtocol.AspNetCore.Tests: 637 passed / 0 failed on each of net10.0, net9.0, and net8.0. In an earlier net10.0 run on a loaded machine,TokenCacheTestsandAuthEventTestseach had one failure. Both classes passed 18/18 on three isolated reruns, and a full rerun passed 637/637.ModelContextProtocol.Testsauthentication tests: 21/21 on net10.0, net9.0, net8.0, and net472.I couldn't test against a live Entra tenant. The simulated server reproduces the documented behavior (400 when
resourceis present on refresh, success without it).🤖 Generated with Claude Code
https://claude.ai/code/session_011p29dMDsLnn6KDFmsz2PGr