apereo/cas · warning

Unable to successfully fetch JWKS resource from

Error message

Unable to successfully fetch JWKS resource from [{}]

What it means

This warning is logged by OidcRestfulJsonWebKeystoreGeneratorService.generate when the HTTP GET to the configured cas.authn.oidc.jwks.rest.url fails: HttpUtils.execute returns null (connection failure, timeout, exhausted retries) or the response status is not 2xx. The method returns null, meaning no JWKS resource could be fetched from the REST endpoint.

Solutions

  1. Verify cas.authn.oidc.jwks.rest.url is correct and the endpoint is reachable from the CAS server (curl the URL and expect a 2xx JWKS body)
  2. Check/fix the basic auth username and password configured for the REST JWKS endpoint
  3. Inspect the endpoint's server logs for the failing status (404/401/500) and fix the server-side cause
  4. If the endpoint is intermittently unavailable, raise maximumRetryAttempts or add a fallback JWKS file configuration

Example fix

# before: wrong credentials/URL
cas.authn.oidc.jwks.rest.url=https://jwks.internal.example.com/wrong-path
cas.authn.oidc.jwks.rest.basic-auth-username=stale-user
# after
cas.authn.oidc.jwks.rest.url=https://jwks.internal.example.com/jwks
cas.authn.oidc.jwks.rest.basic-auth-username=svc-cas
cas.authn.oidc.jwks.rest.basic-auth-password=<current-secret>
Defensive patterns

Strategy: retry

Validate before calling

// Before configuring the REST JWKS source, verify it serves a 2xx JWKS
HttpRequest request = HttpRequest.newBuilder(URI.create(restUrl)).GET().build();
HttpResponse<String> resp = client.send(request, HttpResponse.BodyHandlers.ofString());
if (resp.statusCode() / 100 != 2) {
    throw new IllegalStateException("JWKS REST endpoint " + restUrl
        + " returned HTTP " + resp.statusCode());
}

Type guard

static boolean isReachableJwksEndpoint(String url) {
    try {
        var exec = HttpExecutionRequest.builder().method(HttpMethod.GET).url(url).build();
        var response = HttpUtils.execute(exec);
        return response != null && HttpStatus.valueOf(response.getCode()).is2xxSuccessful();
    } catch (Exception e) {
        return false;
    }
}

Try / catch

try {
    Optional<Resource> jwks = generatorService.find();
    if (jwks.isEmpty()) {
        LOG.warn("REST JWKS source unavailable; falling back to file-based JWKS");
        return fileBasedJwksResource();
    }
    return jwks;
} catch (Exception e) {
    LOG.error("JWKS fetch from REST failed", e);
    return Optional.empty();
}

Prevention

When it happens

Trigger: generate()/find() is invoked while the REST JWKS endpoint is down or unreachable, returns 401/403 due to wrong basic-auth credentials (basicAuthUsername/basicAuthPassword), returns 404/500, or the configured URL is wrong; retries configured via maximumRetryAttempts are exhausted.

Common situations: External JWKS store service not yet started when CAS boots; misconfigured cas.authn.oidc.jwks.rest.url (typo, wrong host/port, TLS certificate not trusted); incorrect basic auth credentials; firewall/proxy blocking the request; endpoint returning 401 after a credential rotation.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-oidc-core-api/src/main/java/org/apereo/cas/oidc/jwks/generator/OidcRestfulJsonWebKeystoreGeneratorService.java:56

    @Override
    public Optional<Resource> find() throws Exception {
        return Optional.ofNullable(generate());
    }

    @Override
    public @Nullable Resource generate() throws Exception {
        val rest = oidcProperties.getJwks().getRest();
        val exec = HttpExecutionRequest.builder()
            .basicAuthPassword(rest.getBasicAuthPassword())
            .basicAuthUsername(rest.getBasicAuthUsername())
            .maximumRetryAttempts(rest.getMaximumRetryAttempts())
            .method(HttpMethod.GET)
            .headers(rest.getHeaders())
            .url(rest.getUrl())
            .build();
        val response = HttpUtils.execute(exec);
        if (response == null || !HttpStatus.valueOf(response.getCode()).is2xxSuccessful()) {
            LOGGER.warn("Unable to successfully fetch JWKS resource from [{}]", rest.getUrl());
            return null;
        }

        try (val content = ((HttpEntityContainer) response).getEntity().getContent()) {
            val result = IOUtils.toString(content, StandardCharsets.UTF_8);
            LOGGER.debug("Received payload result from [{}] as [{}]", rest.getUrl(), result);
            return new ByteArrayResource(result.getBytes(StandardCharsets.UTF_8), "OIDC JWKS");
        }
    }

    @Override
    public JsonWebKeySet store(final JsonWebKeySet jsonWebKeySet) {
        val rest = oidcProperties.getJwks().getRest();
        val headers = CollectionUtils.<String, String>wrap(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        headers.putAll(rest.getHeaders());
        val exec = HttpExecutionRequest.builder()
            .basicAuthPassword(rest.getBasicAuthPassword())
            .basicAuthUsername(rest.getBasicAuthUsername())

View on GitHub (pinned to e7288fc434)