BookStackApp/BookStack · error · OidcInvalidTokenException

Token authorized party exists but does not match the expecte

Error message

Token authorized party exists but does not match the expected client_id

What it means

Per OIDC Core spec section 3.1.3.7, when an azp (authorized party) claim is present it must equal the client ID of the consuming client. OidcIdToken rejects the token when azp exists but does not match the configured client ID, because the token was authorized for a different party.

Source

Thrown at app/Access/Oidc/OidcIdToken.php:48

        // Already done in parent.

        // 2. The Client MUST validate that the aud (audience) Claim contains its client_id value registered
        // at the Issuer identified by the iss (issuer) Claim as an audience. The ID Token MUST be rejected
        // if the ID Token does not list the Client as a valid audience, or if it contains additional
        // audiences not trusted by the Client.
        // Partially done in parent.
        $aud = is_string($this->payload['aud']) ? [$this->payload['aud']] : $this->payload['aud'];
        if (count($aud) !== 1) {
            throw new OidcInvalidTokenException('Token audience value has ' . count($aud) . ' values, Expected 1');
        }

        // 3. If the ID Token contains multiple audiences, the Client SHOULD verify that an azp Claim is present.
        // NOTE: Addressed by enforcing a count of 1 above.

        // 4. If an azp (authorized party) Claim is present, the Client SHOULD verify that its client_id
        // is the Claim Value.
        if (isset($this->payload['azp']) && $this->payload['azp'] !== $clientId) {
            throw new OidcInvalidTokenException('Token authorized party exists but does not match the expected client_id');
        }

        // 5. The current time MUST be before the time represented by the exp Claim
        // (possibly allowing for some small leeway to account for clock skew).
        if (empty($this->payload['exp'])) {
            throw new OidcInvalidTokenException('Missing token expiration time value');
        }

        $skewSeconds = 120;
        $now = time();
        if ($now >= (intval($this->payload['exp']) + $skewSeconds)) {
            throw new OidcInvalidTokenException('Token has expired');
        }

        // 6. The iat Claim can be used to reject tokens that were issued too far away from the current time,
        // limiting the amount of time that nonces need to be stored to prevent attacks.
        // The acceptable range is Client specific.
        if (empty($this->payload['iat'])) {

View on GitHub (pinned to 18f8469a1c)

Solutions

  1. Compare the token's azp claim (decode the JWT) with the client_id configured in BookStack and make them match
  2. Fix the OIDC client_id in BookStack .env/config if it is wrong
  3. At the IdP, configure the client/mapper so azp equals the BookStack client ID
  4. Ensure tokens are being obtained through BookStack's own authorization flow, not reused from another application

Example fix

// before (.env)
OIDC_CLIENT_ID=wrong-client
// after
OIDC_CLIENT_ID=bookstack-client  # must equal the token's azp/aud
Defensive patterns

Strategy: validation

Validate before calling

$payload = /* decode jwt payload */;
if (isset($payload['azp']) && $payload['azp'] !== $expectedClientId) {
    throw new InvalidArgumentException('azp does not match configured client_id');
}

Type guard

function azpMatchesClient(array $payload, string $clientId): bool {
    return !isset($payload['azp']) || $payload['azp'] === $clientId;
}

Try / catch

try {
    $idToken = OidcIdToken::validate($token, $clientId, $keys);
} catch (OidcInvalidTokenException $e) {
    if (str_contains($e->getMessage(), 'authorized party')) {
        Log::error('OIDC azp mismatch — check client_id config and IdP client setup');
    }
    throw $e;
}

Prevention

When it happens

Trigger: validateTokenClaims (via validate) sees payload['azp'] set to a value different from BookStack's configured OIDC client ID — the token was minted for another client or the IdP sets azp to a client identifier that differs from the aud value.

Common situations: Keycloak/Auth0 issuing tokens with azp from a different client due to token exchange or impersonation; BookStack client_id misconfigured (e.g. pointing at the wrong Keycloak client); copying a token from another application during debugging; IdP realm migration changing client IDs.

Related errors


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