You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
OAuth token refresh hits the wrong endpoint when the auth server lives under a path #3240
Ran into this with a hosted MCP server whose authorization server isn't at the origin root — token endpoint is https://host/oauth2/api/v1/token, not https://host/token.
If a client starts up with a cached-but-expired access token (+ refresh token) and hasn't done discovery yet, async_auth_flow refreshes at the very top — before any 401/metadata discovery. So oauth_metadata is None and _refresh_token uses the fallback urljoin(get_authorization_base_url(server_url), "/token"), i.e. just {scheme}://{netloc}/token. That 404s, _handle_refresh_response clears the tokens, and the flow drops to full interactive auth — which a headless/gateway client can't do. So the server silently disconnects every time the access token expires (mine are 5 min, so… constantly).
Same path-stripping fallback is in _get_token_endpoint, _perform_authorization_code_grant (/authorize) and DCR (/register) — refresh is just the one that bites silently.
Repro (roughly):
MCP server whose AS metadata puts token_endpoint under a path, not {origin}/token
log in normally so tokens get cached
let the access token expire (or clear the expiry), reconnect with a fresh provider
watch the refresh POST go to https://host/token → 404 → "Token refresh failed" → tokens cleared → it tries to open a browser
Fix looks like: discover metadata before the eager refresh (or stop dropping the issuer path in the fallback). Happy to PR — have a branch that pulls the PRM/ASM discovery out of the 401 branch and runs it before the refresh.
The root cause is clear: async_auth_flow does the eager refresh at the top before any metadata discovery, so _refresh_token falls back to urljoin(get_authorization_base_url(server_url), "/token") — which strips the path when the AS isn't at the origin root.
Proposed fix: Pull PRM/ASM discovery (_get_oauth_metadata / _get_authorization_server_metadata) out of the 401-handling branch and run it eagerly before the refresh attempt. If discovery fails (no AS metadata endpoint), fall back to the current behavior. This way:
Refresh uses the real token_endpoint from metadata when available
Headless/gateway clients that can't do interactive auth don't silently disconnect on token expiry
The fallback path-stripping logic stays as a last resort for servers without metadata
Relevant experience: Contributed #3093 (non-2xx response tests for the Python SDK), familiar with the auth flow internals.
Withdrawing my claim on this from 2026-08-04 ("I'd like to take this on").
I never followed up, and there is already a working fix in flight: PR #3263 (fix(client/auth): discover AS metadata before cold-start token refresh, with issuer-binding check) has been open since 2026-08-07, has been through nine review rounds with all findings confirmed fixed, and is green on client-conformance, server-conformance and zizmor. Leaving a stale claim here makes the issue look taken when the only thing actually moving is that PR.
Two notes for whoever picks this up, neither of which is a request for assignment:
The diagnosis in my 2026-08-04 comment still stands — async_auth_flow does the eager refresh before any metadata discovery, so _refresh_token falls back to urljoin(get_authorization_base_url(server_url), "/token"), which strips the path when the authorization server is not at the origin root.
Activity
zsxh1990 commentedon Aug 4, 2026
I'd like to take this on.
The root cause is clear:
async_auth_flowdoes the eager refresh at the top before any metadata discovery, so_refresh_tokenfalls back tourljoin(get_authorization_base_url(server_url), "/token")— which strips the path when the AS isn't at the origin root.Proposed fix: Pull PRM/ASM discovery (
_get_oauth_metadata/_get_authorization_server_metadata) out of the 401-handling branch and run it eagerly before the refresh attempt. If discovery fails (no AS metadata endpoint), fall back to the current behavior. This way:token_endpointfrom metadata when availableRelevant experience: Contributed #3093 (non-2xx response tests for the Python SDK), familiar with the auth flow internals.
Happy to open a PR if this direction looks right.
fix: skip eager token refresh when OAuth metadata is unavailable
test: verify eager refresh is skipped when oauth_metadata is None
fix: skip eager token refresh when OAuth metadata is unavailable
Ecocitizenz commentedon Aug 4, 2026
OAuth client: refresh before re-authorizing, and discover before refr…
zsxh1990 commentedon Oct 14, 2026
Withdrawing my claim on this from 2026-08-04 ("I'd like to take this on").
I never followed up, and there is already a working fix in flight: PR #3263 (
fix(client/auth): discover AS metadata before cold-start token refresh, with issuer-binding check) has been open since 2026-08-07, has been through nine review rounds with all findings confirmed fixed, and is green onclient-conformance,server-conformanceandzizmor. Leaving a stale claim here makes the issue look taken when the only thing actually moving is that PR.Two notes for whoever picks this up, neither of which is a request for assignment:
The diagnosis in my 2026-08-04 comment still stands —
async_auth_flowdoes the eager refresh before any metadata discovery, so_refresh_tokenfalls back tourljoin(get_authorization_base_url(server_url), "/token"), which strips the path when the authorization server is not at the origin root.The open review note on fix(client/auth): discover AS metadata before cold-start token refresh, with issuer-binding check #3263 is worth weighing before merge: the fix adds roughly 120 lines, and the reviewer asked for a judgement call on whether that is proportionate for a fallback-path bug. That is a design question, not a correctness one.
Not asking to be assigned — just making the record honest. The work is not claimed by me any more.