{"record":{"id":"7d13be4dedd9082c","repo":"apache/pulsar","slug":"found-null-audience-in-token-for-claimed-field","errorCode":null,"errorMessage":"Found null Audience in token, for claimed field: ${audienceClaim}","messagePattern":"Found null Audience in token, for claimed field: (.+?)","errorType":"exception","errorClass":"JwtException","httpStatus":null,"severity":"error","filePath":"pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authentication/AuthenticationProviderToken.java","lineNumber":240,"sourceCode":"    }\n\n    private static String validateToken(final String token) throws AuthenticationException {\n        if (StringUtils.isNotBlank(token)) {\n            return token;\n        } else {\n            throw new AuthenticationException(\"Blank token found\");\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private Jws<Claims> authenticateToken(final String token) throws AuthenticationException {\n        try {\n            Jws<Claims> jwt = parser.parseClaimsJws(token);\n\n            if (audienceClaim != null) {\n                Object object = jwt.getBody().get(audienceClaim);\n                if (object == null) {\n                    throw new JwtException(\"Found null Audience in token, for claimed field: \" + audienceClaim);\n                }\n\n                if (object instanceof Collection) {\n                    Collection<String> audiences = (Collection<String>) object;\n                    // audience not contains this broker, throw exception.\n                    if (audiences.stream().noneMatch(audienceInToken -> audienceInToken.equals(audience))) {\n                        incrementFailureMetric(ErrorCode.INVALID_AUDIENCES);\n                        throw new AuthenticationException(\"Audiences in token: [\"\n                                + String.join(\", \", audiences) + \"] not contains this broker: \" + audience);\n                    }\n                } else if (object instanceof String) {\n                    if (!object.equals(audience)) {\n                        incrementFailureMetric(ErrorCode.INVALID_AUDIENCES);\n                        throw new AuthenticationException(\n                                \"Audiences in token: [\" + object + \"] not contains this broker: \" + audience);\n                    }\n                } else {\n                    // should not reach here.","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authentication/AuthenticationProviderToken.java#L222-L258","documentation":"authenticateToken() enforces audience validation when audienceClaim is configured: the JWT body must contain that claim. This check throws io.jsonwebtoken.JwtException (which authenticateToken wraps/rethrows) when the claim key exists in the config but the token's payload has a null/absent value for it — the token was issued without the audience claim the broker requires.","triggerScenarios":"A token parsed by parser.parseClaimsJws(token) whose Claims map returns null for the configured audienceClaim key, while the broker has tokenAudienceClaim set.","commonSituations":"Tokens issued by an older token issuer or a different tool that doesn't add the 'aud' (or custom) claim; changing the audienceClaim config name (e.g. from 'aud' to a custom name) without re-issuing tokens; mixing tokens from multiple issuers with different claim layouts.","solutions":["Re-issue the token including the audience claim with the broker's audience value","Align the token issuer's claim name with the broker's tokenAudienceClaim setting","If audience validation is not needed, remove tokenAudienceClaim from broker config"],"exampleFix":"// before: token without aud claim\nString token = Jwts.builder().setSubject(\"admin\").signWith(key).compact();\n// after\nString token = Jwts.builder().setSubject(\"admin\")\n    .claim(\"aud\", \"pulsar\") // or setAudience(\"pulsar\")\n    .signWith(key).compact();","handlingStrategy":"validation","validationCode":"Claims claims = Jwts.parser().setSigningKey(key).build()\n    .parseClaimsJws(token).getBody();\nif (claims.get(\"aud\") == null) {\n    throw new JwtException(\"Token missing required audience claim\");\n}","typeGuard":"boolean hasAudienceClaim(Jws<Claims> jwt, String claim) {\n    return jwt != null && jwt.getBody().get(claim) != null;\n}","tryCatchPattern":"try {\n    role = provider.authenticate(authData);\n} catch (Exception e) {\n    log.warn(\"Token rejected: missing audience claim; re-issue token with aud\", e);\n    return 401;\n}","preventionTips":["Ensure the token issuer always adds the audience claim matching tokenAudienceClaim","Re-issue tokens whenever the claim name config changes","Add a token smoke test to deployment pipelines"],"tags":["jwt","token","claims"],"backgroundTag":"jwt-missing-claim","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"}