quarkusio/quarkus · error · org.jose4j.keys.resolvers.UnresolvableKeyException

JWK with kid '%s' is not available

Error message

JWK with kid '%s' is not available

What it means

Thrown by OidcProvider's JsonWebKeyResolver.resolveKey when a signed JWT carries a 'kid' (key ID) header but no matching JWK can be found in the tenant's JSON Web Key Set. Per RFC 7515 semantics, once the token declares a 'kid' the verifier must use exactly that key; the provider refuses to fall back to other keys or thumbprint matching. This is a fail-fast safeguard against tokens signed with unknown or rotated-out keys.

Source

Thrown at extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java:554

            if (oidcConfig.certificateChain().trustStoreFile().isPresent()) {
                chainResolverFallback = new CertChainPublicKeyResolver(oidcConfig);
            } else {
                chainResolverFallback = null;
            }
        }

        @Override
        public Key resolveKey(JsonWebSignature jws, List<JsonWebStructure> nestingContext)
                throws UnresolvableKeyException {
            Key key = null;

            // Try 'kid' first
            String kid = jws.getKeyIdHeaderValue();
            if (kid != null) {
                key = getKeyWithId(kid);
                if (key == null) {
                    // if `kid` was set then the key must exist
                    throw new UnresolvableKeyException(String.format("JWK with kid '%s' is not available", kid));
                }
            }

            String thumbprint = null;
            if (key == null) {
                thumbprint = jws.getHeader(HeaderParameterNames.X509_CERTIFICATE_SHA256_THUMBPRINT);
                if (thumbprint != null) {
                    key = getKeyWithS256Thumbprint(thumbprint);
                    if (key == null) {
                        // if only `x5tS256` was set then the key must exist
                        throw new UnresolvableKeyException(
                                String.format("JWK with the SHA256 certificate thumbprint '%s' is not available", thumbprint));
                    }
                }
            }

            if (key == null) {
                thumbprint = jws.getHeader(HeaderParameterNames.X509_CERTIFICATE_THUMBPRINT);

View on GitHub (pinned to e1c734241f)

Solutions

  1. Force a JWKS refresh (restart the app or invalidate the cached key set) so keys rotated at the provider are re-fetched
  2. Verify the token was issued by the tenant/realm you configured — check quarkus.oidc.auth-server-url / jwks-path matches the token issuer
  3. Compare the token's 'kid' header (decode the JWT at jwt.io or similar) against the keys published at the JWKS endpoint
  4. If the provider doesn't publish 'kid' but the token has one, remove the mismatch or configure the signing key locally via quarkus.oidc.token.public-key / certificate

Example fix

// token kid 'key-2' but only 'key-1' published after rotation
// before: stale cached JWKS -> UnresolvableKeyException
// after: make refresh interval aggressive and align tenant config
quarkus.oidc.token.audience=...
Defensive patterns

Strategy: fallback

Validate before calling

// decode token header and check kid is present in fetched JWKS before verification
String kid = com.nimbusds.jose.JOSEHeader parse of token; // or decode base64url header JSON
Set<String> availableKids = fetchedJwks.getKeys().stream().map(JsonWebKey::getKeyId).collect(Collectors.toSet());
if (!availableKids.contains(kid)) { throw new IllegalStateException("kid " + kid + " missing from JWKS"); }

Type guard

boolean jwksHasKid(String jwksJson, String kid) {
    var set = io.quarkus.oidc.runtime.OidcUtils.parse JWKS if available;
    return set != null && set.getKeys().stream().anyMatch(k -> kid.equals(k.getKeyId()));
}

Try / catch

try {
    SecurityIdentity id = identityProvider.authenticate(...);
} catch (AuthenticationFailedException e) {
    if (e.getCause() instanceof UnresolvableKeyException) {
        // request JWKS refresh / retry verification once
    }
}

Prevention

When it happens

Trigger: Verifying an ID token/access token whose JOSE header contains 'kid' while getKeyWithId(kid) returns null — i.e. the JWKS fetched from the OIDC provider (or the local quarkus.oidc.* jwks/key config) has no key with that 'kid'.

Common situations: The OIDC provider rotated signing keys but the app still holds a cached/stale JWKS; a typo'd or misconfigured jwks.path / jwks.resolve-early setting; testing tokens issued by a different realm/tenant than the one configured; custom or third-party identity providers whose 'kid' values don't match what's published in the JWKS endpoint.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/a06d640d8088074b. Report an issue: GitHub.