passbolt/passbolt_api · error · InternalErrorException

Invalid JWKS endpoint response. Keys missing.

Error message

Invalid JWKS endpoint response. Keys missing.

What it means

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.

Solutions

  1. 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).
  2. Check the server's outbound network path: disable SSL inspection / whitelist the Azure domain so proxies do not substitute their own response.
  3. Re-check the Azure tenant/endpoint configuration so getOpenIdConfiguration() resolves the correct jwks_uri.
  4. Retry later if Azure is having an outage; the error is on the provider side, not in local code.

Example fix

null
Defensive patterns

Strategy: try-catch

Validate before calling

$config = json_decode(file_get_contents('https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration'), true);
$jwks = json_decode(file_get_contents($config['jwks_uri']), true);
if (!is_array($jwks) || !isset($jwks['keys'])) {
    throw new RuntimeException('JWKS endpoint did not return a keys array');
}

Type guard

function isValidJwksResponse(mixed $response): bool {
    return is_array($response) && isset($response['keys']) && is_array($response['keys']);
}

Try / catch

try {
    $keys = $provider->getJwtVerificationKeys();
} catch (InternalErrorException $e) {
    if (str_contains($e->getMessage(), 'Keys missing')) {
        Log::error('JWKS endpoint returned unexpected payload: ' . $e->getMessage());
    }
    throw $e;
}

Prevention

When it happens

Trigger: 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.'

Common situations: 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.

Related errors


AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/71477f826a2a0456. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltEe/Sso/src/Utility/Azure/Provider/AzureProvider.php:173

     *
     * @return array
     */
    public function getJwtVerificationKeys(): array
    {
        $openIdConfiguration = $this->getOpenIdConfiguration();
        $keysUri = $openIdConfiguration['jwks_uri'];

        $factory = $this->getRequestFactory();
        $request = $factory->getRequestWithOptions('get', $keysUri, []);

        try {
            $response = $this->getParsedResponse($request);
        } catch (Throwable $exception) {
            throw new InternalErrorException(__('Cannot parse JWKS endpoint response.'), 500, $exception);
        }

        if (!is_array($response) || !isset($response['keys'])) {
            throw new InternalErrorException(__('Invalid JWKS endpoint response. Keys missing.'));
        }

        /**
         * Here we are using custom method to check JWK key signature as we can't use `JWK::parseKeySet` method directly
         * because Azure don't provide "kty" parameter in the keys.
         *
         * @see \Firebase\JWT\JWK::parseKeySet()
         */
        return $this->parseJwksKeys($response['keys']);
    }

    /**
     * Parse & check JWT keys signature from Azure.
     *
     * @param array $responseKeys keys from Jwks endpoint
     * @return array of openssl compatible keys
     */
    protected function parseJwksKeys(array $responseKeys): array

View on GitHub (pinned to 31c1bbc10f)