fix(auth): replay duplicate refresh rotations - #4911
Draft
zzstoatzz wants to merge 2 commits into
Draft
Conversation
🤖 Generated with OpenAI Codex
🤖 Generated with OpenAI Codex
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.
A client-facing refresh token is currently deleted as soon as it rotates, so a lost-response retry or overlapping
/tokenrequest can turn a successful refresh intoinvalid_grantand force a new authorization flow. This adds an opt-in, bounded idempotency window that returns the exact response from the original rotation rather than refreshing upstream or minting another descendant.The secure default remains strict one-time use. Operators that observe duplicate refreshes can enable a window of at most 60 seconds on
OAuthProxy,OIDCProxy, or any built-in proxy provider:Security model
The window defaults to zero because accepting a consumed bearer token weakens replay detection. An eligible retry must use the same client and requested scopes, and its immediate successor must still have valid refresh-token metadata and a live JTI mapping. Consuming or revoking the successor invalidates the predecessor's cached response, preventing an older
A → Bresponse from being replayed afterB → C. Explicitly revoking the predecessor also removes its cached response, and the cache lifetime never exceeds the predecessor's own expiry.The exact response is retained in
client_storageonly for the configured window. FastMCP's default storage is encrypted; custom stores already hold upstream bearer credentials and must provide equivalent protection.Concurrency boundary
A per-token lock serializes overlapping refreshes within one process. The short-lived response record is stored before the predecessor is invalidated, so retries and other workers arriving after that commit observe one successor. The generic key-value interface has no compare-and-swap primitive, so this does not claim global serialization when two different processes both pass the record check before either commits.
Fixes #4901
🤖 Generated with OpenAI Codex