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

  1. 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
  2. Ensure the client actually sends the 'Authorization: Bearer <jwt>' header (check proxies/LBs that may strip it)
  3. 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

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

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/72ba75fa045ed3d2. Report an issue: GitHub.