Skip to content

fix: DPoP nonce retry race issue#2580

Merged
nandan-bhat merged 6 commits intomainfrom
fix/dpop-nonce-retry-race
Apr 17, 2026
Merged

fix: DPoP nonce retry race issue#2580
nandan-bhat merged 6 commits intomainfrom
fix/dpop-nonce-retry-race

Conversation

@nandan-bhat
Copy link
Copy Markdown
Contributor

Background:

The proxy code reused shared fetcher state for requests to the same audience. As part of that reuse, it replaced the fetcher’s token lookup function on every request. When DPoP nonce retry happened, request A could pause, request B could come in and replace that function, and then request A’s retry could continue using request B’s token.

Fix:

This PR stops sharing request-specific token lookup state across requests. It now only reuses the DPoP handle per audience, and creates a fresh fetcher for each request with that request’s own token lookup function. That keeps DPoP nonce state reusable, while making sure retries stay bound to the correct session.

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Mar 31, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.18%. Comparing base (b06d30f) to head (372d576).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2580      +/-   ##
==========================================
+ Coverage   90.09%   90.18%   +0.08%     
==========================================
  Files          63       63              
  Lines        7614     7590      -24     
  Branches     1615     1615              
==========================================
- Hits         6860     6845      -15     
+ Misses        742      733       -9     
  Partials       12       12              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@nandan-bhat nandan-bhat marked this pull request as ready for review April 3, 2026 18:07
@nandan-bhat nandan-bhat requested a review from a team as a code owner April 3, 2026 18:07
# Conflicts:
#	src/server/auth-client.ts
@nandan-bhat nandan-bhat force-pushed the fix/dpop-nonce-retry-race branch from 3b36da5 to bd1c457 Compare April 17, 2026 08:49
Comment thread src/server/auth-client.ts

let fetcher: Fetcher<Response>;
if (this.provider) {
fetcher = await this.provider.getProxyFetcher(cacheKey, async () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing getProxyFetcher from this file makes getProxyFetcher a dead code, that can be removed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auth-client-provider.ts:42: const MAX_PROXY_FETCHERS = 100
auth-client-provider.ts:64: private proxyFetchers: LruMap<string, any>
auth-client-provider.ts:79: this.proxyFetchers = new LruMap(MAX_PROXY_FETCHERS
auth-client-provider.ts:207-220: getProxyFetcher() method
auth-client-provider.test.ts:519-600: Tests for getProxyFetcher  
mcd.integration.test.ts:750-780: MCD integration tests for proxy fetcher caching  

These are dead code after this change

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 372d576

Comment thread src/server/auth-client.ts Outdated
// Fetcher-scoped DPoP handle and nonce management
dpopHandle:
this.useDPoP && (options.useDPoP ?? true)
options.dpopHandle ??
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this implementation even if useDPoP is false and user passes some dpopHandle this will pass.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 372d576

@tusharpandey13
Copy link
Copy Markdown
Contributor

Was dropping support for MCD cross-instance DPoP handle sharing intentional?
This change moves the cache from provider to internal client.
Before: AuthClientProvider (this.provider.getProxyFetcher(cacheKey))
New: AuthClient (this.proxyDpopHandles[audience]).

@nandan-bhat
Copy link
Copy Markdown
Contributor Author

Was dropping support for MCD cross-instance DPoP handle sharing intentional? This change moves the cache from provider to internal client. Before: AuthClientProvider (this.provider.getProxyFetcher(cacheKey)) New: AuthClient (this.proxyDpopHandles[audience]).

This is intentional. In practice we still share the DPoP handle for the same domain, because the provider already reuses a single AuthClient per domain. So moving the cache from the provider to AuthClient does not lose the useful MCD sharing, but it does avoid sharing request-specific fetcher state which was the root cause of the issue.

@nandan-bhat nandan-bhat merged commit 98c36dc into main Apr 17, 2026
9 checks passed
@nandan-bhat nandan-bhat deleted the fix/dpop-nonce-retry-race branch April 17, 2026 12:38
@nandan-bhat nandan-bhat mentioned this pull request Apr 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants