{"record":{"id":"545a85f40a9b255c","repo":"apache/pulsar","slug":"audiences-in-token-is-not-in-expected-format-ob","errorCode":null,"errorMessage":"Audiences in token is not in expected format: ${object}","messagePattern":"Audiences in token is not in expected format: (.+?)","errorType":"exception","errorClass":"AuthenticationException","httpStatus":null,"severity":"error","filePath":"pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authentication/AuthenticationProviderToken.java","lineNumber":260,"sourceCode":"\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.\n                    incrementFailureMetric(ErrorCode.INVALID_AUDIENCES);\n                    throw new AuthenticationException(\"Audiences in token is not in expected format: \" + object);\n                }\n            }\n\n            var expiration = jwt.getBody().getExpiration();\n            var tokenRemainingDurationMs = expiration != null ? expiration.getTime() - new Date().getTime() : null;\n            authenticationMetricsToken.recordTokenDuration(tokenRemainingDurationMs);\n            return jwt;\n        } catch (JwtException e) {\n            if (e instanceof ExpiredJwtException) {\n                authenticationMetricsToken.recordTokenExpired();\n            }\n            incrementFailureMetric(ErrorCode.INVALID_TOKEN);\n            throw new AuthenticationException(\"Failed to authentication token: \" + e.getMessage());\n        }\n    }\n\n    private String getPrincipal(Jws<Claims> jwt) {\n        try {","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authentication/AuthenticationProviderToken.java#L242-L278","documentation":"This branch is reached when the configured audience claim in the JWT is neither a Collection nor a String. The provider only knows how to validate audience values of those two shapes, so any other JSON type (number, boolean, nested object/array of non-strings) is rejected with this AuthenticationException. The comment 'should not reach here' marks it as a defensive case for malformed tokens.","triggerScenarios":"authenticateToken parses a token where jwt.getBody().get(audienceClaim) returns an object that is not a Collection<String> or String — e.g. the aud-style claim was issued as a number or a map — while audience validation is enabled.","commonSituations":"A custom token issuer serializes the audience claim in an unexpected type (e.g. numeric realm ID); a misconfigured claim name points at a claim that holds a non-string value; a JSON library upgrade changes deserialization of the claim.","solutions":["Re-issue the token so the audience claim is either a string or an array of strings.","Fix the token issuer's claim generation so it emits the standard aud format (string or list of strings).","Verify the configured tokenAudienceClaim points at the intended claim; if the claim name collides with a non-audience field, correct it.","If audience checking is unnecessary, disable it by removing the audience claim configuration."],"exampleFix":"// before (issuer): payload.put(\"aud\", 12345)\n// after (issuer): payload.put(\"aud\", \"urn:my-broker\")","handlingStrategy":"type-guard","validationCode":"// Before using a token, assert the audience claim type:\nObject aud = claims.get(\"aud\");\nboolean formatOk = aud instanceof String || aud instanceof java.util.Collection<?>;","typeGuard":"static boolean isSupportedAudienceFormat(Object claimValue) {\n    return claimValue instanceof String\n        || (claimValue instanceof Collection<?> c && c.stream().allMatch(String.class::isInstance));\n}","tryCatchPattern":"try {\n    String role = authProvider.authenticate(authData);\n} catch (AuthenticationException e) {\n    if (e.getMessage().startsWith(\"Audiences in token is not in expected format\")) {\n        throw new IllegalStateException(\"Token issuer emits audience claim with unsupported JSON type; fix the issuer\", e);\n    }\n}","preventionTips":["Use a standard JWT library (jjwt, jose4j) on the issuer side so aud is emitted as string or array of strings.","Never hand-roll the token payload with non-string audience values.","Add an issuer-side unit test decoding the produced token and asserting the aud claim type.","Ensure the configured audience claim name doesn't collide with another custom claim of a different type."],"tags":["jwt","authentication","audience","malformed-token"],"backgroundTag":"jwt-claim-format-invalid","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"}