BookStackApp/BookStack · critical · OidcInvalidTokenException
Missing token audience value
Error message
Missing token audience value
What it means
OIDC requires every ID token to carry an aud (audience) claim listing the client(s) the token was issued for. If the payload's aud claim is absent or empty, validateCommonClaims throws OidcInvalidTokenException('Missing token audience value'). Without aud there is no way to confirm the token was issued for your client.
Source
Thrown at app/Access/Oidc/OidcJwtWithClaims.php:164
* Validate common claims for OIDC JWT tokens.
* As per https://openid.net/specs/openid-connect-basic-1_0.html#IDTokenValidation
* and https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
*
* @throws OidcInvalidTokenException
*/
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
- Verify you are validating an ID token, not an access token — only ID tokens are guaranteed an aud claim for OIDC flows
- Check the decoded payload (base64url-decode the second segment) to confirm aud is actually present; if missing, fix the token source, not the validator
- In your IdP client settings, ensure the audience/client-id mapping is set so aud is always emitted
- If an intermediary rewrites the payload (claims trimming, mapping), whitelist aud so it survives
Example fix
// before: validating an access token $token = $result->getAccessToken(); $validator->validateCommonTokenDetails($token, $clientId); // after: validate the ID token, which carries aud $validator->validateCommonTokenDetails($result->getIdToken(), $clientId);
Defensive patterns
Strategy: validation
Validate before calling
$payload = json_decode(base64_decode(strtr(explode('.', $token)[1], '-_', '+/') . '=='), true);
if (empty($payload['aud'])) {
throw new UnexpectedValueException('Token has no aud claim — is this an ID token?');
} Try / catch
try {
$jwt->validateCommonTokenDetails($token, $clientId);
} catch (OidcInvalidTokenException $e) {
if ($e->getMessage() === 'Missing token audience value') {
throw new UnauthorizedException('ID token lacks aud claim — ensure you are validating an ID token, not an access token');
}
throw $e;
} Prevention
- Only pass ID tokens (from the id_token field of the auth response) into this validator; access tokens go to resource servers
- Sanity-check the decoded payload's claim set once per provider integration (id tokens must have iss, sub, aud, exp, iat)
- Audit any middleware that filters/maps claims to ensure aud is preserved
- Enable IdP-side logging to confirm aud is emitted for your client
When it happens
Trigger: validateCommonClaims($clientId) checks empty($this->payload['aud']) immediately after the issuer check. Triggers when: the IdP is misconfigured not to emit aud; the token is an access token (rather than an ID token) that omits aud; claim-mapping/mangling in middleware strips or renames aud; the token payload was decoded into $this->payload incorrectly (e.g. wrong key order or partially decoded).
Common situations: Feeding an opaque access token or userinfo response into an ID-token validator; IdP client configured without the audience mapping; custom claim filtering (e.g. a proxy that trims claims) removing aud; using a token type whose aud is named differently by the provider.
Related errors
- Token audience value did not match the expected client_id
- Token audience value has ' . count($aud) . ' values, Expecte
- Token authorized party exists but does not match the expecte
- Missing token expiration time value
- Missing token issued at time value
AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02).
Data as JSON: /api/errors/2fa3a5e5bc6a3e30.
Report an issue: GitHub.