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

  1. Confirm the 'oauth_client_id' in Roundcube config matches the client the IDP issued the token for.
  2. Decode the failing token and inspect its 'azp' claim to see which client it targets.
  3. Re-do the login/token exchange flow so a fresh token for the correct client is obtained.
  4. 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

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


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)