apereo/cas · warning · InvalidCookieException

Invalid cookie Required remote address does not match

Error message

Invalid cookie %s Required remote address %s does not match %s

What it means

CAS binds cookies to the client's IP or geolocated location when created. On validation, if geoLocateClientSession is enabled and the stored location does not match the current resolved location, the cookie is rejected with InvalidCookieException. This prevents cookie theft/replay from a different client.

Solutions

  1. Disable geolocation session binding (set the relevant cookie geolocation property to false) if clients frequently change IPs
  2. Ensure the same geo-IP database version/config is deployed to every CAS node
  3. Investigate proxy/CDN headers (X-Forwarded-For) handling so client IP resolution is stable
  4. If IP changes are legitimate, use allowedIpAddressesPattern to whitelist the client's range instead of exact matching

Example fix

// before
// cas.tgc.pinnable-session-cookie.geo-locate-client-session=true
// after
// cas.tgc.pinnable-session-cookie.geo-locate-client-session=false
Defensive patterns

Strategy: validation

Validate before calling

String storedIp = /* part 2 of compound cookie */;
String currentIp = ClientInfoHolder.getClientInfo().getClientIpAddress();
if (!storedIp.equals(currentIp)) { /* expect rejection unless allowedIpAddressesPattern covers it */ }

Type guard

boolean ipBindingIntact(RegisteredServiceCookieProperties p, String stored, String current) {
    return stored.equals(current) || RegexUtils.find(p.getAllowedIpAddressesPattern(), current);
}

Try / catch

try { obtainCookieValue(...); } catch (InvalidCookieException e) {
    LOGGER.warn("Cookie IP binding failed; forcing fresh login");
}

Prevention

When it happens

Trigger: cas.tgc GeoLocateClientSession enabled (or cookieProperties.isGeoLocateClientSession() true) and getClientGeoLocation(clientInfo) resolves a different location/IP than the one embedded in the cookie — client IP changed (mobile network, VPN, NAT pools), geo-IP database returns different results across nodes, or cookie replayed from another machine.

Common situations: User on roaming/mobile connection whose IP changed between requests; MaxMind/geoIP database versions differ across cluster nodes so the same IP geolocates differently; corporate proxy rotates egress IPs; developer tests across localhost vs actual IP.

Related errors


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

Appendix: source

Thrown at core/cas-server-core-cookie-api/src/main/java/org/apereo/cas/web/support/mgmr/DefaultCasCookieValueManager.java:127

        if (Stream.of(cookieValue, cookieClientLocationOrIp, cookieUserAgent).anyMatch(StringUtils::isBlank)) {
            throw new InvalidCookieException("Invalid cookie %s. Required fields are empty".formatted(cookieProperties.getName()));
        }

        val clientInfo = ClientInfoHolder.getClientInfo();
        if (clientInfo == null) {
            val message = "Unable to match required remote address %s because client ip at time of cookie creation is unknown for cookie %s"
                .formatted(cookieProperties.getName(), cookieClientLocationOrIp);
            LOGGER.warn(message);
            throw new InvalidCookieException(message);
        }

        if (cookieProperties.isGeoLocateClientSession()) {
            val clientLocationOrIp = getClientGeoLocation(clientInfo);
            if (!cookieClientLocationOrIp.equals(clientLocationOrIp)) {
                val message = "Invalid cookie %s Required remote address %s does not match %s"
                    .formatted(cookieProperties.getName(), cookieClientLocationOrIp, clientLocationOrIp);
                LOGGER.warn(message);
                throw new InvalidCookieException(message);
            }
        } else {
            val clientIpAddress = clientInfo.getClientIpAddress();
            if (!cookieClientLocationOrIp.equals(clientIpAddress)) {
                if (StringUtils.isBlank(cookieProperties.getAllowedIpAddressesPattern())
                    || !RegexUtils.find(cookieProperties.getAllowedIpAddressesPattern(), clientIpAddress)) {
                    val message = "Invalid cookie %s. Required remote address %s does not match %s"
                        .formatted(cookieProperties.getName(), cookieClientLocationOrIp, clientIpAddress);
                    LOGGER.warn(message);
                    throw new InvalidCookieException(message);
                }
                LOGGER.debug("Required remote address [{}] does not match [{}], but it's authorized to proceed",
                    cookieClientLocationOrIp, clientIpAddress);
            }
        }

        val agent = HttpRequestUtils.getHttpServletRequestUserAgent(request);

View on GitHub (pinned to e7288fc434)