roundcube/roundcubemail · error · RuntimeException
Failed to validate JWT: missing aud/azp value
Error message
Failed to validate JWT: missing aud/azp value
What it means
Every OIDC token must bind to a party: jwt_decode() requires at least one of 'azp' or 'aud'. A token with neither cannot be attributed to this client, so it is rejected.
Solutions
- Configure the IDP to include 'aud' (or 'azp') in issued tokens.
- Verify the token type reaching jwt_decode is an ID/access/logout token, not a raw userinfo payload.
- Decode the token and confirm which claims exist; fix the issuing endpoint accordingly.
- If you control a test issuer, add "aud":"<client_id>" to the payload.
Example fix
// before
{"iss":"https://idp","sub":"u1","exp":1893456000}
// after
{"iss":"https://idp","sub":"u1","aud":"roundcube","exp":1893456000} Defensive patterns
Strategy: validation
Validate before calling
$claims = json_decode(base64_decode(strtr(explode('.', $token)[1], '-_', '+/')), true);
if (!isset($claims['azp']) && !isset($claims['aud'])) { /* abort: unattributable token */ } Type guard
function hasPartyClaim(array $claims): bool { return isset($claims['azp']) || isset($claims['aud']); } Try / catch
try { $payload = $oauth->jwt_decode($token); } catch (\RuntimeException $e) { // reject token, redirect to login } Prevention
- Require the IDP to always emit 'aud' on JWTs.
- Validate token shape with a JWT library before sending to Roundcube.
- Avoid routing non-standard payloads through jwt_decode.
When it happens
Trigger: parse_tokens() -> jwt_decode() on a token whose decoded payload contains neither 'azp' nor 'aud' claims.
Common situations: Custom or minimal IDP issuing bare JWTs without audience claims; malformed test tokens; provider emitting opaque-style tokens as JWTs without standard claims; userinfo-like payloads mistakenly passed through jwt_decode.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- Failed to validate JWT: invalid azp value
- Failed to validate JWT: invalid aud value
- OIDC: Handle only logout events
- OIDC: event has no "sub"
- OIDC: event has non-empty "nonce"
AI-assisted analysis of roundcube/roundcubemail@4b54c2acfb (2026-09-14).
Data as JSON: /api/errors/57f1ceda5d4e50a9.
Report an issue: GitHub.
Appendix: source
Thrown at program/include/rcmail_oauth.php:452
// Validate the token (throws exceptions)
$header = new \stdClass();
$body = (array) JWT::decode($jwt, JWK::parseKeySet($this->jwks), $header);
$header = (array) $header;
} else {
[$headb64, $bodyb64, $cryptob64] = explode('.', $jwt);
$header = json_decode(static::base64url_decode($headb64), true);
$body = json_decode(static::base64url_decode($bodyb64), true);
// $crypto = static::base64url_decode($cryptob64);
}
// FIXME depends on body type: ID, Logout, Bearer, Refresh,
if (isset($body['azp']) && $body['azp'] !== $this->options['client_id']) {
throw new \RuntimeException('Failed to validate JWT: invalid azp value');
} elseif (isset($body['aud']) && !in_array($this->options['client_id'], (array) $body['aud'])) {
throw new \RuntimeException('Failed to validate JWT: invalid aud value');
} elseif (!isset($body['azp']) && !isset($body['aud'])) {
throw new \RuntimeException('Failed to validate JWT: missing aud/azp value');
}
// if defined in parameters, check that issuer match
if (isset($this->options['issuer']) && $body['iss'] !== $this->options['issuer']) {
throw new \RuntimeException('Failed to validate JWT: issuer mismatch');
}
// check that token is not an outdated message
if (isset($body['exp']) && (time() > $body['exp'])) {
throw new \RuntimeException('Failed to validate JWT: expired message');
}
$body['header'] = $header;
$this->log_debug('jwt: %s', json_encode($body));
return $body;
}View on GitHub (pinned to 4b54c2acfb)