{"record":{"id":"a15b4f4f6eed8203","repo":"apache/pulsar","slug":"error-verifying-jwt","errorCode":"ERROR_VERIFYING_JWT","errorMessage":"JWT verification failed: ","messagePattern":"JWT verification failed: ","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":476,"sourceCode":"            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\n     * missing. Each issuer URL should use the HTTPS scheme. The plugin fails initialization if any\n     * issuer url is insecure, unless requireHttps is false.\n     * @param allowedIssuers - issuers to validate\n     * @param requireHttps - whether to require https for issuers.\n     * @param allowEmptyIssuers - whether to allow empty issuers. This setting only makes sense when kubernetes is used\n     *                   as a fallback issuer.\n     * @return the validated issuers\n     * @throws IllegalArgumentException if the allowedIssuers is empty, or contains insecure issuers when required\n     */\n    private Set<String> validateIssuers(Set<String> allowedIssuers, boolean requireHttps, boolean allowEmptyIssuers) {\n        if (allowedIssuers == null || (allowedIssuers.isEmpty() && !allowEmptyIssuers)) {\n            throw new IllegalArgumentException(\"Missing configured value for: \" + ALLOWED_TOKEN_ISSUERS);","sourceCodeStart":458,"sourceCodeEnd":494,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker-auth-oidc/src/main/java/org/apache/pulsar/broker/authentication/oidc/AuthenticationProviderOpenID.java#L458-L494","documentation":"Thrown by verifyJWT when java-jwt raises a generic JWTVerificationException or an IllegalArgumentException during verifier.verify(jwt) — any verification failure not covered by the more specific expired/signature/invalid-claim/algorithm-mismatch/decode handlers. The provider increments the ERROR_VERIFYING_JWT failure metric and appends the library's message.","triggerScenarios":"verification fails on claim-presence checks configured via withClaimPresence (iss, sub, iat, exp or the custom role claim missing), withAnyOfAudience rejection with an IllegalArgumentException, or other JWTVerificationException subclasses not explicitly caught.","commonSituations":"ID token lacks the subject (sub) or issued-at (iat) claim; the roleClaim configured on the broker is absent from tokens issued by the IdP; audience claim does not match tokenAudience/allowedAudiences configuration after an IdP or client-audience change; token clock fields set to zero or negative values.","solutions":["Decode the token (e.g. jwt.io) and check that sub, iat, exp and aud claims are present and non-empty","Compare the token's aud claim against the broker's allowed token audiences and align configuration","If isRoleClaimNotSubject is set, ensure the configured roleClaim exists in the token or add it via IdP claim mapping","Enable broker debug logging of AuthenticationException messages to see the exact java-jwt message for the failure"],"exampleFix":"// before (broker requires custom claim the IdP does not issue)\nauthenticationProviderOpenID.roleClaim=\"custom_role\";\n// after (map the claim in the IdP or use the default subject role)\nauthenticationProviderOpenID.roleClaim=\"roles\"; // and map roles claim in IdP","handlingStrategy":"validation","validationCode":"// Pre-check the token's required claims before presenting it to the broker\nDecodedJWT jwt = JWT.decode(token);\nif (jwt.getSubject() == null || jwt.getIssuedAt() == null || jwt.getExpiresAt() == null\n        || jwt.getAudience() == null || jwt.getAudience().isEmpty()) {\n    throw new IllegalStateException(\"Token missing required claims: sub, iat, exp, aud\");\n}\n// and confirm the aud claim matches an allowed broker audience","typeGuard":null,"tryCatchPattern":"try {\n    authentication.authenticate(authDataSource);\n} catch (AuthenticationException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"JWT verification failed\")) {\n        // inspect claims: missing sub/iat/exp, absent role claim, or audience mismatch\n        throw new IllegalStateException(\"Token rejected: \" + e.getMessage(), e);\n    } else {\n        throw e;\n    }\n}","preventionTips":["Configure the IdP to include sub, iat, exp and aud in every ID token (per the OIDC basic spec)","Keep the broker's roleClaim setting in sync with claims actually present in tokens","Align client audience and broker allowedTokenAudiences after any IdP tenant/app change","Decode a sample token after IdP upgrades to detect removed or renamed claims early"],"tags":["jwt","oidc","authentication","claim-validation"],"backgroundTag":"jwt-verification-failed","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}