apache/pulsar · error · AuthenticationException
ERROR_VERIFYING_JWT_SIGNATURE
ERROR_VERIFYING_JWT_SIGNATURE
Error message
JWT signature verification exception:
What it means
The JWT is syntactically valid and its header names an algorithm, but the signature does not verify against the supplied public key: java-jwt throws SignatureVerificationException. The provider records ERROR_VERIFYING_JWT_SIGNATURE and throws AuthenticationException with the library's message. This means the token was either not signed by the trusted key or was altered in transit.
Source
Thrown at pulsar-broker-auth-oidc/src/main/java/org/apache/pulsar/broker/authentication/oidc/AuthenticationProviderOpenID.java:464
.withAnyOfAudience(allowedAudiences)
.withClaimPresence(RegisteredClaims.ISSUED_AT)
.withClaimPresence(RegisteredClaims.EXPIRES_AT)
.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 isView on GitHub (pinned to 820761864e)
Solutions
- Force a JWKS refresh / clear the broker's key cache so it picks up the IdP's current signing keys (check 'kid' handling)
- Verify the broker's issuer/discovery URL matches the IdP that actually issued the client's token
- Compare the token's signing key with the broker's configured public key (openssl: verify the JWT signature manually) to confirm they belong to the same key pair
- Re-obtain a fresh token from the correct issuer and retry — rule out token tampering by proxies in transit
Example fix
// before: stale key cache
PublicKey key = keyCache.get(oldKid); // rotated away by IdP
// after: fetch current keys for the token's kid
JWKS jwks = JWKS.fetch(discoveryUrl);
PublicKey key = jwks.getByKid(jwt.getHeaderClaim("kid").asString()); Defensive patterns
Strategy: validation
Validate before calling
// verify the token against the IdP's current public key before connecting
Claims claims = Jwts.parserBuilder()
.setSigningKey(resolveJwksKey(jwt.getHeader().get("kid")))
.build().parseClaimsJws(jwt).getBody(); // throws SignatureException on mismatch Try / catch
try {
return verifyJWT(publicKey, publicKeyAlg, jwt);
} catch (AuthenticationException e) {
if (e.getMessage().startsWith("JWT signature verification exception")) {
log.warn("Bad JWT signature from peer — possible wrong issuer or rotated keys");
refreshJwksKeys();
}
throw e;
} Prevention
- Keep JWKS cache refresh short enough to absorb IdP key rotation quickly
- Verify the broker's issuer/discovery URL matches the token's iss claim
- Verify tokens with openssl/jwt-cli against the configured key during setup
- Investigate clusters of signature failures — they may indicate tampering or probing
When it happens
Trigger: verifier.verify(jwt) in verifyJWT() throws SignatureVerificationException — the base64url signature decoded from the token does not match the signature computed with the broker's public key under the selected Algorithm.
Common situations: Broker cached the wrong/old JWKS key (IdP rotated keys; 'kid' mismatch not checked); client obtained its token from a different issuer than the broker is configured to trust; token payload modified by a misbehaving proxy; developer hand-crafted a token signed with a dev key not matching the broker's configured key.
Related errors
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/9ee49278ca999178.
Report an issue: GitHub.