roundcube/roundcubemail · error · RuntimeException
Failed to validate JWT: invalid azp value
Error message
Failed to validate JWT: invalid azp value
What it means
jwt_decode() validates that a token's 'azp' (authorized party) claim equals the configured OAuth client_id. If the token was issued for a different client, Roundcube rejects it because the token is not intended for this application.
Solutions
- Confirm the 'oauth_client_id' in Roundcube config matches the client the IDP issued the token for.
- Decode the failing token and inspect its 'azp' claim to see which client it targets.
- Re-do the login/token exchange flow so a fresh token for the correct client is obtained.
- If the IDP sets unexpected azp (e.g. adds legacy clients to the audience), fix the IDP client config.
Example fix
// before $config['oauth_client_id'] = 'old-client'; // token azp = 'roundcube-web' // after $config['oauth_client_id'] = 'roundcube-web';
Defensive patterns
Strategy: validation
Validate before calling
$claims = json_decode(base64_decode(strtr(explode('.', $token)[1], '-_', '+/')), true);
if (isset($claims['azp']) && $claims['azp'] !== RCUBE_OAUTH_CLIENT_ID) { /* abort: token issued for another client */ } Type guard
function azpMatches(array $claims, string $clientId): bool { return !isset($claims['azp']) || $claims['azp'] === $clientId; } Try / catch
try { $payload = $oauth->jwt_decode($token); } catch (\RuntimeException $e) { // fall back to re-authentication flow } Prevention
- Keep oauth_client_id in sync with the IDP client registration.
- Never reuse tokens obtained for other applications.
- Re-run the full login flow after changing client ids.
When it happens
Trigger: parse_tokens() -> jwt_decode() on an ID/access/logout token whose payload has 'azp' set to a client id other than $this->options['client_id'].
Common situations: Multiple OAuth clients registered; tokens minted for another app (e.g. web client vs Roundcube) pasted into the flow; IDP misconfiguration putting the wrong azp; rotated client ids where config still holds the old id.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Failed to validate JWT: invalid aud value
- Failed to validate JWT: missing aud/azp 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/5854d814d62384c7.
Report an issue: GitHub.
Appendix: source
Thrown at program/include/rcmail_oauth.php:448
if ($this->options['jwks_uri']) {
// TODO: If jwks is not available we could get the public key from config
$this->fetch_jwks();
// 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;
View on GitHub (pinned to 4b54c2acfb)