{"record":{"id":"60281078ddaa72f7","repo":"apache/pulsar","slug":"error-retrieving-public-key","errorCode":"ERROR_RETRIEVING_PUBLIC_KEY","errorMessage":"No JWK found for Key ID ","messagePattern":"No JWK found for Key ID ","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-broker-auth-oidc/src/main/java/org/apache/pulsar/broker/authentication/oidc/JwksCache.java","lineNumber":223,"sourceCode":"                }\n            });\n        } catch (ApiException e) {\n            authenticationProvider.incrementFailureMetric(ERROR_RETRIEVING_PUBLIC_KEY);\n            future.completeExceptionally(\n                    new AuthenticationException(\"Failed to retrieve public key from Kubernetes API server: \"\n                            + e.getMessage()));\n        }\n        return future;\n    }\n\n    private Jwk getJwkForKID(Optional<String> maybeJwksUri, List<Jwk> jwks, String keyId) {\n        for (Jwk jwk : jwks) {\n            if (jwk.getId().equals(keyId)) {\n                return jwk;\n            }\n        }\n        authenticationProvider.incrementFailureMetric(ERROR_RETRIEVING_PUBLIC_KEY);\n        throw new IllegalArgumentException(\"No JWK found for Key ID \" + keyId);\n    }\n\n    /**\n     * The JWK Set is stored in the \"keys\" key see https://www.rfc-editor.org/rfc/rfc7517#section-5.1.\n     *\n     * @param jwksUri - the URI used to retrieve the JWKS\n     * @param jwks - the JWKS to convert\n     * @return a list of {@link Jwk}\n     */\n    private List<Jwk> convertToJwks(String jwksUri, Map<String, Object> jwks) throws AuthenticationException {\n        try {\n            @SuppressWarnings(\"unchecked\")\n            List<Map<String, Object>> jwkList = (List<Map<String, Object>>) jwks.get(\"keys\");\n            final List<Jwk> result = new ArrayList<>();\n            for (Map<String, Object> jwk : jwkList) {\n                result.add(Jwk.fromValues(jwk));\n            }\n            return result;","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker-auth-oidc/src/main/java/org/apache/pulsar/broker/authentication/oidc/JwksCache.java#L205-L241","documentation":"JwksCache.getJwkForKID() scans the cached JWK Set (fetched from the issuer's jwks_uri or the Kubernetes API server) for a JWK whose kid matches the JWT header's key ID. When none matches it increments the ERROR_RETRIEVING_PUBLIC_KEY failure metric and throws IllegalArgumentException(\"No JWK found for Key ID <kid>\"). getJwkAndMaybeReload() first retries with a fresh JWKS fetch (rate-limited by keyIdCacheMissRefreshSeconds) before propagating the failure, so this usually means the key is genuinely absent even after a reload.","triggerScenarios":"A JWT whose kid header does not match any key published in the issuer's JWKS; the JWKS was cached before the IdP rotated keys and the cache-miss refresh window (keyIdCacheMissRefreshSeconds) has not elapsed; the broker is pointed at the wrong issuer/jwks_uri so its key set never contains the token's kid; the token's kid header is missing or empty; in Kubernetes mode, the service-account token's kid is not in the keyset served by the API server.","commonSituations":"IdP key rotation (e.g. Auth0/Keycloak rotating signing keys) faster than the configured refresh interval; misconfigured afdGw / configurationError where authenticationProvider OpenID is given the wrong issuer URL; multiple environments (staging vs prod IdP) mixed up so tokens from one are validated against the other's JWKS; tokens signed with a key the IdP has since unpublished.","solutions":["Confirm the token's kid exists in the issuer's JWKS: decode the JWT header (e.g. jq -R 'split(\".\")[0] | @base64d') and compare with the keys at the configured jwks_uri.","Verify the configured issuer URL points to the same IdP/environment that issued the token.","Tune oidcCacheMissRefreshSeconds (KEY_ID_CACHE_MISS_REFRESH_SECONDS) down so unknown kids trigger a JWKS reload sooner after key rotation.","If rotation lag is the cause, wait for cache expiry/refresh or restart the broker to force a fresh JWKS fetch, then retry authentication.","Ensure the IdP still publishes the signing key used for the token (it may have removed old keys after rotation)."],"exampleFix":"// broker.conf\n// before (slow to pick up rotated keys)\noidcCacheMissRefreshSeconds=3600\n// after (refresh JWKS quickly on unknown kid)\noidcCacheMissRefreshSeconds=60","handlingStrategy":"retry","validationCode":"// Before configuring, confirm the token's kid is published:\n// kid=$(echo $JWT | cut -d. -f1 | base64 -d 2>/dev/null | jq -r .kid)\n// curl -s $JWKS_URI | jq -e --arg kid \"$kid\" '.keys[] | select(.kid == $kid)'","typeGuard":null,"tryCatchPattern":"future.exceptionally(ex -> {\n    if (ex.getCause() instanceof IllegalArgumentException\n            && ex.getCause().getMessage().startsWith(\"No JWK found for Key ID\")) {\n        // force JWKS refresh / re-fetch token with a current kid, then retry once\n    }\n    return null;\n});","preventionTips":["Set oidcCacheMissRefreshSeconds low enough to pick up IdP key rotation quickly.","Keep cache expiration/refresh settings shorter than the IdP's key rotation cadence.","Decode failing tokens and cross-check their kid against the live JWKS endpoint.","Ensure the issuer URL points at the same IdP environment that issued the tokens."],"tags":["jwt","jwks","oidc","key-rotation","authentication"],"backgroundTag":"jwk-key-id-not-found","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"}