apache/pulsar · error · AuthenticationException
ERROR_DECODING_JWT
ERROR_DECODING_JWT
Error message
Invalid token: cannot be null
What it means
decodeJWT in AuthenticationProviderOpenID requires a non-null token string. If the raw JWT passed in from authenticateToken is null, it throws AuthenticationException(ERROR_DECODING_JWT) before attempting any decoding. This is a guard against absent credentials rather than a malformed-token failure.
Source
Thrown at pulsar-broker-auth-oidc/src/main/java/org/apache/pulsar/broker/authentication/oidc/AuthenticationProviderOpenID.java:291
log.error().exception(e).log("Exception while retrieving role from JWT");
return null;
}
}
/**
* Convert a JWT string into a {@link DecodedJWT}
* The benefit of using this method is that it utilizes the already instantiated {@link JWT} parser.
* WARNING: this method does not verify the authenticity of the token. It only decodes it.
*
* @param token - string JWT to be decoded
* @return a decoded JWT
* @throws AuthenticationException if the token string is null or if any part of the token contains
* an invalid jwt or JSON format of each of the jwt parts.
*/
DecodedJWT decodeJWT(String token) throws AuthenticationException {
if (token == null) {
incrementFailureMetric(AuthenticationExceptionCode.ERROR_DECODING_JWT);
throw new AuthenticationException("Invalid token: cannot be null");
}
try {
return jwtLibrary.decodeJwt(token);
} catch (JWTDecodeException e) {
incrementFailureMetric(AuthenticationExceptionCode.ERROR_DECODING_JWT);
throw new AuthenticationException("Unable to decode JWT: " + e.getMessage());
}
}
/**
* Authenticate the parameterized JWT.
*
* @param token - a nonnull JWT to authenticate
* @return a fully authenticated JWT, or AuthenticationException if the JWT is proven to be invalid in any way
*/
private CompletableFuture<DecodedJWT> authenticateToken(String token) {
if (token == null) {
incrementFailureMetric(AuthenticationExceptionCode.ERROR_DECODING_JWT);View on GitHub (pinned to 820761864e)
Solutions
- Configure the client with a valid OpenID Connect token (auth plugin AuthToken and the JWT in authParams) so the broker receives a non-null credential
- Ensure the client actually sends the 'Authorization: Bearer <jwt>' header (check proxies/LBs that may strip it)
- On the broker side, reject or handle null credentials earlier (e.g., check authenticationData.hasDataFromPeer() before calling authenticateToken) with a clearer error
Example fix
// before
decodedJwt = decodeJWT(authenticationData.getCredential());
// after
String token = authenticationData.getCredential();
if (token == null || token.isEmpty()) {
throw new AuthenticationException("No credential provided by client");
}
decodedJwt = decodeJWT(token); Defensive patterns
Strategy: type-guard
Validate before calling
String cred = authenticationData.getCredential();
if (cred == null || cred.isEmpty()) {
throw new AuthenticationException("Client supplied no token");
} Type guard
boolean hasToken(Object authData) {
return authData instanceof AuthenticationDataSource ads
&& ads.hasDataFromPeer()
&& ads.getCredential() != null
&& !ads.getCredential().isEmpty();
} Try / catch
try {
return decodeJWT(token);
} catch (AuthenticationException e) {
if (e.getMessage().contains("cannot be null")) {
log.debug("No JWT supplied by peer; rejecting as unauthenticated");
return null;
}
throw e;
} Prevention
- Configure the client auth plugin (AuthenticationToken) with a real JWT before connecting
- Check proxies/load balancers preserve the Authorization header
- Reject null credentials early with a clearer message before deeper parsing
- Enable client-side auth logging to confirm the header is actually sent
When it happens
Trigger: authenticateToken() is invoked with a null token — typically when the client sent no Authorization header/credential at all (getCredential() returned null) and the value was passed straight to decodeJWT.
Common situations: Client connects without any authentication data configured (missing authParams/auth plugin on the client); a proxy strips the Authorization header; null passed in unit tests or code paths that don't first check for anonymous/unauthenticated connections when the broker allows none.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/72ba75fa045ed3d2.
Report an issue: GitHub.