BookStackApp/BookStack · error · OidcIssuerDiscoveryException

Unexpected issuer value found on discovery response

Error message

Unexpected issuer value found on discovery response

What it means

After decoding the discovery document, loadSettingsFromIssuerDiscovery() compares the document's 'issuer' claim with the configured issuer and throws OidcIssuerDiscoveryException if they differ. OIDC requires an exact match; a mismatch means the document may belong to a different tenant/realm or a man-in-the-middle.

Source

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

    }

    /**
     * @throws OidcIssuerDiscoveryException
     * @throws ClientExceptionInterface
     */
    protected function loadSettingsFromIssuerDiscovery(ClientInterface $httpClient): array
    {
        $issuerUrl = rtrim($this->issuer, '/') . '/.well-known/openid-configuration';
        $request = new Request('GET', $issuerUrl);
        $response = $httpClient->sendRequest($request);
        $result = json_decode($response->getBody()->getContents(), true);

        if (empty($result) || !is_array($result)) {
            throw new OidcIssuerDiscoveryException("Error discovering provider settings from issuer at URL {$issuerUrl}");
        }

        if ($result['issuer'] !== $this->issuer) {
            throw new OidcIssuerDiscoveryException('Unexpected issuer value found on discovery response');
        }

        $discoveredSettings = [];

        if (!empty($result['authorization_endpoint'])) {
            $discoveredSettings['authorizationEndpoint'] = $result['authorization_endpoint'];
        }

        if (!empty($result['token_endpoint'])) {
            $discoveredSettings['tokenEndpoint'] = $result['token_endpoint'];
        }

        if (!empty($result['userinfo_endpoint'])) {
            $discoveredSettings['userinfoEndpoint'] = $result['userinfo_endpoint'];
        }

        if (!empty($result['jwks_uri'])) {
            $keys = $this->loadKeysFromUri($result['jwks_uri'], $httpClient);

View on GitHub (pinned to 18f8469a1c)

Solutions

  1. Open the discovery URL and copy the exact 'issuer' value into OIDC_ISSUER verbatim.
  2. Check scheme/port/path differences caused by a reverse proxy; set the IdP's public/frontend URL so its advertised issuer matches what BookStack uses.
  3. Clear the 'oidc-discovery::<issuer>' cache entry after correcting the value.
  4. Confirm you're pointing at the intended realm/tenant, not a sibling one.

Example fix

# before
OIDC_ISSUER=https://idp.example.com/realms/wrong
# after (must equal the 'issuer' claim in discovery JSON)
OIDC_ISSUER=https://idp.example.com/realms/main
Defensive patterns

Strategy: validation

Validate before calling

$doc = json_decode(file_get_contents($discoveryUrl), true);
if (($doc['issuer'] ?? null) !== config('oidc.issuer')) {
    throw new RuntimeException('Configured issuer does not match discovery issuer claim: ' . ($doc['issuer'] ?? 'null'));
}

Try / catch

try {
    $settings->discoverFromIssuer($client, $cache, 15);
} catch (OidcIssuerDiscoveryException $e) {
    if (str_contains($e->getMessage(), 'Unexpected issuer')) {
        Log::error('Issuer mismatch — compare configured issuer with discovery document');
    }
}

Prevention

When it happens

Trigger: Configured OIDC_ISSUER differs from the 'issuer' field in the fetched /.well-known/openid-configuration — typically by scheme, port, trailing path, realm name, or http vs https behind a proxy.

Common situations: Keycloak realm path mismatch (issuer without /realms/<name>); IdP behind TLS-offloading proxy advertising https while configured issuer is http (or vice versa); issuer configured with trailing slash while the document has none; pointing at the wrong realm/tenant.

Related errors


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