apereo/cas · warning

Unable to locate JSON web key for

Error message

Unable to locate JSON web key for [{}]

What it means

This warning is logged by OidcJsonWebKeyStoreUtils.getJsonWebKeyFromJsonWebKeySet when, after filtering a parsed JWKS by the requested key id (kid) and/or key usage, no key with a non-null key material remains. The method returns Optional.empty(), so callers silently receive no key set. It means the keystore exists and parses, but does not contain a key matching the service's configured jwksKeyId/usage.

Solutions

  1. Compare the jwksKeyId set on the OidcRegisteredService with the kid values actually present in the JWKS JSON and correct the mismatch
  2. Regenerate or edit the JWKS so it contains a PublicJsonWebKey with the expected kid, usage, and key material
  3. If no specific kid is needed, clear jwksKeyId on the service so any valid key matches
  4. Ensure the resource at the service's jwks URL/path is valid JWKS JSON; if it is a plain secret string, verify the derived kid/usage expectations

Example fix

// before: service registry entry
"@class":"org.apereo.cas.services.OidcRegisteredService",
"jwks":"https://rp.example.com/jwks.json",
"jwksKeyId":"old-rotated-kid"
// after
"@class":"org.apereo.cas.services.OidcRegisteredService",
"jwks":"https://rp.example.com/jwks.json",
"jwksKeyId":"current-kid-2026"
Defensive patterns

Strategy: validation

Validate before calling

// Before relying on a service's JWKS, parse and check a matching key exists
JsonWebKeySet jwks = new JsonWebKeySet(jwksJson);
boolean hasUsableKey = jwks.getJsonWebKeys().stream()
    .anyMatch(k -> k instanceof PublicJsonWebKey
        && (serviceJwksKeyId == null || serviceJwksKeyId.equalsIgnoreCase(k.getKeyId()))
        && k.getKey() != null);
if (!hasUsableKey) {
    throw new IllegalStateException("JWKS has no key matching kid=" + serviceJwksKeyId);
}

Type guard

static boolean hasUsableKeyFor(JsonWebKeySet jwks, String kid) {
    return jwks.getJsonWebKeys().stream().anyMatch(k ->
        k instanceof PublicJsonWebKey
            && (kid == null || kid.equalsIgnoreCase(k.getKeyId()))
            && k.getKey() != null);
}

Try / catch

try {
    return OidcJsonWebKeyStoreUtils.getJsonWebKeySet(service, resourceLoader, usage);
} catch (Exception e) {
    LOG.error("JWKS lookup failed for service {}", service.getClientId(), e);
    return Optional.empty();
}

Prevention

When it happens

Trigger: Calling getJsonWebKeySet for an OidcRegisteredService whose jwksKeyId does not match any kid in the JWKS file/inline JSON, or whose keys lack the requested usage (use=sign/enc), or whose matched keys have null key material (e.g. only private or only public parts available). Also triggered when the service's jwks value is not valid JSON and is instead treated as a raw AES secret with a mismatching kid/usage.

Common situations: Rotating JWKS on the service side but forgetting to update cas.serviceRegistry OidcRegisteredService.jwksKeyId; copying a public-only JWKS while tokens require the private key; a key with a missing or wrong 'use' claim; typo in the kid string in the service registry entry.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/06235c5714af6e25. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-oidc-core-api/src/main/java/org/apereo/cas/oidc/jwks/OidcJsonWebKeyStoreUtils.java:95

     * @param jwks         the jwks
     * @param requestedKey the kid
     * @param usage        the usage
     * @return the json web key from jwks
     */
    public static Optional<JsonWebKeySet> getJsonWebKeyFromJsonWebKeySet(
        final JsonWebKeySet jwks, final Optional<String> requestedKey, final Optional<OidcJsonWebKeyUsage> usage) {
        if (jwks.getJsonWebKeys().isEmpty()) {
            LOGGER.warn("No JSON web keys are available in the keystore");
            return Optional.empty();
        }

        val keyResult = getJsonWebKeyByKeyId(jwks, requestedKey, usage)
            .getJsonWebKeys()
            .stream()
            .filter(key -> key.getKey() != null)
            .collect(Collectors.toList());
        if (keyResult.isEmpty()) {
            LOGGER.warn("Unable to locate JSON web key for [{}]", requestedKey.map(Object::toString));
            return Optional.empty();
        }
        return Optional.of(new JsonWebKeySet(keyResult));
    }

    private static List<JsonWebKey> filterJsonWebKeySetKeysBy(final JsonWebKeySet jwks,
                                                              final Optional<String> keyIdRequest,
                                                              final Optional<OidcJsonWebKeyUsage> usage) {

        var filter = (Predicate<JsonWebKey>) PublicJsonWebKey.class::isInstance;
        if (keyIdRequest.isPresent()) {
            filter = filter.and(jsonWebKey -> Strings.CI.equals(jsonWebKey.getKeyId(), keyIdRequest.get()));
        }
        if (usage.isPresent()) {
            filter = filter.and(jsonWebKey -> usage.get().is(jsonWebKey));
        }
        return jwks.getJsonWebKeys()
            .stream()

View on GitHub (pinned to e7288fc434)