{"record":{"id":"9ee49278ca999178","repo":"apache/pulsar","slug":"error-verifying-jwt-signature","errorCode":"ERROR_VERIFYING_JWT_SIGNATURE","errorMessage":"JWT signature verification exception: ","messagePattern":"JWT signature verification exception: ","errorType":"exception","errorClass":"AuthenticationException","httpStatus":null,"severity":"error","filePath":"pulsar-broker-auth-oidc/src/main/java/org/apache/pulsar/broker/authentication/oidc/AuthenticationProviderOpenID.java","lineNumber":464,"sourceCode":"                .withAnyOfAudience(allowedAudiences)\n                .withClaimPresence(RegisteredClaims.ISSUED_AT)\n                .withClaimPresence(RegisteredClaims.EXPIRES_AT)\n                .withClaimPresence(RegisteredClaims.SUBJECT);\n\n        if (isRoleClaimNotSubject) {\n            verifierBuilder = verifierBuilder.withClaimPresence(roleClaim);\n        }\n\n        JWTVerifier verifier = verifierBuilder.build();\n\n        try {\n            return verifier.verify(jwt);\n        } catch (TokenExpiredException e) {\n            incrementFailureMetric(AuthenticationExceptionCode.EXPIRED_JWT);\n            throw new AuthenticationException(\"JWT expired: \" + e.getMessage());\n        } catch (SignatureVerificationException e) {\n            incrementFailureMetric(AuthenticationExceptionCode.ERROR_VERIFYING_JWT_SIGNATURE);\n            throw new AuthenticationException(\"JWT signature verification exception: \" + e.getMessage());\n        } catch (InvalidClaimException e) {\n            incrementFailureMetric(AuthenticationExceptionCode.INVALID_JWT_CLAIM);\n            throw new AuthenticationException(\"JWT contains invalid claim: \" + e.getMessage());\n        } catch (AlgorithmMismatchException e) {\n            incrementFailureMetric(AuthenticationExceptionCode.ALGORITHM_MISMATCH);\n            throw new AuthenticationException(\"JWT algorithm does not match Public Key algorithm: \" + e.getMessage());\n        } catch (JWTDecodeException e) {\n            incrementFailureMetric(AuthenticationExceptionCode.ERROR_DECODING_JWT);\n            throw new AuthenticationException(\"Error while decoding JWT: \" + e.getMessage());\n        } catch (JWTVerificationException | IllegalArgumentException e) {\n            incrementFailureMetric(AuthenticationExceptionCode.ERROR_VERIFYING_JWT);\n            throw new AuthenticationException(\"JWT verification failed: \" + e.getMessage());\n        }\n    }\n\n    /**\n     * Validate the configured allow list of allowedIssuers. The allowedIssuers set must be nonempty in order for\n     * the plugin to authenticate any token. Thus, it fails initialization if the configuration is","sourceCodeStart":446,"sourceCodeEnd":482,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker-auth-oidc/src/main/java/org/apache/pulsar/broker/authentication/oidc/AuthenticationProviderOpenID.java#L446-L482","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: stale key cache\nPublicKey key = keyCache.get(oldKid); // rotated away by IdP\n// after: fetch current keys for the token's kid\nJWKS jwks = JWKS.fetch(discoveryUrl);\nPublicKey key = jwks.getByKid(jwt.getHeaderClaim(\"kid\").asString());","handlingStrategy":"validation","validationCode":"// verify the token against the IdP's current public key before connecting\nClaims claims = Jwts.parserBuilder()\n    .setSigningKey(resolveJwksKey(jwt.getHeader().get(\"kid\")))\n    .build().parseClaimsJws(jwt).getBody(); // throws SignatureException on mismatch","typeGuard":null,"tryCatchPattern":"try {\n    return verifyJWT(publicKey, publicKeyAlg, jwt);\n} catch (AuthenticationException e) {\n    if (e.getMessage().startsWith(\"JWT signature verification exception\")) {\n        log.warn(\"Bad JWT signature from peer — possible wrong issuer or rotated keys\");\n        refreshJwksKeys();\n    }\n    throw e;\n}","preventionTips":["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"],"tags":["auth","oidc","jwt","signature","security"],"backgroundTag":"jwt-signature-verification-failed","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}