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 is

View on GitHub (pinned to 820761864e)

Solutions

  1. Force a JWKS refresh / clear the broker's key cache so it picks up the IdP's current signing keys (check 'kid' handling)
  2. Verify the broker's issuer/discovery URL matches the IdP that actually issued the client's token
  3. 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
  4. 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

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.