{"record":{"id":"32cb51a8320d2b42","repo":"passbolt/passbolt_api","slug":"invalid-jwks-endpoint-response-keys-missing-32cb51","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/Provider/AbstractOauth2Provider.php","lineNumber":285,"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        $defaultAlg = $this->getJwksDefaultAlg();\n        $this->assertJwkDefaultAlg($defaultAlg);\n\n        return JWK::parseKeySet($response, $defaultAlg);\n    }\n\n    /**\n     * Returns the alg of the keys.\n     *\n     * @return mixed\n     */\n    protected function getJwksDefaultAlg(): mixed\n    {\n        return Configure::read('passbolt.plugins.sso.security.jwks.defaultAlg');\n    }\n","sourceCodeStart":267,"sourceCodeEnd":303,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/Sso/src/Utility/Provider/AbstractOauth2Provider.php#L267-L303","documentation":"After successfully fetching the JWKS endpoint, the provider expects an object/array containing a 'keys' member (per RFC 7517). If the response is not an array or lacks 'keys', the IdP returned something unexpected and the provider throws this InternalErrorException because JWT signature verification is impossible without the key set.","triggerScenarios":"getJwtVerificationKeys() receives a parsed response that is not an array, or an array without a 'keys' index — e.g. the JWKS URI returned JSON like {\"error\":\"...\"}, an empty object, or a plain list.","commonSituations":"A reverse proxy or captive portal returns an HTML/JSON error page with 200 status; misconfigured discovery metadata pointing at the wrong endpoint; an IdP API version change altering the response shape.","solutions":["Fetch the JWKS URI manually and confirm the response contains a top-level 'keys' array (RFC 7517 format)","Fix the SSO provider configuration/discovery URL if it points to a non-JWKS endpoint","Check for interceptors (proxy, WAF) returning 200 with an error body instead of the actual key set","Clear any caching layer serving a stale or corrupted JWKS response","If the IdP changed its JWKS format/version, upgrade the SSO plugin"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"$payload = json_decode(file_get_contents($provider->getJwksUri()), true);\nif (!is_array($payload) || !isset($payload['keys'])) {\n    throw new \\RuntimeException('IdP returned malformed JWKS; check discovery URL and proxies');\n}","typeGuard":null,"tryCatchPattern":"try {\n    $keys = $provider->getJwtVerificationKeys();\n} catch (\\Cake\\Http\\Exception\\InternalErrorException $e) {\n    if (str_contains($e->getMessage(), 'Keys missing')) {\n        // surface IdP misconfiguration guidance to the admin\n    }\n}","preventionTips":["Confirm the JWKS URL returns RFC 7517 format with a top-level 'keys' array","Check for proxies/WAFs that substitute 200-error pages for real responses","Pin and validate discovery metadata when configuring the provider","Re-verify after IdP API version upgrades"],"tags":["jwks","oauth2","sso","unexpected-response"],"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"}