{"record":{"id":"27a035bdddfabbf0","repo":"phacility/phabricator","slug":"failed-to-decode-oauth-access-token-response-s","errorCode":null,"errorMessage":"Failed to decode OAuth access token response: %s","messagePattern":"Failed to decode OAuth access token response: (.+?)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/auth/adapter/PhutilOAuthAuthAdapter.php","lineNumber":213,"sourceCode":"\n    return $data;\n  }\n\n  protected function readAccessTokenResponse($body) {\n    // NOTE: Most providers either return JSON or HTTP query strings, so try\n    // both mechanisms. If your provider does something else, override this\n    // method.\n\n    $data = json_decode($body, true);\n\n    if (!is_array($data)) {\n      $data = array();\n      parse_str($body, $data);\n    }\n\n    if (empty($data['access_token']) &&\n        empty($data['error'])) {\n      throw new Exception(\n        pht('Failed to decode OAuth access token response: %s', $body));\n    }\n\n    return $data;\n  }\n\n  protected function getOAuthAccountData($key, $default = null) {\n    if ($this->oauthAccountData === null) {\n      $this->oauthAccountData = $this->loadOAuthAccountData();\n    }\n\n    return idx($this->oauthAccountData, $key, $default);\n  }\n\n}\n","sourceCodeStart":195,"sourceCodeEnd":229,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/auth/adapter/PhutilOAuthAuthAdapter.php#L195-L229","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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)."],"exampleFix":"// before: provider returns 'token=abc&expires=3600' instead of access_token=...\n// after: override the parser in your PhutilOAuthAuthAdapter subclass\nprotected function readAccessTokenResponse($body) {\n  $data = array();\n  parse_str($body, $data);\n  if (!isset($data['access_token']) && isset($data['token'])) {\n    $data['access_token'] = $data['token'];\n  }\n  return $data;\n}","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  $token_data = $adapter->getAccessTokenData();\n} catch (Exception $ex) {\n  $body = ''; if (preg_match('/response: (.*)$/s', $ex->getMessage(), $m)) { $body = $m[1]; }\n  if (strpos($body, '<') === 0) {\n    // HTML body: wrong endpoint or an intercepting proxy; alert the admin.\n    phlog('OAuth token endpoint returned HTML: '.substr($body, 0, 200));\n  }\n  throw $ex;\n}","preventionTips":["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."],"tags":["oauth2","authentication","response-parsing","third-party-provider","php"],"backgroundTag":"oauth2-token-response-malformed","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}