Repository navigation
Serve still-valid access tokens while a token refresh is in flight - #6
Open
Ignacio-Vidal wants to merge 429 commits into
Open
Ignacio-Vidal wants to merge 429 commits into
Ignacio-Vidal wants to merge 429 commits into
Conversation
Ignacio-Vidal
force-pushed
the
oidc-client-serve-valid-token-during-refresh
branch
from
September 1, 2026 20:58
890da22 to
df8aac8
Compare
Ignacio-Vidal
force-pushed
the
oidc-client-serve-valid-token-during-refresh
branch
from
September 1, 2026 22:07
21d6d31 to
39e4d26
Compare
Ignacio-Vidal
force-pushed
the
oidc-client-serve-valid-token-during-refresh
branch
from
September 3, 2026 20:42
ccd6b73 to
863023f
Compare
Ignacio-Vidal
force-pushed
the
oidc-client-serve-valid-token-during-refresh
branch
from
September 5, 2026 08:37
d45354b to
9f47497
Compare
Ignacio-Vidal
force-pushed
the
oidc-client-serve-valid-token-during-refresh
branch
from
September 21, 2026 18:57
9f47497 to
10ddaee
Compare
Override ConsoleHandler's stale System.out reference and remove foreign handlers from the JUL root logger during Quarkus logging initialization.
…date-mandrel Add section about native-image-configure tool
Co-authored-by: Yoann Rodière <yoann@hibernate.org>
…l.INSTANCE) Pool beans Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
…in/org.mvnpm-type-fest-5.10.0 Build(deps): Bump org.mvnpm:type-fest from 5.8.0 to 5.10.0
Services: add support for capturing date/time/tz and related types
Honor custom test port for @TestHTTPResource with Amazon Lambda HTTP
Update the guides for the Java 21 minimum
…avadoc Fix Javadoc of `LambdaTransliterator`
Fix AgroalDataSourceUtil.activeDataSourceNames() to include named datasources
…ct-panache1-53368 Fail the build when classic Panache is used with Quarkus Data
Deprecate quarkus.hibernate-orm.log-session-metrics property
…ging-48763 Fix Gradle `testLogging.showStandardStreams` being ignored by Quarkus tests
Fix capabilities section in writing-extensions guide
closes: quarkusio#57231 Signed-off-by: Michal Vavřík <dev@michalvavrik.net>
OIDC Client: cache token requests based on additional params
…in/org.htmlunit-htmlunit-5.6.0 Build(deps): Bump org.htmlunit:htmlunit from 5.5.0 to 5.6.0
…ility-grafana-export Export the Dev UI observability dashboard to Grafana
TokensHelper replaces the cached tokens with the in-flight Uni for the duration of a refresh, so every request arriving in that window subscribes to that Uni and waits for the token endpoint to respond. The tokens being replaced are normally still valid at that point, because refresh-token-time-skew starts the refresh before they expire, so that wait is avoidable. Retain those tokens in TokenRequestState and serve them to callers arriving while the refresh is in flight, as long as they have not expired -- the same rule already applied when no refresh is running. The only difference is that the caller waits on a refresh someone else started rather than one it starts itself. This is opt-in via the new quarkus.oidc-client.min-remaining-access-token-lifespan property, which sets how much of the access token's lifespan must be left for it to still be worth returning, so that it does not expire in transit or while the target service is processing the request. When not configured, callers wait for the refresh, as before. A refresh only starts once the remaining lifespan has dropped below refresh-token-time-skew, so a minimum which is not less than the skew could never be satisfied and would silently disable the optimisation. This combination, a minimum below one second (the access token expiry is tracked in whole seconds, so it would truncate to zero) and a missing skew are rejected with a ConfigurationException when the OIDC client is created.
Ignacio-Vidal
force-pushed
the
oidc-client-serve-valid-token-during-refresh
branch
from
October 18, 2026 11:51
470fb4d to
3543ec5
Compare
Tokens is public API, so replacing its 7-arg constructor broke existing callers. Restore it, delegating with no minimum remaining access token lifespan so tokens created with it keep the previous behaviour, and deprecate it for removal in favour of the 8-arg constructor.
A caller forces the acquisition of new tokens because the ones it was given were rejected, typically after a 401 when refresh on unauthorized is enabled. If a refresh is already in flight, the tokens being refreshed kept being served to the other callers, even though they are now known to have been rejected. When new tokens are forced during a refresh, drop the tokens being refreshed from the state so that every caller waits for the refresh in flight.
The access token expiry is tracked in whole seconds, and the test created tokens expiring in 2s with a 2s minimum remaining lifespan, so crossing a second boundary before they were requested was enough for them not to be served. Use a 6s skew, 5s remaining and a 1s minimum, which leaves 4s for the test to run.
… is set The debug message explaining that the tokens being refreshed would not be reused because too little of their lifespan was left was also logged when no minimum remaining access token lifespan was configured, where it does not apply. Log it only when a minimum is configured, and include it in the message.
This branch was successfully deployed
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.

While
TokensHelperhas a token refresh in flight, the cached tokens are replaced by the pendingUni, so any request arriving in that window subscribes to it and waits for the token endpoint to respond. The tokens being replaced are normally still valid at that moment —refresh-token-time-skewexists precisely to start the refresh early — so that wait buys nothing.This retains those tokens in
TokenRequestStateand keeps serving them for the duration of the refresh, falling back to the in-flightUnionly once they have genuinely expired.quarkus.oidc-client.refresh-intervalalready moves most refreshes off the request path, but it does not close this window: the window is one token round trip wide and recurs on every refresh, so concurrent traffic still lands in it.Measured on a service taking unauthenticated traffic and making authenticated downstream calls, with
refresh-intervalconfigured and deliberately short-lived (5s) tokens — 12 concurrent clients over 15s, ~13k requests: roughly 0.6% of requests blocked between 5ms and 32ms waiting on an in-flight refresh, the spread reflecting where in the refresh each request arrived.TokensHelperTestcovers the new behaviour: a caller arriving during a blocked refresh receives the previous, still-valid tokens, and the refresh is not duplicated. It fails without the change.