Skip to content

Commit f7e365c

Browse files
committed
fix: deduplicate concurrent OAuth refresh token exchanges
When multiple parallel requests receive 401 responses, each independently calls onUnauthorized -> handleOAuthUnauthorized -> refreshAuthorization with the same refresh token. OAuth providers using rotating refresh tokens (Atlassian, Asana, per RFC 6819 5.2.2.3) detect the second use as a replay attack and revoke the entire token family, logging the user out. The fix adds promise coalescing in adaptOAuthProvider: the first 401 handler stores its refresh promise, and all concurrent 401s await the same promise instead of initiating separate refresh exchanges. The promise is cleared after completion (success or failure) so future token refreshes proceed normally. Closes #1760
1 parent cce3ac7 commit f7e365c

1 file changed

Lines changed: 14 additions & 1 deletion

File tree

packages/client/src/client/auth.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,25 @@ export async function handleOAuthUnauthorized(provider: OAuthClientProvider, ctx
120120
* original `OAuthClientProvider` for OAuth-specific paths (`finishAuth()`, 403 upscoping).
121121
*/
122122
export function adaptOAuthProvider(provider: OAuthClientProvider): AuthProvider {
123+
let inflightRefresh: Promise<void> | undefined;
123124
return {
124125
token: async () => {
125126
const tokens = await provider.tokens();
126127
return tokens?.access_token;
127128
},
128-
onUnauthorized: async ctx => handleOAuthUnauthorized(provider, ctx)
129+
onUnauthorized: async ctx => {
130+
// Deduplicate concurrent 401 handlers to prevent multiple
131+
// refresh token exchanges. OAuth providers with rotating
132+
// refresh tokens (RFC 6819 5.2.2.3) revoke the entire
133+
// token family when a refresh token is used more than once.
134+
if (inflightRefresh) {
135+
return inflightRefresh;
136+
}
137+
inflightRefresh = handleOAuthUnauthorized(provider, ctx).finally(() => {
138+
inflightRefresh = undefined;
139+
});
140+
return inflightRefresh;
141+
}
129142
};
130143
}
131144

0 commit comments

Comments
 (0)