{"record":{"id":"8b7c82ffd0ed6c9f","repo":"BookStackApp/BookStack","slug":"token-audience-value-did-not-match-the-expected-cl","errorCode":null,"errorMessage":"Token audience value did not match the expected client_id","messagePattern":"Token audience value did not match the expected client_id","errorType":"exception","errorClass":"OidcInvalidTokenException","httpStatus":null,"severity":"critical","filePath":"app/Access/Oidc/OidcJwtWithClaims.php","lineNumber":169,"sourceCode":"     */\n    protected function validateCommonClaims(string $clientId): void\n    {\n        // 1. The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery)\n        // MUST exactly match the value of the iss (issuer) Claim.\n        if (empty($this->payload['iss']) || $this->issuer !== $this->payload['iss']) {\n            throw new OidcInvalidTokenException('Missing or non-matching token issuer value');\n        }\n\n        // 2. The Client MUST validate that the aud (audience) Claim contains its client_id value registered\n        // at the Issuer identified by the iss (issuer) Claim as an audience. The ID Token MUST be rejected\n        // if the ID Token does not list the Client as a valid audience.\n        if (empty($this->payload['aud'])) {\n            throw new OidcInvalidTokenException('Missing token audience value');\n        }\n\n        $aud = is_string($this->payload['aud']) ? [$this->payload['aud']] : $this->payload['aud'];\n        if (!in_array($clientId, $aud, true)) {\n            throw new OidcInvalidTokenException('Token audience value did not match the expected client_id');\n        }\n    }\n}\n","sourceCodeStart":151,"sourceCodeEnd":173,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/app/Access/Oidc/OidcJwtWithClaims.php#L151-L173","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Base64url-decode the token payload and compare its aud values to the $clientId you pass — fix whichever side is wrong (usually the configured clientId)","Ensure the clientId passed to validateCommonTokenDetails is exactly the client_id registered at the issuer (no typos, whitespace, or wrong-environment value)","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","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"],"exampleFix":"// before: wrong identifier used as expected audience\n$validator->validateCommonTokenDetails($idToken, 'my-frontend-app-name');\n// after: use the registered client_id from the same config used for the auth request\n$validator->validateCommonTokenDetails($idToken, $config->get('oidc.client_id'));","handlingStrategy":"validation","validationCode":"$payload = json_decode(base64_decode(strtr(explode('.', $token)[1], '-_', '+/') . '=='), true);\n$aud = $payload['aud'] ?? [];\n$aud = is_string($aud) ? [$aud] : $aud;\nif (!in_array($clientId, $aud, true)) {\n    throw new UnexpectedValueException('aud=' . implode(',', $aud) . ' does not contain expected client_id=' . $clientId);\n}","typeGuard":null,"tryCatchPattern":"try {\n    $jwt->validateCommonTokenDetails($token, $clientId);\n} catch (OidcInvalidTokenException $e) {\n    if ($e->getMessage() === 'Token audience value did not match the expected client_id') {\n        throw new UnauthorizedException('ID token was not issued for this client (aud mismatch)');\n    }\n    throw $e;\n}","preventionTips":["Pass the exact registered client_id from the same config used for the authorize request — never a name, alias, or redirect URI","After renaming/re-registering a client in the IdP, update and re-test the app config in all environments","In multi-app setups, verify tokens carry the correct azp/aud for the app that receives them (token mix-up guard)","Write an integration test that validates a real (test-tenant) ID token end-to-end to catch aud config drift"],"tags":["php","oidc","jwt","audience-validation","client-id"],"backgroundTag":"jwt-audience-mismatch","analyzedSha":"18f8469a1c72f8cc8497e9372635e6dea5028071","analyzedAt":"2026-09-02T19:49:33.068Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}