BookStackApp/BookStack · critical · OidcInvalidTokenException

Token audience value did not match the expected client_id

Error message

Token audience value did not match the expected client_id

What it means

The token has an aud (audience) claim, but it does not list the client_id this validator was configured with. validateCommonClaims normalizes aud to an array and requires strict in_array($clientId, $aud, true); any other audience causes this OidcInvalidTokenException. This rejects tokens minted for a different application.

Source

Thrown at app/Access/Oidc/OidcJwtWithClaims.php:169

     */
    protected function validateCommonClaims(string $clientId): void
    {
        // 1. The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery)
        // MUST exactly match the value of the iss (issuer) Claim.
        if (empty($this->payload['iss']) || $this->issuer !== $this->payload['iss']) {
            throw new OidcInvalidTokenException('Missing or non-matching token issuer value');
        }

        // 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.
        if (empty($this->payload['aud'])) {
            throw new OidcInvalidTokenException('Missing token audience value');
        }

        $aud = is_string($this->payload['aud']) ? [$this->payload['aud']] : $this->payload['aud'];
        if (!in_array($clientId, $aud, true)) {
            throw new OidcInvalidTokenException('Token audience value did not match the expected client_id');
        }
    }
}

View on GitHub (pinned to 18f8469a1c)

Solutions

  1. Base64url-decode the token payload and compare its aud values to the $clientId you pass — fix whichever side is wrong (usually the configured clientId)
  2. Ensure the clientId passed to validateCommonTokenDetails is exactly the client_id registered at the issuer (no typos, whitespace, or wrong-environment value)
  3. If the token is legitimately for an API audience and your app is the authorized party (azp), configure the IdP to include your client id in aud, or validate at the resource with the API's identifier
  4. For multi-audience tokens, confirm the strict in_array semantics: the client id must appear exactly as an array element — normalize how you store/pass it

Example fix

// before: wrong identifier used as expected audience
$validator->validateCommonTokenDetails($idToken, 'my-frontend-app-name');
// after: use the registered client_id from the same config used for the auth request
$validator->validateCommonTokenDetails($idToken, $config->get('oidc.client_id'));
Defensive patterns

Strategy: validation

Validate before calling

$payload = json_decode(base64_decode(strtr(explode('.', $token)[1], '-_', '+/') . '=='), true);
$aud = $payload['aud'] ?? [];
$aud = is_string($aud) ? [$aud] : $aud;
if (!in_array($clientId, $aud, true)) {
    throw new UnexpectedValueException('aud=' . implode(',', $aud) . ' does not contain expected client_id=' . $clientId);
}

Try / catch

try {
    $jwt->validateCommonTokenDetails($token, $clientId);
} catch (OidcInvalidTokenException $e) {
    if ($e->getMessage() === 'Token audience value did not match the expected client_id') {
        throw new UnauthorizedException('ID token was not issued for this client (aud mismatch)');
    }
    throw $e;
}

Prevention

When it happens

Trigger: validateCommonClaims($clientId) builds $aud from the aud claim (string or array) and checks membership with strict in_array using the $clientId passed into validateCommonTokenDetails. Triggers when: the token's aud is another client's id (token mix-up between apps); your $clientId argument is wrong/stale (e.g. dev vs prod client id); the IdP emits azp/authorized-party differently with extra audiences; case or whitespace differences in the client id (strict comparison is byte-exact).

Common situations: Two frontend apps sharing an auth client but validating with their own client ids; after renaming/re-registering the OAuth client in the IdP while the app still sends the old id; using the redirect URI or app name instead of the registered client_id as the audience check; tokens issued via a resource-server flow whose aud is the API identifier, not your client id.

Related errors


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