BookStackApp/BookStack · error · OidcIssuerDiscoveryException

Error reading keys from issuer jwks_uri

Error message

Error reading keys from issuer jwks_uri

What it means

loadKeysFromUri() fetches the JWKS from the IdP's jwks_uri and throws OidcIssuerDiscoveryException if the body is empty, invalid JSON, or lacks a 'keys' array. Without signing keys, incoming ID tokens cannot be verified, so the library refuses to continue.

Source

Thrown at app/Access/Oidc/OidcProviderSettings.php:181

            return $key['kty'] === 'RSA' && $use === 'sig' && $alg === 'RS256';
        });
    }

    /**
     * Return an array of jwks as PHP key=>value arrays.
     *
     * @throws ClientExceptionInterface
     * @throws OidcIssuerDiscoveryException
     */
    protected function loadKeysFromUri(string $uri, ClientInterface $httpClient): array
    {
        $request = new Request('GET', $uri);
        $response = $httpClient->sendRequest($request);
        $result = json_decode($response->getBody()->getContents(), true);

        if (empty($result) || !is_array($result) || !isset($result['keys'])) {
            throw new OidcIssuerDiscoveryException('Error reading keys from issuer jwks_uri');
        }

        return $result['keys'];
    }

    /**
     * Get the settings needed by an OAuth provider, as a key=>value array.
     */
    public function arrayForOAuthProvider(): array
    {
        $settingKeys = ['clientId', 'clientSecret', 'authorizationEndpoint', 'tokenEndpoint', 'userinfoEndpoint'];
        $settings = [];
        foreach ($settingKeys as $setting) {
            $settings[$setting] = $this->$setting;
        }

        return $settings;
    }

View on GitHub (pinned to 18f8469a1c)

Solutions

  1. curl the jwks_uri from the BookStack host and confirm it returns {"keys":[...]}.
  2. Fix DNS/firewall/proxy so the JWKS host is reachable from the BookStack container.
  3. Clear the discovery cache ('oidc-discovery::<issuer>') after restoring access, since a cached discovery doc holds the jwks_uri.
  4. Check IdP health/logs; rotate to a known-good realm if keys were removed from the JWKS.
Defensive patterns

Strategy: try-catch

Validate before calling

$jwksUri = json_decode(file_get_contents($discoveryUrl), true)['jwks_uri'] ?? null;
$jwks = json_decode(@file_get_contents($jwksUri) ?? '', true);
if (!is_array($jwks) || !isset($jwks['keys'])) {
    throw new RuntimeException('JWKS endpoint did not return a keys array');
}

Type guard

function isValidJwks(?array $decoded): bool {
    return is_array($decoded) && isset($decoded['keys']) && is_array($decoded['keys']) && $decoded['keys'] !== [];
}

Try / catch

try {
    $settings->discoverFromIssuer($client, $cache, 15);
} catch (OidcIssuerDiscoveryException $e) {
    if (str_contains($e->getMessage(), 'jwks_uri')) {
        Log::error('JWKS fetch failed: check jwks_uri reachability from app host');
    }
}

Prevention

When it happens

Trigger: GET jwks_uri returns non-JSON (HTML error page, empty 200), a proxy/WAF interstitial, or a document without the 'keys' member; also raised if the JWKS endpoint errors with a body that still parses as empty/non-array.

Common situations: JWKS URL unreachable through internal DNS while the main discovery doc is cached and reachable; IdP returning 5xx HTML pages; jwks_uri pointing to an internal hostname BookStack can't resolve; intermediate proxy stripping/altering the response.

Related errors


AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02). Data as JSON: /api/errors/b7c3b948e25c23dd. Report an issue: GitHub.