apereo/cas · warning

No client IP or user-agent was provided. Skipping adaptive…

Error message

No client IP or user-agent was provided. Skipping adaptive authentication policy...

What it means

DefaultAdaptiveAuthenticationPolicy.isAuthenticationRequestAllowed skips all adaptive checks (IP rejection, user-agent rejection, geo/country rejection) and returns true when ClientInfoHolder has no client info or the user-agent string is blank. Adaptive policy is effectively bypassed for that request.

Solutions

  1. Ensure the client info filter is registered and ordered first so ClientInfoHolder is populated.
  2. Configure the reverse proxy to forward X-Forwarded-For and User-Agent headers.
  3. Verify cas.authn.adaptive.policy configuration exists and the request carries a User-Agent.
  4. If bypass is unacceptable, fail closed by wrapping the policy and rejecting requests with missing client info.

Example fix

// before: filter missing, adaptive policy silently bypassed
// after: register the filter
@Bean
public FilterRegistrationBean<CasClientInfoHttpRequestFilter> clientInfoFilter(
        final CasClientInfoHttpRequestFilter filter) {
    val reg = new FilterRegistrationBean<>(filter);
    reg.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return reg;
}
Defensive patterns

Strategy: fallback

Validate before calling

if (ClientInfoHolder.getClientInfo() == null || StringUtils.isBlank(request.getHeader("User-Agent"))) {
    LOGGER.warn("Client info/user-agent missing — adaptive policy will be skipped");
}

Prevention

When it happens

Trigger: ClientInfoHolder.getClientInfo() returns null (no ClientInfoFilter/filter servlet not applied, e.g. non-web or mis-ordered filter chain) or the userAgent parameter is blank when the webflow action invokes the policy.

Common situations: Reverse proxy not forwarding X-Forwarded-For so client info is empty; CasClientInfoHttpRequestFilter removed/ordered out; internal calls to the flow (e.g. REST/scripted) without headers; tests invoking the policy directly.

Understand the failure class

Related errors


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

Appendix: source

Thrown at core/cas-server-core-authentication-api/src/main/java/org/apereo/cas/authentication/adaptive/DefaultAdaptiveAuthenticationPolicy.java:38

 * @author Misagh Moayyed
 * @since 5.1.0
 */
@Slf4j
@RequiredArgsConstructor
public class DefaultAdaptiveAuthenticationPolicy implements AdaptiveAuthenticationPolicy {

    private final GeoLocationService geoLocationService;

    private final IPAddressIntelligenceService ipAddressIntelligenceService;

    private final AdaptiveAuthenticationProperties adaptiveAuthenticationProperties;

    @Override
    public boolean isAuthenticationRequestAllowed(final RequestContext requestContext, final String userAgent,
                                                  final GeoLocationRequest location) throws Throwable {
        val clientInfo = ClientInfoHolder.getClientInfo();
        if (clientInfo == null || StringUtils.isBlank(userAgent)) {
            LOGGER.warn("No client IP or user-agent was provided. Skipping adaptive authentication policy...");
            return true;
        }
        val clientIp = clientInfo.getClientIpAddress();
        if (isIpAddressRejected(requestContext, clientIp)) {
            LOGGER.warn("Client IP [{}] is rejected for authentication", clientIp);
            return false;
        }

        if (isUserAgentRejected(userAgent)) {
            LOGGER.warn("User agent [{}] is rejected for authentication", userAgent);
            return false;
        }
        LOGGER.debug("User agent [{}] is authorized to proceed", userAgent);
        if (this.geoLocationService != null && location != null && StringUtils.isNotBlank(clientIp)
            && StringUtils.isNotBlank(this.adaptiveAuthenticationProperties.getPolicy().getRejectCountries())) {
            val loc = this.geoLocationService.locate(clientIp, location);
            if (loc != null) {
                LOGGER.debug("Determined geolocation for [{}] to be [{}]", clientIp, loc);

View on GitHub (pinned to e7288fc434)