{"record":{"id":"4b80438334ffe624","repo":"passbolt/passbolt_api","slug":"invalid-response-expected-array-got-s-response-text","errorCode":null,"errorMessage":"Invalid response. Expected array, got \"%s\". Response text (truncated): %s","messagePattern":"Invalid response\\. Expected array, got \"(.+?)\"\\. Response text \\(truncated\\): (.+?)","errorType":"exception","errorClass":"InternalErrorException","httpStatus":500,"severity":"error","filePath":"plugins/PassboltEe/Sso/src/Utility/Provider/AbstractOauth2Provider.php","lineNumber":172,"sourceCode":"    }\n\n    /**\n     * Check the endpoints info we expect to use later are present\n     *\n     * @param mixed $response from .well-known\n     * @return void\n     */\n    public function validateOpenIdConfiguration(mixed $response): void\n    {\n        if (!is_array($response)) {\n            $msg = sprintf('Invalid response. Expected array, got \"%s\".', gettype($response));\n            if (is_string($response)) {\n                // Cap excerpt to limit log volume on large/HTML responses; mb_strcut is UTF-8-safe.\n                $excerpt = mb_strcut($response, 0, 200, 'UTF-8');\n                // Escape newlines and control characters via JSON encoding so they don't corrupt log output.\n                $msg .= ' ' . sprintf('Response text (truncated): %s', json_encode($excerpt));\n            }\n            throw new InternalErrorException($msg);\n        }\n        if (!isset($response['jwks_uri'])) {\n            throw new InternalErrorException('Invalid response. Missing JWKS URI');\n        }\n        if (!isset($response['authorization_endpoint'])) {\n            throw new InternalErrorException('Invalid response. Missing authorization endpoint.');\n        }\n        if (!isset($response['token_endpoint'])) {\n            throw new InternalErrorException('Invalid response. Missing token endpoint.');\n        }\n        if (!Validation::url($response['jwks_uri'])) {\n            throw new InternalErrorException('Invalid response. Invalid JWKS URI');\n        }\n        if (!Validation::url($response['authorization_endpoint'])) {\n            throw new InternalErrorException('Invalid response. Invalid authorization endpoint.');\n        }\n        if (!Validation::url($response['token_endpoint'])) {\n            throw new InternalErrorException('Invalid response. Invalid token endpoint.');","sourceCodeStart":154,"sourceCodeEnd":190,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/Sso/src/Utility/Provider/AbstractOauth2Provider.php#L154-L190","documentation":"validateOpenIdConfiguration() requires the discovery document to be an array (decoded JSON object). When the parsed response is not an array (e.g. a raw string of HTML or plain text), it throws InternalErrorException with the expected type, a 200-char truncated excerpt of the response text (UTF-8-safe via mb_strcut, JSON-encoded to protect logs). This guards against IdPs returning error pages instead of the OIDC discovery JSON.","triggerScenarios":"The GET to the .well-known/openid-configuration endpoint returns a non-JSON body (string): HTML login/proxy page, plain-text error, or a body that failed JSON decoding earlier.","commonSituations":"Reverse proxy or captive portal intercepting the request; wrong issuer URL pointing at an HTML page; IdP outage returning an HTML error page; misconfigured base URL behind authentication.","solutions":["Inspect the truncated response excerpt in the message to see what the endpoint actually returned.","Curl the .well-known/openid-configuration URL from the passbolt server and confirm it returns JSON.","Fix the issuer/base URL in passbolt SSO settings to the real OIDC discovery endpoint.","Remove any proxy/rewrite rules intercepting outbound requests to the IdP."],"exampleFix":"// before (issuer points to UI page)\n'issuer' => 'https://auth.example.com/console',\n// after (points to OIDC discovery root)\n'issuer' => 'https://auth.example.com',","handlingStrategy":"validation","validationCode":"$body = file_get_contents($wellKnownUrl);\n$decoded = json_decode($body, true);\nif (!is_array($decoded)) { throw new UnexpectedValueException('Discovery endpoint did not return a JSON object: ' . mb_strcut((string)$body, 0, 200)); }","typeGuard":"function isDiscoveryDocument(mixed $response): bool { return is_array($response) && isset($response['jwks_uri'], $response['authorization_endpoint'], $response['token_endpoint']); }","tryCatchPattern":"try { $config = $provider->getOpenIdConfiguration(); } catch (InternalErrorException $e) { if (str_contains($e->getMessage(), 'Expected array')) { /* IdP returned HTML/error page: check issuer URL and proxy */ } throw $e; }","preventionTips":["Verify the discovery URL returns application/json (curl -I)","Exclude the IdP domain from captive-portal/auth proxies","Point the issuer at the OIDC root, not an HTML console page","Add a health check that decodes the discovery document on deploy"],"tags":["openid-configuration","json","response-validation","oauth2"],"backgroundTag":"unexpected-response-shape","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}