Drop expired pending authorizations from the in-memory storage - #580
Merged
koic merged 1 commit intoSep 28, 2026
Conversation
koic
force-pushed
the
drop_expired_pending_authorizations_from_the_in_memory_storage
branch
from
September 28, 2026 02:45
28a7115 to
edcd38b
Compare
## Motivation and Context A provider without a `callback_handler` saves a pending authorization, holding the PKCE verifier and the authorization server metadata, every time `Flow#run!` sends the user to the authorization server, and `Flow#finish!` removes only the entry whose `state` its callback carries. An authorization the user abandoned gets no callback, so its entry stayed in `InMemoryStorage` for the life of the process, and since the transport starts a new one for every `401` it meets, a server that keeps answering `401` grew the storage by one entry per request. `pending_authorization_max_age` limited how long an entry could be redeemed, not how long it was kept, while the provider's comment said it kept verifiers from staying in storage indefinitely. `InMemoryStorage` now takes a `pending_authorization_max_age:` of its own, which `Provider.new` sets to its own value when it builds the default storage, and drops every entry older than it whenever a pending authorization is saved, so what the storage keeps is bounded by the authorizations started within that window. The documentation asks custom storages to expire entries at the same age. The three pending-authorization methods now run under a mutex, so `delete_pending_authorization` removes and returns the entry in one step on every Ruby implementation, not only where a global interpreter lock makes `Hash#delete` atomic, which is what `Flow#finish!` relies on to let exactly one of two callbacks racing on the same `state` redeem the code. And since every hash the storage holds carries a secret, `inspect` now reports only whether each is present. ## How Has This Been Tested? New tests in `test/mcp/client/oauth/provider_test.rb` save entries older and younger than the age and check which ones a later save drops, both on a storage built by hand and on a provider's default storage, check the keyword's validation, and check that `inspect` shows no token, client secret, or verifier. Against the previous library an entry older than the age is still there after later saves. ## Breaking Changes None. `InMemoryStorage#inspect` no longer prints the stored hashes.
koic
force-pushed
the
drop_expired_pending_authorizations_from_the_in_memory_storage
branch
from
September 28, 2026 02:45
edcd38b to
c78d325
Compare
atesgoral
approved these changes
Sep 28, 2026
koic
deleted the
drop_expired_pending_authorizations_from_the_in_memory_storage
branch
September 28, 2026 16:08
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 and Context
A provider without a
callback_handlersaves a pending authorization, holding the PKCE verifier and the authorization server metadata, every timeFlow#run!sends the user to the authorization server, andFlow#finish!removes only the entry whosestateits callback carries. An authorization the user abandoned gets no callback, so its entry stayed inInMemoryStoragefor the life of the process, and since the transport starts a new one for every401it meets, a server that keeps answering401grew the storage by one entry per request.pending_authorization_max_agelimited how long an entry could be redeemed, not how long it was kept, while the provider's comment said it kept verifiers from staying in storage indefinitely.InMemoryStoragenow takes apending_authorization_max_age:of its own, whichProvider.newsets to its own value when it builds the default storage, and drops every entry older than it whenever a pending authorization is saved, so what the storage keeps is bounded by the authorizations started within that window. The documentation asks custom storages to expire entries at the same age. The three pending-authorization methods now run under a mutex, sodelete_pending_authorizationremoves and returns the entry in one step on every Ruby implementation, not only where a global interpreter lock makesHash#deleteatomic, which is whatFlow#finish!relies on to let exactly one of two callbacks racing on the samestateredeem the code. And since every hash the storage holds carries a secret,inspectnow reports only whether each is present.How Has This Been Tested?
New tests in
test/mcp/client/oauth/provider_test.rbsave entries older and younger than the age and check which ones a later save drops, both on a storage built by hand and on a provider's default storage, check the keyword's validation, and check thatinspectshows no token, client secret, or verifier. Against the previous library an entry older than the age is still there after later saves.Breaking Changes
None.
InMemoryStorage#inspectno longer prints the stored hashes.Types of changes
Checklist