phacility/phabricator · error · Exception
Failed to decode OAuth access token response: %s
Error message
Failed to decode OAuth access token response: %s
What it means
readAccessTokenResponse() tries json_decode() first and falls back to parse_str(); if neither yields an access_token or error key, the whole body is embedded in this exception. It means the token endpoint returned something that is neither a JSON object nor an HTTP query string containing a usable field - most commonly HTML.
Source
Thrown at src/applications/auth/adapter/PhutilOAuthAuthAdapter.php:213
return $data;
}
protected function readAccessTokenResponse($body) {
// NOTE: Most providers either return JSON or HTTP query strings, so try
// both mechanisms. If your provider does something else, override this
// method.
$data = json_decode($body, true);
if (!is_array($data)) {
$data = array();
parse_str($body, $data);
}
if (empty($data['access_token']) &&
empty($data['error'])) {
throw new Exception(
pht('Failed to decode OAuth access token response: %s', $body));
}
return $data;
}
protected function getOAuthAccountData($key, $default = null) {
if ($this->oauthAccountData === null) {
$this->oauthAccountData = $this->loadOAuthAccountData();
}
return idx($this->oauthAccountData, $key, $default);
}
}
View on GitHub (pinned to 5720a38cfe)
Solutions
- Read the body embedded in the exception message - it tells you exactly what the endpoint returned.
- If the body is HTML, the token endpoint URL is wrong or something is intercepting the request; verify the URL with curl.
- If the provider uses a nonstandard format (e.g., XML or a differently-named field), override readAccessTokenResponse() in your adapter subclass to parse it.
- Confirm the request reaches the real provider (check for proxies, SSL interception, DNS hijacking).
Example fix
// before: provider returns 'token=abc&expires=3600' instead of access_token=...
// after: override the parser in your PhutilOAuthAuthAdapter subclass
protected function readAccessTokenResponse($body) {
$data = array();
parse_str($body, $data);
if (!isset($data['access_token']) && isset($data['token'])) {
$data['access_token'] = $data['token'];
}
return $data;
} Defensive patterns
Strategy: fallback
Try / catch
try {
$token_data = $adapter->getAccessTokenData();
} catch (Exception $ex) {
$body = ''; if (preg_match('/response: (.*)$/s', $ex->getMessage(), $m)) { $body = $m[1]; }
if (strpos($body, '<') === 0) {
// HTML body: wrong endpoint or an intercepting proxy; alert the admin.
phlog('OAuth token endpoint returned HTML: '.substr($body, 0, 200));
}
throw $ex;
} Prevention
- curl the token endpoint once during setup to confirm it returns query-string or JSON bodies.
- For nonstandard providers, override readAccessTokenResponse() in the adapter subclass instead of patching core.
- Watch for proxies, WAFs, and SSO walls on the token endpoint path.
When it happens
Trigger: Token endpoint URL wrong and returning an HTML 404/500 page with status 200; a reverse proxy, CDN, or WAF intercepting the POST and serving an HTML challenge/block page; provider returning XML or a JSONP wrapper; empty response body; response double-encoded (JSON inside a query-string parameter).
Common situations: Misconfigured getTokenBaseURI() in a custom adapter; provider returning XML (older LinkedIn/Twitter style APIs); corporate proxy injecting an HTML login page; rate-limit pages served as HTML; a provider that requires a different POST content-type and echoes an HTML error.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Access token error: %s
- Expected '%s' to be '%s'!
- Expected '%s' in response!
- Expected valid JSON response from "user.whoami" request.
- Expected token to finish OAuth handshake!
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/27a035bdddfabbf0.
Report an issue: GitHub.