apache/pulsar · error · AuthenticationException
INVALID_JWT_CLAIM
INVALID_JWT_CLAIM
Error message
JWT contains invalid claim:
What it means
The signature was valid but one or more required claims are wrong or missing: java-jwt's verifier throws InvalidClaimException (e.g., the token lacks the 'aud' the verifier requires, or an iss/aud/nbf claim fails the check). The provider records INVALID_JWT_CLAIM and throws AuthenticationException with the library's detail. Note the provider builds its verifier with claim requirements based on the OIDC Basic spec (ID Token claim presence), so tokens that are mere access tokens or from misconfigured clients fail here.
Source
Thrown at pulsar-broker-auth-oidc/src/main/java/org/apache/pulsar/broker/authentication/oidc/AuthenticationProviderOpenID.java:467
.withClaimPresence(RegisteredClaims.SUBJECT);
if (isRoleClaimNotSubject) {
verifierBuilder = verifierBuilder.withClaimPresence(roleClaim);
}
JWTVerifier verifier = verifierBuilder.build();
try {
return verifier.verify(jwt);
} catch (TokenExpiredException e) {
incrementFailureMetric(AuthenticationExceptionCode.EXPIRED_JWT);
throw new AuthenticationException("JWT expired: " + e.getMessage());
} catch (SignatureVerificationException e) {
incrementFailureMetric(AuthenticationExceptionCode.ERROR_VERIFYING_JWT_SIGNATURE);
throw new AuthenticationException("JWT signature verification exception: " + e.getMessage());
} catch (InvalidClaimException e) {
incrementFailureMetric(AuthenticationExceptionCode.INVALID_JWT_CLAIM);
throw new AuthenticationException("JWT contains invalid claim: " + e.getMessage());
} catch (AlgorithmMismatchException e) {
incrementFailureMetric(AuthenticationExceptionCode.ALGORITHM_MISMATCH);
throw new AuthenticationException("JWT algorithm does not match Public Key algorithm: " + e.getMessage());
} catch (JWTDecodeException e) {
incrementFailureMetric(AuthenticationExceptionCode.ERROR_DECODING_JWT);
throw new AuthenticationException("Error while decoding JWT: " + e.getMessage());
} catch (JWTVerificationException | IllegalArgumentException e) {
incrementFailureMetric(AuthenticationExceptionCode.ERROR_VERIFYING_JWT);
throw new AuthenticationException("JWT verification failed: " + e.getMessage());
}
}
/**
* Validate the configured allow list of allowedIssuers. The allowedIssuers set must be nonempty in order for
* the plugin to authenticate any token. Thus, it fails initialization if the configuration is
* missing. Each issuer URL should use the HTTPS scheme. The plugin fails initialization if any
* issuer url is insecure, unless requireHttps is false.
* @param allowedIssuers - issuers to validateView on GitHub (pinned to 820761864e)
Solutions
- Inspect the failing token's payload (decode base64url) and confirm it is an ID Token containing iss, sub, aud, exp, iat — not a bare access token
- Align the 'aud' (audience) configured for the broker with the audience the IdP puts in the token (client_id) — fix whichever side is wrong
- Update the issuer URL in the broker configuration to match the IdP's iss claim exactly (including scheme/trailing slash)
- Reconfigure the IdP client to include required claims (e.g., enable ID token issuance with the correct audience)
Example fix
// before: client passes access token String jwt = tokenResponse.getAccessToken(); // after: pass the ID token with iss/sub/aud/exp/iat String jwt = tokenResponse.getIdToken();
Defensive patterns
Strategy: validation
Validate before calling
DecodedJWT j = JWT.decode(jwt);
for (String req : List.of("iss", "sub", "aud", "exp", "iat")) {
if (j.getClaim(req).isNull()) {
throw new IllegalArgumentException("Token missing required claim: " + req);
}
}
if (!expectedAudience.equals(j.getAudience().get(0))) {
throw new IllegalArgumentException("aud mismatch: " + j.getAudience());
} Try / catch
try {
return verifyJWT(publicKey, publicKeyAlg, jwt);
} catch (AuthenticationException e) {
if (e.getMessage().startsWith("JWT contains invalid claim")) {
log.warn("Claim validation failed for token; check aud/iss configuration");
}
throw e;
} Prevention
- Send the ID Token, not the access token, as the client credential
- Set the broker's expected audience to the IdP client_id (or configure the IdP to emit the broker's audience)
- Match the issuer URL exactly, including scheme and trailing slash
- Decode a sample token during setup and eyeball required claims before deploying
When it happens
Trigger: verifier.verify(jwt) in verifyJWT() throws InvalidClaimException — typically the required 'aud' claim doesn't match, required claims (iss, sub, aud, exp, iat) are absent, or a claim's value doesn't satisfy the requirement set on the verifier builder.
Common situations: Client sends an opaque access token instead of an ID Token (no iss/sub/aud claims); IdP issues tokens with a different audience than the broker expects/verifier requires; IdP omits 'iat' or 'nonce' requirements; issuer URL misconfiguration (trailing slash differences) after an IdP migration.
Related errors
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/da81fda0b2c4f945.
Report an issue: GitHub.