fix(auth): migrate OAuth token refresh to platform.claude.com with fallback (#3246)
Anthropic migrated their OAuth infrastructure from console.anthropic.com to platform.claude.com (Claude Code v2.1.81+). Update _refresh_oauth_token() to try the new endpoint first, falling back to the old one for tokens issued before the migration. Also switches Content-Type from application/x-www-form-urlencoded to application/json to match current Claude Code behavior. Salvaged from PR #2741 by kshitijk4poor.
This commit is contained in:
@@ -210,9 +210,12 @@ def _refresh_oauth_token(creds: Dict[str, Any]) -> Optional[str]:
|
|||||||
Only works for credentials that have a refresh token (from claude /login
|
Only works for credentials that have a refresh token (from claude /login
|
||||||
or claude setup-token with OAuth flow).
|
or claude setup-token with OAuth flow).
|
||||||
|
|
||||||
|
Tries the new platform.claude.com endpoint first (Claude Code >=2.1.81),
|
||||||
|
then falls back to console.anthropic.com for older tokens.
|
||||||
|
|
||||||
Returns the new access token, or None if refresh fails.
|
Returns the new access token, or None if refresh fails.
|
||||||
"""
|
"""
|
||||||
import urllib.parse
|
import time
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
refresh_token = creds.get("refreshToken", "")
|
refresh_token = creds.get("refreshToken", "")
|
||||||
@@ -223,38 +226,42 @@ def _refresh_oauth_token(creds: Dict[str, Any]) -> Optional[str]:
|
|||||||
# Client ID used by Claude Code's OAuth flow
|
# Client ID used by Claude Code's OAuth flow
|
||||||
CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"
|
CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"
|
||||||
|
|
||||||
data = urllib.parse.urlencode({
|
# Anthropic migrated OAuth from console.anthropic.com to platform.claude.com
|
||||||
|
# (Claude Code v2.1.81+). Try new endpoint first, fall back to old.
|
||||||
|
token_endpoints = [
|
||||||
|
"https://platform.claude.com/v1/oauth/token",
|
||||||
|
"https://console.anthropic.com/v1/oauth/token",
|
||||||
|
]
|
||||||
|
|
||||||
|
payload = json.dumps({
|
||||||
"grant_type": "refresh_token",
|
"grant_type": "refresh_token",
|
||||||
"refresh_token": refresh_token,
|
"refresh_token": refresh_token,
|
||||||
"client_id": CLIENT_ID,
|
"client_id": CLIENT_ID,
|
||||||
}).encode()
|
}).encode()
|
||||||
|
|
||||||
req = urllib.request.Request(
|
headers = {
|
||||||
"https://console.anthropic.com/v1/oauth/token",
|
"Content-Type": "application/json",
|
||||||
data=data,
|
"User-Agent": f"claude-cli/{_CLAUDE_CODE_VERSION} (external, cli)",
|
||||||
headers={
|
}
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
"User-Agent": f"claude-cli/{_CLAUDE_CODE_VERSION} (external, cli)",
|
|
||||||
},
|
|
||||||
method="POST",
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
for endpoint in token_endpoints:
|
||||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
req = urllib.request.Request(
|
||||||
result = json.loads(resp.read().decode())
|
endpoint, data=payload, headers=headers, method="POST",
|
||||||
new_access = result.get("access_token", "")
|
)
|
||||||
new_refresh = result.get("refresh_token", refresh_token)
|
try:
|
||||||
expires_in = result.get("expires_in", 3600) # seconds
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||||
|
result = json.loads(resp.read().decode())
|
||||||
|
new_access = result.get("access_token", "")
|
||||||
|
new_refresh = result.get("refresh_token", refresh_token)
|
||||||
|
expires_in = result.get("expires_in", 3600)
|
||||||
|
|
||||||
if new_access:
|
if new_access:
|
||||||
import time
|
new_expires_ms = int(time.time() * 1000) + (expires_in * 1000)
|
||||||
new_expires_ms = int(time.time() * 1000) + (expires_in * 1000)
|
_write_claude_code_credentials(new_access, new_refresh, new_expires_ms)
|
||||||
# Write refreshed credentials back to ~/.claude/.credentials.json
|
logger.debug("Refreshed Claude Code OAuth token via %s", endpoint)
|
||||||
_write_claude_code_credentials(new_access, new_refresh, new_expires_ms)
|
return new_access
|
||||||
logger.debug("Successfully refreshed Claude Code OAuth token")
|
except Exception as e:
|
||||||
return new_access
|
logger.debug("Token refresh failed at %s: %s", endpoint, e)
|
||||||
except Exception as e:
|
|
||||||
logger.debug("Failed to refresh Claude Code token: %s", e)
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user