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

  1. Read the body embedded in the exception message - it tells you exactly what the endpoint returned.
  2. If the body is HTML, the token endpoint URL is wrong or something is intercepting the request; verify the URL with curl.
  3. If the provider uses a nonstandard format (e.g., XML or a differently-named field), override readAccessTokenResponse() in your adapter subclass to parse it.
  4. 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

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

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/27a035bdddfabbf0. Report an issue: GitHub.