Skip to content

Commit 8ffed4a

Browse files
committed
fix(auth): restart full flow exactly once after failed refresh (no duplicate authorization_code)
1 parent db801ad commit 8ffed4a

1 file changed

Lines changed: 17 additions & 1 deletion

File tree

src/mcp/client/auth/oauth2.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ async def _handle_refresh_response(self, response: httpx.Response) -> bool:
478478
await self.context.storage.set_tokens(token_response)
479479

480480
return True
481-
except ValidationError: # pragma: no cover
481+
except ValidationError:
482482
logger.exception("Invalid refresh response")
483483
self.context.clear_tokens()
484484
return False
@@ -562,6 +562,11 @@ async def async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx.
562562
if self.context.is_token_valid():
563563
self._add_auth_header(request)
564564

565+
# Capture the access token actually used to send this request so the
566+
# 401 handler below can detect a token change made by a concurrent
567+
# request while this one was in flight.
568+
sent_access_token = self.context.current_tokens.access_token if self.context.current_tokens else None
569+
565570
response = yield request
566571

567572
# === Phase 4: 401 / 403 full OAuth flow ===
@@ -572,6 +577,17 @@ async def async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx.
572577
# here in the same pattern as Phase 1-2.
573578
if response.status_code == 401:
574579
async with self.context.lock:
580+
# Concurrency guard: while this request was in flight, another
581+
# request holding ``context.lock`` may have already completed a
582+
# token refresh or a full re-authorization. If the stored access
583+
# token changed since we sent this request, the 401 is stale -
584+
# retry once with the new token instead of running a second,
585+
# duplicate ``authorization_code`` exchange.
586+
current_access_token = self.context.current_tokens.access_token if self.context.current_tokens else None
587+
if current_access_token is not None and current_access_token != sent_access_token:
588+
self._add_auth_header(request)
589+
yield request
590+
return
575591
# Perform full OAuth flow
576592
try:
577593
# OAuth flow must be inline due to generator constraints

0 commit comments

Comments
 (0)