{"record":{"id":"71477f826a2a0456","repo":"passbolt/passbolt_api","slug":"invalid-jwks-endpoint-response-keys-missing","errorCode":null,"errorMessage":"Invalid JWKS endpoint response. Keys missing.","messagePattern":"Invalid JWKS endpoint response\\. Keys missing\\.","errorType":"exception","errorClass":"InternalErrorException","httpStatus":500,"severity":"error","filePath":"plugins/PassboltEe/Sso/src/Utility/Azure/Provider/AzureProvider.php","lineNumber":173,"sourceCode":"     *\n     * @return array\n     */\n    public function getJwtVerificationKeys(): array\n    {\n        $openIdConfiguration = $this->getOpenIdConfiguration();\n        $keysUri = $openIdConfiguration['jwks_uri'];\n\n        $factory = $this->getRequestFactory();\n        $request = $factory->getRequestWithOptions('get', $keysUri, []);\n\n        try {\n            $response = $this->getParsedResponse($request);\n        } catch (Throwable $exception) {\n            throw new InternalErrorException(__('Cannot parse JWKS endpoint response.'), 500, $exception);\n        }\n\n        if (!is_array($response) || !isset($response['keys'])) {\n            throw new InternalErrorException(__('Invalid JWKS endpoint response. Keys missing.'));\n        }\n\n        /**\n         * Here we are using custom method to check JWK key signature as we can't use `JWK::parseKeySet` method directly\n         * because Azure don't provide \"kty\" parameter in the keys.\n         *\n         * @see \\Firebase\\JWT\\JWK::parseKeySet()\n         */\n        return $this->parseJwksKeys($response['keys']);\n    }\n\n    /**\n     * Parse & check JWT keys signature from Azure.\n     *\n     * @param array $responseKeys keys from Jwks endpoint\n     * @return array of openssl compatible keys\n     */\n    protected function parseJwksKeys(array $responseKeys): array","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/Sso/src/Utility/Azure/Provider/AzureProvider.php#L155-L191","documentation":"This InternalErrorException is thrown by AzureProvider::getJwtVerificationKeys when the response fetched from the Azure AD JWKS endpoint either is not a decodable array or lacks the required 'keys' member. The JWKS endpoint must return a JSON object containing a 'keys' array of signing keys; anything else (error page, HTML, empty body, proxy interception) is treated as an unusable key set and the SSO token verification cannot proceed.","triggerScenarios":"The HTTP GET to the jwks_uri (resolved from the OpenID configuration) returns a 200/JSON body that is not an array, or a JSON object without a 'keys' property — e.g. an authentication/error JSON payload from a proxy, an empty response, or Azure returning an error document instead of the key set. Note that transport-level failures are caught earlier and surface as 'Cannot parse JWKS endpoint response.'","commonSituations":"Corporate proxy or firewall intercepting the outbound request and returning an HTML/JSON error page; misconfigured or stale OpenID configuration pointing at a wrong jwks_uri; Azure AD outage or throttling returning an error body with 200; network appliance (SSL inspection) rewriting the response.","solutions":["Verify the jwks_uri URL returned by the OpenID configuration is reachable and returns a JSON object with a 'keys' array (curl the URL from the server).","Check the server's outbound network path: disable SSL inspection / whitelist the Azure domain so proxies do not substitute their own response.","Re-check the Azure tenant/endpoint configuration so getOpenIdConfiguration() resolves the correct jwks_uri.","Retry later if Azure is having an outage; the error is on the provider side, not in local code."],"exampleFix":"null","handlingStrategy":"try-catch","validationCode":"$config = json_decode(file_get_contents('https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration'), true);\n$jwks = json_decode(file_get_contents($config['jwks_uri']), true);\nif (!is_array($jwks) || !isset($jwks['keys'])) {\n    throw new RuntimeException('JWKS endpoint did not return a keys array');\n}","typeGuard":"function isValidJwksResponse(mixed $response): bool {\n    return is_array($response) && isset($response['keys']) && is_array($response['keys']);\n}","tryCatchPattern":"try {\n    $keys = $provider->getJwtVerificationKeys();\n} catch (InternalErrorException $e) {\n    if (str_contains($e->getMessage(), 'Keys missing')) {\n        Log::error('JWKS endpoint returned unexpected payload: ' . $e->getMessage());\n    }\n    throw $e;\n}","preventionTips":["Curl the jwks_uri from the production server during setup to confirm a valid keys array is returned","Whitelist Azure endpoints from SSL-inspection proxies","Monitor OpenID configuration changes for Azure endpoint updates","Cache JWKS with a TTL and refresh proactively to avoid outages becoming user-facing errors"],"tags":["sso","azure","jwks","http-response","jwt"],"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"}