{"record":{"id":"5854d814d62384c7","repo":"roundcube/roundcubemail","slug":"failed-to-validate-jwt-invalid-azp-value","errorCode":null,"errorMessage":"Failed to validate JWT: invalid azp value","messagePattern":"Failed to validate JWT: invalid azp value","errorType":"validation","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"program/include/rcmail_oauth.php","lineNumber":448,"sourceCode":"        if ($this->options['jwks_uri']) {\n            // TODO: If jwks is not available we could get the public key from config\n            $this->fetch_jwks();\n\n            // Validate the token (throws exceptions)\n            $header = new \\stdClass();\n            $body = (array) JWT::decode($jwt, JWK::parseKeySet($this->jwks), $header);\n            $header = (array) $header;\n        } else {\n            [$headb64, $bodyb64, $cryptob64] = explode('.', $jwt);\n\n            $header = json_decode(static::base64url_decode($headb64), true);\n            $body = json_decode(static::base64url_decode($bodyb64), true);\n            // $crypto = static::base64url_decode($cryptob64);\n        }\n\n        // FIXME depends on body type: ID, Logout, Bearer, Refresh,\n        if (isset($body['azp']) && $body['azp'] !== $this->options['client_id']) {\n            throw new \\RuntimeException('Failed to validate JWT: invalid azp value');\n        } elseif (isset($body['aud']) && !in_array($this->options['client_id'], (array) $body['aud'])) {\n            throw new \\RuntimeException('Failed to validate JWT: invalid aud value');\n        } elseif (!isset($body['azp']) && !isset($body['aud'])) {\n            throw new \\RuntimeException('Failed to validate JWT: missing aud/azp value');\n        }\n\n        // if defined in parameters, check that issuer match\n        if (isset($this->options['issuer']) && $body['iss'] !== $this->options['issuer']) {\n            throw new \\RuntimeException('Failed to validate JWT: issuer mismatch');\n        }\n\n        // check that token is not an outdated message\n        if (isset($body['exp']) && (time() > $body['exp'])) {\n            throw new \\RuntimeException('Failed to validate JWT: expired message');\n        }\n\n        $body['header'] = $header;\n","sourceCodeStart":430,"sourceCodeEnd":466,"githubUrl":"https://github.com/roundcube/roundcubemail/blob/4b54c2acfb54d5ee3d1c281ca7f143bed0dea804/program/include/rcmail_oauth.php#L430-L466","documentation":"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.","triggerScenarios":"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'].","commonSituations":"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.","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."],"exampleFix":"// before\n$config['oauth_client_id'] = 'old-client'; // token azp = 'roundcube-web'\n// after\n$config['oauth_client_id'] = 'roundcube-web';","handlingStrategy":"validation","validationCode":"$claims = json_decode(base64_decode(strtr(explode('.', $token)[1], '-_', '+/')), true);\nif (isset($claims['azp']) && $claims['azp'] !== RCUBE_OAUTH_CLIENT_ID) { /* abort: token issued for another client */ }","typeGuard":"function azpMatches(array $claims, string $clientId): bool { return !isset($claims['azp']) || $claims['azp'] === $clientId; }","tryCatchPattern":"try { $payload = $oauth->jwt_decode($token); } catch (\\RuntimeException $e) { // fall back to re-authentication flow }","preventionTips":["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."],"tags":["oauth","jwt","validation","oidc"],"backgroundTag":"invalid-argument-value","analyzedSha":"4b54c2acfb54d5ee3d1c281ca7f143bed0dea804","analyzedAt":"2026-09-14T13:23:19.231Z","contentChangedAt":"2026-09-14T13:23:19.231Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}