Repository: keycloak/keycloak
Pull request #37797: fix: adds back reporting of non-ip client addresses
Author: login: shawkins
Labels: name: flaky-test
Created: 2025-03-04T00:44:52Z | Merged: 2025-03-27T19:33:20Z

closes: #36843

For an initial proposal:
- where we are simply capturing what is called the client ip (which was most places like DeviceRepresentation, SessionRepresentation, etc.) those have been changed to use the remote host instead. I can update the javadocs of the relevant getIpAddress or similar methods to indicate that they aren't always ip addresses if that seems warranted. As mentioned in the issue the only alternative is to put in a more obviously invalid form of the host - such as \:host - so that any attempt to use a basic InetAddress forName will fail without a dns lookup.
- in places where we were differentiating, no changes were made, but I did update the policy evaluation api docs
- the additional places where we were might resolve an InetAddress, they were updated to better handle the null case to avoid any processing - see the TrustedHost logic
- in remote apis where we were sending a client ip, those will now do a null check - this was done for the google identity provider and registration recapta

@mabartos @vmuzikar @pedroigor WDYT?

<!---
Please read https://github.com/keycloak/keycloak/blob/main/CONTRIBUTING.md and follow these guidelines when contributing to Keycloak
-->



Diff:
diff --git a/common/src/main/java/org/keycloak/common/ClientConnection.java b/common/src/main/java/org/keycloak/common/ClientConnection.java
index 3e6c124da8b6..909f14b86ee8 100755
--- a/common/src/main/java/org/keycloak/common/ClientConnection.java
+++ b/common/src/main/java/org/keycloak/common/ClientConnection.java
@@ -26,9 +26,12 @@
 public interface ClientConnection {
 
     /**
-     * @return the address as a string if it is available, otherwise null 
+     * @return the IP address as a string if it is available, otherwise null
      */
     String getRemoteAddr();
+    /**
+     * @return the remote host, which will be an IP address or whatever is provided via proxy headers
+     */
     String getRemoteHost();
     int getRemotePort();
 
diff --git a/core/src/main/java/org/keycloak/representations/account/DeviceRepresentation.java b/core/src/main/java/org/keycloak/representations/account/DeviceRepresentation.java
index 9ec8b370d496..903cdba7205e 100644
--- a/core/src/main/java/org/keycloak/representations/account/DeviceRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/account/DeviceRepresentation.java
@@ -57,6 +57,11 @@ public void setId(String id) {
         this.id = id;
     }
 
+    /**
+     * Note: will not be an address when a proxy does not provide a valid one
+     *
+     * @return the ip address
+     */
     public String getIpAddress() {
         return ipAddress;
     }
diff --git a/core/src/main/java/org/keycloak/representations/account/SessionRepresentation.java b/core/src/main/java/org/keycloak/representations/account/SessionRepresentation.java
index 996c85226c74..4b86319d70f6 100644
--- a/core/src/main/java/org/keycloak/representations/account/SessionRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/account/SessionRepresentation.java
@@ -24,6 +24,11 @@ public void setId(String id) {
         this.id = id;
     }
 
+    /**
+     * Note: will not be an address when a proxy does not provide a valid one
+     *
+     * @return the ip address
+     */
     public String getIpAddress() {
         return ipAddress;
     }
diff --git a/core/src/main/java/org/keycloak/representations/idm/AuthDetailsRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/AuthDetailsRepresentation.java
index 746aa718de8d..b65f5aa05a3e 100644
--- a/core/src/main/java/org/keycloak/representations/idm/AuthDetailsRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/AuthDetailsRepresentation.java
@@ -52,6 +52,11 @@ public void setUserId(String userId) {
         this.userId = userId;
     }
 
+    /**
+     * Note: will not be an address when a proxy does not provide a valid one
+     *
+     * @return the ip address
+     */
     public String getIpAddress() {
         return ipAddress;
     }
diff --git a/core/src/main/java/org/keycloak/representations/idm/EventRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/EventRepresentation.java
index 2ecf06240bad..b47337ba03e0 100644
--- a/core/src/main/java/org/keycloak/representations/idm/EventRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/EventRepresentation.java
@@ -91,6 +91,11 @@ public void setSessionId(String sessionId) {
         this.sessionId = sessionId;
     }
 
+    /**
+     * Note: will not be an address when a proxy does not provide a valid one
+     *
+     * @return the ip address
+     */
     public String getIpAddress() {
         return ipAddress;
     }
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserSessionRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserSessionRepresentation.java
index 8bada557793a..a9f8679bd9fc 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserSessionRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserSessionRepresentation.java
@@ -59,6 +59,11 @@ public void setUserId(String userId) {
         this.userId = userId;
     }
 
+    /**
+     * Note: will not be an address when a proxy does not provide a valid one
+     *
+     * @return the ip address
+     */
     public String getIpAddress() {
         return ipAddress;
     }
diff --git a/docs/documentation/authorization_services/topics/policy-evaluation-api.adoc b/docs/documentation/authorization_services/topics/policy-evaluation-api.adoc
index 731907861005..cab4c47b99db 100644
--- a/docs/documentation/authorization_services/topics/policy-evaluation-api.adoc
+++ b/docs/documentation/authorization_services/topics/policy-evaluation-api.adoc
@@ -99,11 +99,11 @@ The `EvaluationContext` also gives you access to attributes related to both the
 | String. Format `MM/dd/yyyy hh:mm:ss`
 
 | kc.client.network.ip_address
-| IPv4 address of the client
+| IP address of the client, can be null if a valid IP is not provided.
 | String
 
 | kc.client.network.host
-| Client's host name
+| Client's host name, will be the IP address or whatever is provided by proxy headers
 | String
 
 | kc.client.id
diff --git a/server-spi-private/src/main/java/org/keycloak/events/Event.java b/server-spi-private/src/main/java/org/keycloak/events/Event.java
index b43f76a529cc..009482bada72 100644
--- a/server-spi-private/src/main/java/org/keycloak/events/Event.java
+++ b/server-spi-private/src/main/java/org/keycloak/events/Event.java
@@ -110,6 +110,11 @@ public void setSessionId(String sessionId) {
         this.sessionId = maxLength(sessionId, 255);
     }
 
+    /**
+     * Note: will not be an address when a proxy does not provide a valid one
+     *
+     * @return the ip address
+     */
     public String getIpAddress() {
         return ipAddress;
     }
@@ -134,6 +139,7 @@ public void setDetails(Map<String, String> details) {
         this.details = details;
     }
 
+    @Override
     public Event clone() {
         Event clone = new Event();
         clone.id = id;
diff --git a/server-spi-private/src/main/java/org/keycloak/events/EventBuilder.java b/server-spi-private/src/main/java/org/keycloak/events/EventBuilder.java
index a466ab84dc52..07e5663798e9 100755
--- a/server-spi-private/src/main/java/org/keycloak/events/EventBuilder.java
+++ b/server-spi-private/src/main/java/org/keycloak/events/EventBuilder.java
@@ -60,7 +60,7 @@ public class EventBuilder {
 
     public EventBuilder(RealmModel realm, KeycloakSession session, ClientConnection clientConnection) {
         this(realm, session);
-        ipAddress(clientConnection.getRemoteAddr());
+        ipAddress(clientConnection.getRemoteHost());
     }
 
     public EventBuilder(RealmModel realm, KeycloakSession session) {
@@ -236,6 +236,7 @@ public void error(String error) {
         send(this.storeImmediately == null ? true : this.storeImmediately);
     }
 
+    @Override
     public EventBuilder clone() {
         return new EventBuilder(session, store, listeners, realm, event.clone());
     }
diff --git a/server-spi-private/src/main/java/org/keycloak/events/admin/AuthQuery.java b/server-spi-private/src/main/java/org/keycloak/events/admin/AuthQuery.java
index 76613aef3b24..2a1001899c0d 100644
--- a/server-spi-private/src/main/java/org/keycloak/events/admin/AuthQuery.java
+++ b/server-spi-private/src/main/java/org/keycloak/events/admin/AuthQuery.java
@@ -54,6 +54,11 @@ public void setUserId(String userId) {
         this.userId = userId;
     }
 
+    /**
+     * Note: will not be an address when a proxy does not provide a valid one
+     *
+     * @return the ip address
+     */
     public String getIpAddress() {
         return ipAddress;
     }
diff --git a/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java b/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java
index 9693debbbe8e..934da3c2a5c8 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java
@@ -51,6 +51,11 @@ public interface UserSessionModel {
 
     String getLoginUsername();
 
+    /**
+     * Note: will not be an address when a proxy does not provide a valid one
+     *
+     * @return the ip address
+     */
     String getIpAddress();
 
     String getAuthMethod();
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 0ca85b123132..b77ddbe62c7c 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -1135,7 +1135,7 @@ public static ClientSessionContext attachSession(AuthenticationSessionModel auth
             if (userSession == null) {
                 UserSessionModel.SessionPersistenceState persistenceState = UserSessionModel.SessionPersistenceState.fromString(authSession.getClientNote(AuthenticationManager.USER_SESSION_PERSISTENT_STATE));
 
-                userSession = new UserSessionManager(session).createUserSession(authSession.getParentSession().getId(), realm, authSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), authSession.getProtocol()
+                userSession = new UserSessionManager(session).createUserSession(authSession.getParentSession().getId(), realm, authSession.getAuthenticatedUser(), username, connection.getRemoteHost(), authSession.getProtocol()
                         , remember, brokerSessionId, brokerUserId, persistenceState);
 
                 if (isLightweightUser(userSession.getUser())) {
@@ -1143,7 +1143,7 @@ public static ClientSessionContext attachSession(AuthenticationSessionModel auth
                     lua.setOwningUserSessionId(userSession.getId());
                 }
             } else if (userSession.getUser() == null || !AuthenticationManager.isSessionValid(realm, userSession)) {
-                userSession.restartSession(realm, authSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), authSession.getProtocol()
+                userSession.restartSession(realm, authSession.getAuthenticatedUser(), username, connection.getRemoteHost(), authSession.getProtocol()
                         , remember, brokerSessionId, brokerUserId);
             } else {
                 // We have existing userSession even if it wasn't attached to authenticator. Could happen if SSO authentication was ignored (eg. prompt=login) and in some other cases.
diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationRecaptcha.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationRecaptcha.java
index c9aff990847b..e44fa3a1bf31 100755
--- a/services/src/main/java/org/keycloak/authentication/forms/RegistrationRecaptcha.java
+++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationRecaptcha.java
@@ -80,7 +80,9 @@ protected boolean validate(ValidationContext context, String captcha, Map<String
         List<NameValuePair> formparams = new LinkedList<>();
         formparams.add(new BasicNameValuePair("secret", config.get(SECRET_KEY)));
         formparams.add(new BasicNameValuePair("response", captcha));
-        formparams.add(new BasicNameValuePair("remoteip", context.getConnection().getRemoteAddr()));
+        if (context.getConnection().getRemoteAddr() != null) {
+            formparams.add(new BasicNameValuePair("remoteip", context.getConnection().getRemoteAddr()));
+        }
 
         try {
             UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, StandardCharsets.UTF_8);
diff --git a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
index 08482ab57a84..faad3763587f 100644
--- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
+++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
@@ -314,7 +314,7 @@ private AuthorizationResponse createAuthorizationResponse(KeycloakIdentity ident
         if (accessToken.getSessionState() == null) {
             // Create temporary (request-scoped) transient session
             UserModel user = TokenManager.lookupUserFromStatelessToken(keycloakSession, realm, accessToken);
-            userSessionModel = new UserSessionManager(keycloakSession).createUserSession(KeycloakModelUtils.generateId(), realm, user, user.getUsername(), request.getClientConnection().getRemoteAddr(),
+            userSessionModel = new UserSessionManager(keycloakSession).createUserSession(KeycloakModelUtils.generateId(), realm, user, user.getUsername(), request.getClientConnection().getRemoteHost(),
                     ServiceAccountConstants.CLIENT_AUTH, false, null, null, UserSessionModel.SessionPersistenceState.TRANSIENT);
         } else {
             userSessionModel = sessions.getUserSession(realm, accessToken.getSessionState());
diff --git a/services/src/main/java/org/keycloak/device/DeviceRepresentationProviderImpl.java b/services/src/main/java/org/keycloak/device/DeviceRepresentationProviderImpl.java
index 02e8354196f2..b1d90ae654ff 100644
--- a/services/src/main/java/org/keycloak/device/DeviceRepresentationProviderImpl.java
+++ b/services/src/main/java/org/keycloak/device/DeviceRepresentationProviderImpl.java
@@ -85,7 +85,7 @@ public DeviceRepresentation deviceRepresentation() {
             }
 
             current.setOsVersion(osVersion);
-            current.setIpAddress(context.getConnection().getRemoteAddr());
+            current.setIpAddress(context.getConnection().getRemoteHost());
             current.setMobile(userAgent.toLowerCase().contains("mobile"));
 
             deviceRepresentation = current;
diff --git a/services/src/main/java/org/keycloak/email/freemarker/beans/AdminEventBean.java b/services/src/main/java/org/keycloak/email/freemarker/beans/AdminEventBean.java
index 6dae05d76024..cb21dabae8be 100644
--- a/services/src/main/java/org/keycloak/email/freemarker/beans/AdminEventBean.java
+++ b/services/src/main/java/org/keycloak/email/freemarker/beans/AdminEventBean.java
@@ -25,7 +25,7 @@
  * @author <a href="mailto:giriraj.sharma27@gmail.com">Giriraj Sharma</a>
  */
 public class AdminEventBean {
-    
+
     private AdminEvent adminEvent;
 
     public AdminEventBean(AdminEvent adminEvent) {
@@ -44,10 +44,15 @@ public String getClient() {
         return adminEvent.getAuthDetails().getClientId();
     }
 
+    /**
+     * Note: will not be an address when a proxy does not provide a valid one
+     *
+     * @return the ip address
+     */
     public String getIpAddress() {
         return adminEvent.getAuthDetails().getIpAddress();
     }
-    
+
     public String getResourcePath() {
         return adminEvent.getResourcePath();
     }
diff --git a/services/src/main/java/org/keycloak/email/freemarker/beans/EventBean.java b/services/src/main/java/org/keycloak/email/freemarker/beans/EventBean.java
index 4edb6e242037..a57581f34ecf 100644
--- a/services/src/main/java/org/keycloak/email/freemarker/beans/EventBean.java
+++ b/services/src/main/java/org/keycloak/email/freemarker/beans/EventBean.java
@@ -46,6 +46,11 @@ public String getClient() {
         return event.getClientId();
     }
 
+    /**
+     * Note: will not be an address when a proxy does not provide a valid one
+     *
+     * @return the ip address
+     */
     public String getIpAddress() {
         return event.getIpAddress();
     }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/grants/ClientCredentialsGrantType.java b/services/src/main/java/org/keycloak/protocol/oidc/grants/ClientCredentialsGrantType.java
index 53f877c3900f..458ef9c20e19 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/grants/ClientCredentialsGrantType.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/grants/ClientCredentialsGrantType.java
@@ -116,7 +116,7 @@ public Response process(Context context) {
         }
 
         UserSessionModel userSession = new UserSessionManager(session).createUserSession(authSession.getParentSession().getId(), realm, clientUser, clientUsername,
-                clientConnection.getRemoteAddr(), ServiceAccountConstants.CLIENT_AUTH, false, null, null, sessionPersistenceState);
+                clientConnection.getRemoteHost(), ServiceAccountConstants.CLIENT_AUTH, false, null, null, sessionPersistenceState);
         event.session(userSession);
 
         AuthenticationManager.setClientScopesInSession(session, authSession);
@@ -127,7 +127,7 @@ public Response process(Context context) {
         userSession.setNote(ServiceAccountConstants.CLIENT_ID_SESSION_NOTE, client.getClientId()); // This is for backwards compatibility
         userSession.setNote(ServiceAccountConstants.CLIENT_ID, client.getClientId());
         userSession.setNote(ServiceAccountConstants.CLIENT_HOST, clientConnection.getRemoteHost());
-        userSession.setNote(ServiceAccountConstants.CLIENT_ADDRESS, clientConnection.getRemoteAddr());
+        userSession.setNote(ServiceAccountConstants.CLIENT_ADDRESS, clientConnection.getRemoteHost());
 
         try {
             session.clientPolicy().triggerOnEvent(new ServiceAccountTokenRequestContext(formParams, clientSessionCtx.getClientSession()));
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/tokenexchange/AbstractTokenExchangeProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/tokenexchange/AbstractTokenExchangeProvider.java
index 2a062a14663f..bf7b4e707819 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/tokenexchange/AbstractTokenExchangeProvider.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/tokenexchange/AbstractTokenExchangeProvider.java
@@ -516,7 +516,7 @@ protected Response exchangeExternalToken(String subjectIssuer, String subjectTok
 
         UserModel user = importUserFromExternalIdentity(context);
 
-        UserSessionModel userSession = new UserSessionManager(session).createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteAddr(), "external-exchange", false, null, null);
+        UserSessionModel userSession = new UserSessionManager(session).createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteHost(), "external-exchange", false, null, null);
         externalExchangeContext.provider().exchangeExternalComplete(userSession, context, formParams);
 
         // this must exist so that we can obtain access token from user session if idp's store tokens is off
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/tokenexchange/V1TokenExchangeProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/tokenexchange/V1TokenExchangeProvider.java
index bf0abdba1ec0..8d44e124b14c 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/tokenexchange/V1TokenExchangeProvider.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/tokenexchange/V1TokenExchangeProvider.java
@@ -140,7 +140,7 @@ protected Response tokenExchange() {
                 disallowOnHolderOfTokenMismatch = false;
             }
 
-            tokenSession = new UserSessionManager(session).createUserSession(realm, requestedUser, requestedUser.getUsername(), clientConnection.getRemoteAddr(), "impersonate", false, null, null);
+            tokenSession = new UserSessionManager(session).createUserSession(realm, requestedUser, requestedUser.getUsername(), clientConnection.getRemoteHost(), "impersonate", false, null, null);
             if (tokenUser != null) {
                 tokenSession.setNote(IMPERSONATOR_ID.toString(), tokenUser.getId());
                 tokenSession.setNote(IMPERSONATOR_USERNAME.toString(), tokenUser.getUsername());
diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdaterSourceHostsCondition.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdaterSourceHostsCondition.java
index ead4c7be0812..4a379084898c 100644
--- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdaterSourceHostsCondition.java
+++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdaterSourceHostsCondition.java
@@ -85,7 +85,11 @@ public ClientPolicyVote applyPolicy(ClientPolicyContext context) throws ClientPo
     private boolean isHostMatched() {
         String hostAddress = session.getContext().getConnection().getRemoteAddr();
 
-        logger.tracev("Verifying remote host = {0}", hostAddress);
+        logger.tracev("Verifying remote host = {0}", session.getContext().getConnection().getRemoteHost());
+
+        if (hostAddress == null) {
+            return false;
+        }
 
         List<String> trustedHosts = getTrustedHosts();
         List<String> trustedDomains = getTrustedDomains();
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/TrustedHostClientRegistrationPolicy.java b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/TrustedHostClientRegistrationPolicy.java
index 65d26fbc661e..3d8145e1ae57 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/TrustedHostClientRegistrationPolicy.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/TrustedHostClientRegistrationPolicy.java
@@ -97,29 +97,34 @@ protected void verifyHost() throws ClientRegistrationPolicyException {
 
         String hostAddress = session.getContext().getConnection().getRemoteAddr();
 
-        verifyHost(hostAddress);
-    }
+        logger.debugf("Verifying remote host : %s", session.getContext().getConnection().getRemoteHost());
 
-    protected void verifyHost(String hostAddress) throws ClientRegistrationPolicyException {
-        logger.debugf("Verifying remote host : %s", hostAddress);
+        if (!verifyHost(hostAddress)) {
+            ServicesLogger.LOGGER.failedToVerifyRemoteHost(session.getContext().getConnection().getRemoteHost());
+            throw new ClientRegistrationPolicyException("Host not trusted.");
+        }
+    }
 
+    protected boolean verifyHost(String hostAddress) {
+        if (hostAddress == null) {
+            return false;
+        }
         List<String> trustedHosts = getTrustedHosts();
         List<String> trustedDomains = getTrustedDomains();
 
         // Verify trustedHosts by their IP addresses
         String verifiedHost = verifyHostInTrustedHosts(hostAddress, trustedHosts);
         if (verifiedHost != null) {
-            return;
+            return true;
         }
 
         // Verify domains if hostAddress hostname belongs to the domain. This assumes proper DNS setup
         verifiedHost = verifyHostInTrustedDomains(hostAddress, trustedDomains);
         if (verifiedHost != null) {
-            return;
+            return true;
         }
 
-        ServicesLogger.LOGGER.failedToVerifyRemoteHost(hostAddress);
-        throw new ClientRegistrationPolicyException("Host not trusted.");
+        return false;
     }
 
 
diff --git a/services/src/main/java/org/keycloak/services/managers/DefaultBruteForceProtector.java b/services/src/main/java/org/keycloak/services/managers/DefaultBruteForceProtector.java
index 840e6dbf57e8..5049c1bea8e3 100644
--- a/services/src/main/java/org/keycloak/services/managers/DefaultBruteForceProtector.java
+++ b/services/src/main/java/org/keycloak/services/managers/DefaultBruteForceProtector.java
@@ -213,7 +213,7 @@ protected void processLogin(RealmModel realm, UserModel user, ClientConnection c
             if (success) {
                 success(s, realm, user.getId());
             } else {
-                failure(s, realm, user.getId(), clientConnection.getRemoteAddr(), Time.currentTimeMillis());
+                failure(s, realm, user.getId(), clientConnection.getRemoteHost(), Time.currentTimeMillis());
             }
         }));
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java b/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java
index baccb354a466..97835674a715 100755
--- a/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java
@@ -107,7 +107,7 @@ public Response createUser() {
             return createWelcomePage(null, null);
         } else {
             if (!isLocal()) {
-                ServicesLogger.LOGGER.rejectedNonLocalAttemptToCreateInitialUser(session.getContext().getConnection().getRemoteAddr());
+                ServicesLogger.LOGGER.rejectedNonLocalAttemptToCreateInitialUser(session.getContext().getConnection().getRemoteHost());
                 throw new WebApplicationException(Response.Status.BAD_REQUEST);
             }
 
@@ -277,7 +277,7 @@ private boolean isLocal() {
         HttpHeaders headers = request.getHttpHeaders();
         String xForwardedFor = headers.getHeaderString("X-Forwarded-For");
         String forwarded = headers.getHeaderString("Forwarded");
-        logger.debugf("Checking WelcomePage. Remote address: %s, Local address: %s, X-Forwarded-For header: %s, Forwarded header: %s", remoteAddress.toString(), localAddress.toString(), xForwardedFor, forwarded);
+        logger.debugf("Checking WelcomePage. Remote address: %s, Local address: %s, X-Forwarded-For header: %s, Forwarded header: %s", remoteAddress, localAddress, xForwardedFor, forwarded);
 
         // Consider that welcome page accessed locally just if it was accessed really through "localhost" URL and without loadbalancer (x-forwarded-for and forwarded header is empty).
         return xForwardedFor == null && forwarded == null && SecureContextResolver.isLocalAddress(remoteAddress) && SecureContextResolver.isLocalAddress(localAddress);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminEventBuilder.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminEventBuilder.java
index 35b1d2c280a8..04cc44258045 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/AdminEventBuilder.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminEventBuilder.java
@@ -57,7 +57,7 @@ public class AdminEventBuilder {
     private EventStoreProvider store;
 
     public AdminEventBuilder(RealmModel realm, AdminAuth auth, KeycloakSession session, ClientConnection clientConnection) {
-        this(realm, auth, session, clientConnection.getRemoteAddr(), null);
+        this(realm, auth, session, clientConnection.getRemoteHost(), null);
     }
 
     private AdminEventBuilder(RealmModel realm, AdminAuth auth, KeycloakSession session, String ipAddress, AdminEvent adminEvent) {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientScopeEvaluateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientScopeEvaluateResource.java
index ad29f2971692..1b15e71c2be7 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientScopeEvaluateResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientScopeEvaluateResource.java
@@ -270,7 +270,7 @@ private<R> R sessionAware(UserModel user, String scopeParam, BiFunction<UserSess
             authSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, scopeParam);
 
             UserSessionModel userSession = new UserSessionManager(session).createUserSession(authSession.getParentSession().getId(), realm, user, user.getUsername(),
-                    clientConnection.getRemoteAddr(), "example-auth", false, null, null, UserSessionModel.SessionPersistenceState.TRANSIENT);
+                    clientConnection.getRemoteHost(), "example-auth", false, null, null, UserSessionModel.SessionPersistenceState.TRANSIENT);
 
             AuthenticationManager.setClientScopesInSession(session, authSession);
             ClientSessionContext clientSessionCtx = TokenManager.attachAuthenticationSession(session, userSession, authSession);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java
index f2b0c8e3b113..9e1ca4844d49 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java
@@ -408,7 +408,7 @@ public Map<String, Object> impersonate() {
         }
         EventBuilder event = new EventBuilder(realm, session, clientConnection);
 
-        UserSessionModel userSession = new UserSessionManager(session).createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteAddr(), "impersonate", false, null, null);
+        UserSessionModel userSession = new UserSessionManager(session).createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteHost(), "impersonate", false, null, null);
 
         UserModel adminUser = auth.adminAuth().getUser();
         String impersonatorId = adminUser.getId();
diff --git a/services/src/main/java/org/keycloak/services/util/UserSessionUtil.java b/services/src/main/java/org/keycloak/services/util/UserSessionUtil.java
index 8cea91828c01..8041dd482f08 100644
--- a/services/src/main/java/org/keycloak/services/util/UserSessionUtil.java
+++ b/services/src/main/java/org/keycloak/services/util/UserSessionUtil.java
@@ -218,7 +218,7 @@ private static UserSessionValidationResult createTransientSessionForClient(Keycl
         }
 
         ClientConnection clientConnection = session.getContext().getConnection();
-        UserSessionModel userSession = new UserSessionManager(session).createUserSession(KeycloakModelUtils.generateId(), realm, user, user.getUsername(), clientConnection.getRemoteAddr(),
+        UserSessionModel userSession = new UserSessionManager(session).createUserSession(KeycloakModelUtils.generateId(), realm, user, user.getUsername(), clientConnection.getRemoteHost(),
                 ServiceAccountConstants.CLIENT_AUTH, false, null, null, UserSessionModel.SessionPersistenceState.TRANSIENT);
         // attach an auth session for the client
         attachAuthenticationSession(session, userSession, client);
diff --git a/services/src/main/java/org/keycloak/social/google/GoogleIdentityProvider.java b/services/src/main/java/org/keycloak/social/google/GoogleIdentityProvider.java
index 9ef583b696db..708b152baf7a 100755
--- a/services/src/main/java/org/keycloak/social/google/GoogleIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/social/google/GoogleIdentityProvider.java
@@ -64,7 +64,7 @@ protected String getUserInfoUrl() {
         String uri = super.getUserInfoUrl();
         if (((GoogleIdentityProviderConfig)getConfig()).isUserIp()) {
             ClientConnection connection = session.getContext().getConnection();
-            if (connection != null) {
+            if (connection != null && connection.getRemoteAddr() != null) {
                 uri = KeycloakUriBuilder.fromUri(super.getUserInfoUrl()).queryParam("userIp", connection.getRemoteAddr()).build().toString();
             }
 
diff --git a/services/src/test/java/org/keycloak/services/clientregistration/policy/impl/TrustedHostClientRegistrationPolicyTest.java b/services/src/test/java/org/keycloak/services/clientregistration/policy/impl/TrustedHostClientRegistrationPolicyTest.java
index a73de6c98353..a8ebe068edb2 100644
--- a/services/src/test/java/org/keycloak/services/clientregistration/policy/impl/TrustedHostClientRegistrationPolicyTest.java
+++ b/services/src/test/java/org/keycloak/services/clientregistration/policy/impl/TrustedHostClientRegistrationPolicyTest.java
@@ -16,6 +16,9 @@
  */
 package org.keycloak.services.clientregistration.policy.impl;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -51,8 +54,8 @@ public void testLocalhostName() {
         ComponentModel model = createComponentModel("localhost");
         TrustedHostClientRegistrationPolicy policy = (TrustedHostClientRegistrationPolicy) factory.create(session, model);
 
-        policy.verifyHost("127.0.0.1");
-        Assert.assertThrows(ClientRegistrationPolicyException.class, () -> policy.verifyHost("10.0.0.1"));
+        assertTrue(policy.verifyHost("127.0.0.1"));
+        assertFalse(policy.verifyHost("10.0.0.1"));
         policy.checkURLTrusted("https://localhost", policy.getTrustedHosts(), policy.getTrustedDomains());
         Assert.assertThrows(ClientRegistrationPolicyException.class, () -> policy.checkURLTrusted("https://otherhost",
                 policy.getTrustedHosts(), policy.getTrustedDomains()));
@@ -64,8 +67,8 @@ public void testLocalhostDomain() {
         ComponentModel model = createComponentModel("*.localhost");
         TrustedHostClientRegistrationPolicy policy = (TrustedHostClientRegistrationPolicy) factory.create(session, model);
 
-        policy.verifyHost("127.0.0.1");
-        Assert.assertThrows(ClientRegistrationPolicyException.class, () -> policy.verifyHost("10.0.0.1"));
+        assertTrue(policy.verifyHost("127.0.0.1"));
+        assertFalse(policy.verifyHost("10.0.0.1"));
         policy.checkURLTrusted("https://localhost", policy.getTrustedHosts(), policy.getTrustedDomains());
         policy.checkURLTrusted("https://other.localhost", policy.getTrustedHosts(), policy.getTrustedDomains());
         Assert.assertThrows(ClientRegistrationPolicyException.class, () -> policy.checkURLTrusted("https://otherlocalhost",
@@ -78,8 +81,8 @@ public void testLocalhostIP() {
         ComponentModel model = createComponentModel("127.0.0.1");
         TrustedHostClientRegistrationPolicy policy = (TrustedHostClientRegistrationPolicy) factory.create(session, model);
 
-        policy.verifyHost("127.0.0.1");
-        Assert.assertThrows(ClientRegistrationPolicyException.class, () -> policy.verifyHost("10.0.0.1"));
+        assertTrue(policy.verifyHost("127.0.0.1"));
+        assertFalse(policy.verifyHost("10.0.0.1"));
         policy.checkURLTrusted("https://127.0.0.1", policy.getTrustedHosts(), policy.getTrustedDomains());
         Assert.assertThrows(ClientRegistrationPolicyException.class, () -> policy.checkURLTrusted("https://localhost",
                 policy.getTrustedHosts(), policy.getTrustedDomains()));


Reviews:
user: login: vmuzikar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T15:31:16Z
user: login: shawkins
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T15:47:27Z
user: login: vmuzikar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T16:00:27Z
user: login: shawkins
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T16:34:46Z
user: login: vmuzikar
body: Thanks for the PR.

Looks good in general. Added some comments/questions.

We'd also need a review from a core team. CC @pedroigor @mposolda

The way I understand it is that it's basically a regression caused by https://github.com/keycloak/keycloak/pull/35247. I wonder if we should've originally consider this as a breaking change for the upgrading guide as some extension can be using it as well.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:16:15Z
user: login: shawkins
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:18:51Z
user: login: shawkins
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:19:04Z
user: login: shawkins
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:21:03Z
user: login: vmuzikar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T15:13:43Z
user: login: vmuzikar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T15:13:56Z
user: login: vmuzikar
body: LGTM, thanks!

> We did note it as a breaking change.

Ah, I forgot it was part of a micro release.

Before merge, it'd be good to get an approval from core.
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T15:37:46Z
user: login: keycloak-github-bot[bot]
body: Unreported flaky test detected, please review
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-12T18:26:38Z
user: login: keycloak-github-bot[bot]
body: Unreported flaky test detected, please review
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-12T21:05:21Z
user: login: mabartos
body: LGTM.

btw. where are exactly these upgrading notes? Users should be probably notified about this change even for the upcoming release? 
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T09:46:36Z
user: login: vmuzikar
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T11:25:17Z
user: login: keycloak-github-bot[bot]
body: Unreported flaky test detected, please review
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-13T14:31:25Z
user: login: pedroigor
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T16:23:32Z
user: login: mposolda
body:  I am fine with the idea. I did not reviewed very deeply as I am leaving to cloud-native team members for doing that. But you can consider approved from me.

Looks ok, but will need rebase as already mentioned by @vmuzikar .
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T16:47:34Z
user: login: pedroigor
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T17:48:11Z
user: login: vmuzikar
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T19:00:49Z

Review comments:
diff_hunk: @@ -855,25 +872,33 @@ public Response handleBrowserException(Exception failure) {
             } else if (e.getError() == AuthenticationFlowError.DISPLAY_NOT_SUPPORTED) {
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 event.error(Errors.DISPLAY_UNSUPPORTED);
-                if (e.getResponse() != null) return e.getResponse();
+                if (e.getResponse() != null) {
+                    return e.getResponse();
+                }
                 return ErrorPage.error(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.DISPLAY_UNSUPPORTED);
             } else if (e.getError() == AuthenticationFlowError.CREDENTIAL_SETUP_REQUIRED){
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 event.error(Errors.INVALID_USER_CREDENTIALS);
-                if (e.getResponse() != null) return e.getResponse();
+                if (e.getResponse() != null) {
+                    return e.getResponse();
+                }
                 return ErrorPage.error(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.CREDENTIAL_SETUP_REQUIRED);
             } else if (e.getError() == AuthenticationFlowError.GENERIC_AUTHENTICATION_ERROR) {
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 if (e.getEventDetails() != null) {
                     event.detail(Details.AUTHENTICATION_ERROR_DETAIL, e.getEventDetails());
                 }
                 event.error(Errors.GENERIC_AUTHENTICATION_ERROR);
-                if (e.getResponse() != null) return e.getResponse();
+                if (e.getResponse() != null) {
+                    return e.getResponse();
+                }
                 return ErrorPage.error(session, authenticationSession, Response.Status.BAD_REQUEST, e.getUserErrorMessage());
             } else {
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 event.error(Errors.INVALID_USER_CREDENTIALS);
-                if (e.getResponse() != null) return e.getResponse();
+                if (e.getResponse() != null) {
+                    return e.getResponse();
+                }
path: services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
user: login: vmuzikar
body: I'm sorry, I haven't yet fully reviewed the PR but I wonder if we could limit unrelated changes like this (splitting conditions to multiple lines) in the PR. It adds some additional noise and makes it a bit harder to review. We could do some refactor like  that as a follow-up. Thanks.
created_at: 2025-03-06T15:31:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -855,25 +872,33 @@ public Response handleBrowserException(Exception failure) {
             } else if (e.getError() == AuthenticationFlowError.DISPLAY_NOT_SUPPORTED) {
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 event.error(Errors.DISPLAY_UNSUPPORTED);
-                if (e.getResponse() != null) return e.getResponse();
+                if (e.getResponse() != null) {
+                    return e.getResponse();
+                }
                 return ErrorPage.error(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.DISPLAY_UNSUPPORTED);
             } else if (e.getError() == AuthenticationFlowError.CREDENTIAL_SETUP_REQUIRED){
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 event.error(Errors.INVALID_USER_CREDENTIALS);
-                if (e.getResponse() != null) return e.getResponse();
+                if (e.getResponse() != null) {
+                    return e.getResponse();
+                }
                 return ErrorPage.error(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.CREDENTIAL_SETUP_REQUIRED);
             } else if (e.getError() == AuthenticationFlowError.GENERIC_AUTHENTICATION_ERROR) {
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 if (e.getEventDetails() != null) {
                     event.detail(Details.AUTHENTICATION_ERROR_DETAIL, e.getEventDetails());
                 }
                 event.error(Errors.GENERIC_AUTHENTICATION_ERROR);
-                if (e.getResponse() != null) return e.getResponse();
+                if (e.getResponse() != null) {
+                    return e.getResponse();
+                }
                 return ErrorPage.error(session, authenticationSession, Response.Status.BAD_REQUEST, e.getUserErrorMessage());
             } else {
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 event.error(Errors.INVALID_USER_CREDENTIALS);
-                if (e.getResponse() != null) return e.getResponse();
+                if (e.getResponse() != null) {
+                    return e.getResponse();
+                }
path: services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
user: login: shawkins
body: Sure, it's just happening because of an automatic setting in the IDE. Do you want me to remove them?
created_at: 2025-03-06T15:47:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -855,25 +872,33 @@ public Response handleBrowserException(Exception failure) {
             } else if (e.getError() == AuthenticationFlowError.DISPLAY_NOT_SUPPORTED) {
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 event.error(Errors.DISPLAY_UNSUPPORTED);
-                if (e.getResponse() != null) return e.getResponse();
+                if (e.getResponse() != null) {
+                    return e.getResponse();
+                }
                 return ErrorPage.error(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.DISPLAY_UNSUPPORTED);
             } else if (e.getError() == AuthenticationFlowError.CREDENTIAL_SETUP_REQUIRED){
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 event.error(Errors.INVALID_USER_CREDENTIALS);
-                if (e.getResponse() != null) return e.getResponse();
+                if (e.getResponse() != null) {
+                    return e.getResponse();
+                }
                 return ErrorPage.error(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.CREDENTIAL_SETUP_REQUIRED);
             } else if (e.getError() == AuthenticationFlowError.GENERIC_AUTHENTICATION_ERROR) {
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 if (e.getEventDetails() != null) {
                     event.detail(Details.AUTHENTICATION_ERROR_DETAIL, e.getEventDetails());
                 }
                 event.error(Errors.GENERIC_AUTHENTICATION_ERROR);
-                if (e.getResponse() != null) return e.getResponse();
+                if (e.getResponse() != null) {
+                    return e.getResponse();
+                }
                 return ErrorPage.error(session, authenticationSession, Response.Status.BAD_REQUEST, e.getUserErrorMessage());
             } else {
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 event.error(Errors.INVALID_USER_CREDENTIALS);
-                if (e.getResponse() != null) return e.getResponse();
+                if (e.getResponse() != null) {
+                    return e.getResponse();
+                }
path: services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
user: login: vmuzikar
body: Yes please, if you don't mind.
created_at: 2025-03-06T16:00:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -855,25 +872,33 @@ public Response handleBrowserException(Exception failure) {
             } else if (e.getError() == AuthenticationFlowError.DISPLAY_NOT_SUPPORTED) {
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 event.error(Errors.DISPLAY_UNSUPPORTED);
-                if (e.getResponse() != null) return e.getResponse();
+                if (e.getResponse() != null) {
+                    return e.getResponse();
+                }
                 return ErrorPage.error(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.DISPLAY_UNSUPPORTED);
             } else if (e.getError() == AuthenticationFlowError.CREDENTIAL_SETUP_REQUIRED){
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 event.error(Errors.INVALID_USER_CREDENTIALS);
-                if (e.getResponse() != null) return e.getResponse();
+                if (e.getResponse() != null) {
+                    return e.getResponse();
+                }
                 return ErrorPage.error(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.CREDENTIAL_SETUP_REQUIRED);
             } else if (e.getError() == AuthenticationFlowError.GENERIC_AUTHENTICATION_ERROR) {
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 if (e.getEventDetails() != null) {
                     event.detail(Details.AUTHENTICATION_ERROR_DETAIL, e.getEventDetails());
                 }
                 event.error(Errors.GENERIC_AUTHENTICATION_ERROR);
-                if (e.getResponse() != null) return e.getResponse();
+                if (e.getResponse() != null) {
+                    return e.getResponse();
+                }
                 return ErrorPage.error(session, authenticationSession, Response.Status.BAD_REQUEST, e.getUserErrorMessage());
             } else {
                 ServicesLogger.LOGGER.failedAuthentication(e);
                 event.error(Errors.INVALID_USER_CREDENTIALS);
-                if (e.getResponse() != null) return e.getResponse();
+                if (e.getResponse() != null) {
+                    return e.getResponse();
+                }
path: services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
user: login: shawkins
body: Removed and the additional javadocs added.
created_at: 2025-03-06T16:34:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -80,7 +80,9 @@ protected boolean validate(ValidationContext context, String captcha, Map<String
         List<NameValuePair> formparams = new LinkedList<>();
         formparams.add(new BasicNameValuePair("secret", config.get(SECRET_KEY)));
         formparams.add(new BasicNameValuePair("response", captcha));
-        formparams.add(new BasicNameValuePair("remoteip", context.getConnection().getRemoteAddr()));
+        if (context.getConnection().getRemoteAddr() != null) {
+            formparams.add(new BasicNameValuePair("remoteip", context.getConnection().getRemoteAddr()));
path: services/src/main/java/org/keycloak/authentication/forms/RegistrationRecaptcha.java
user: login: vmuzikar
body: Do we know the implications on the form if the IP is missing?
created_at: 2025-03-12T13:01:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -85,7 +85,11 @@ public ClientPolicyVote applyPolicy(ClientPolicyContext context) throws ClientPo
     private boolean isHostMatched() {
         String hostAddress = session.getContext().getConnection().getRemoteAddr();
path: services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdaterSourceHostsCondition.java
user: login: vmuzikar
body: Should we use `getRemoteHost` here instead? Or if we're really validating the IP, why are we using `getRemoteHost` there?
created_at: 2025-03-12T13:08:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,29 +97,34 @@ protected void verifyHost() throws ClientRegistrationPolicyException {
 
         String hostAddress = session.getContext().getConnection().getRemoteAddr();
 
-        verifyHost(hostAddress);
-    }
+        logger.debugf("Verifying remote host : %s", session.getContext().getConnection().getRemoteHost());
path: services/src/main/java/org/keycloak/services/clientregistration/policy/impl/TrustedHostClientRegistrationPolicy.java
user: login: vmuzikar
body: Similar question as above. Why to log host if what we're really checking is the IP?
created_at: 2025-03-12T13:11:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -85,7 +85,11 @@ public ClientPolicyVote applyPolicy(ClientPolicyContext context) throws ClientPo
     private boolean isHostMatched() {
         String hostAddress = session.getContext().getConnection().getRemoteAddr();
path: services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdaterSourceHostsCondition.java
user: login: shawkins
body: For display / logging purposes we're using the host.
created_at: 2025-03-12T13:18:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,29 +97,34 @@ protected void verifyHost() throws ClientRegistrationPolicyException {
 
         String hostAddress = session.getContext().getConnection().getRemoteAddr();
 
-        verifyHost(hostAddress);
-    }
+        logger.debugf("Verifying remote host : %s", session.getContext().getConnection().getRemoteHost());
path: services/src/main/java/org/keycloak/services/clientregistration/policy/impl/TrustedHostClientRegistrationPolicy.java
user: login: shawkins
body: For display / logging purposes we're using the host.
created_at: 2025-03-12T13:19:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -80,7 +80,9 @@ protected boolean validate(ValidationContext context, String captcha, Map<String
         List<NameValuePair> formparams = new LinkedList<>();
         formparams.add(new BasicNameValuePair("secret", config.get(SECRET_KEY)));
         formparams.add(new BasicNameValuePair("response", captcha));
-        formparams.add(new BasicNameValuePair("remoteip", context.getConnection().getRemoteAddr()));
+        if (context.getConnection().getRemoteAddr() != null) {
+            formparams.add(new BasicNameValuePair("remoteip", context.getConnection().getRemoteAddr()));
path: services/src/main/java/org/keycloak/authentication/forms/RegistrationRecaptcha.java
user: login: shawkins
body: For both of these scnenarios the ip is an optional parameter. It seems to be used as an anti-fraud mechanism. If it's missing my guess is that you're more likely to see throttling.
created_at: 2025-03-12T13:21:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -80,7 +80,9 @@ protected boolean validate(ValidationContext context, String captcha, Map<String
         List<NameValuePair> formparams = new LinkedList<>();
         formparams.add(new BasicNameValuePair("secret", config.get(SECRET_KEY)));
         formparams.add(new BasicNameValuePair("response", captcha));
-        formparams.add(new BasicNameValuePair("remoteip", context.getConnection().getRemoteAddr()));
+        if (context.getConnection().getRemoteAddr() != null) {
+            formparams.add(new BasicNameValuePair("remoteip", context.getConnection().getRemoteAddr()));
path: services/src/main/java/org/keycloak/authentication/forms/RegistrationRecaptcha.java
user: login: vmuzikar
body: Ok, makes sense. Thanks.
created_at: 2025-03-12T15:13:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -85,7 +85,11 @@ public ClientPolicyVote applyPolicy(ClientPolicyContext context) throws ClientPo
     private boolean isHostMatched() {
         String hostAddress = session.getContext().getConnection().getRemoteAddr();
path: services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdaterSourceHostsCondition.java
user: login: vmuzikar
body: Ok, makes sense. Thanks.
created_at: 2025-03-12T15:13:56Z
author_association: CONTRIBUTOR

PR comments:
user: login: shawkins
created_at: 2025-03-12T13:18:03Z
body: > The way I understand it is that it's basically a regression caused by https://github.com/keycloak/keycloak/pull/35247. I wonder if we should've originally consider this as a breaking change for the upgrading guide as some extension can be using it as well.

We did note it as a breaking change.
author_association: CONTRIBUTOR
user: login: vmuzikar
created_at: 2025-03-12T15:38:22Z
body: @shawkins Can you also please rebase to resolve the conflict?
author_association: CONTRIBUTOR
user: login: shawkins
created_at: 2025-03-12T16:17:32Z
body: > @shawkins Can you also please rebase to resolve the conflict?

Should be good now.
author_association: CONTRIBUTOR
user: login: keycloak-github-bot[bot]
created_at: 2025-03-12T18:26:38Z
body: ## Unreported flaky test detected
If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

### org.keycloak.testsuite.webauthn.AppInitiatedActionWebAuthnTest#cancelSetupWebAuthn

[Keycloak CI - WebAuthn IT (chrome)](https://github.com/keycloak/keycloak/actions/runs/13817959203/job/38656264302)


```
java.lang.AssertionError: Event expected
	at org.junit.Assert.fail(Assert.java:89)
	at org.junit.Assert.assertTrue(Assert.java:42)
	at org.junit.Assert.assertNotNull(Assert.java:713)
	at org.keycloak.testsuite.AssertEvents.poll(AssertEvents.java:95)
...
```

```
java.lang.AssertionError: Event expected
	at org.junit.Assert.fail(Assert.java:89)
	at org.junit.Assert.assertTrue(Assert.java:42)
	at org.junit.Assert.assertNotNull(Assert.java:713)
	at org.keycloak.testsuite.AssertEvents.poll(AssertEvents.java:95)
...
```
[Report flaky test](https://github.com/keycloak/keycloak/issues/new?title=Flaky+test%3A+org.keycloak.testsuite.webauthn.AppInitiatedActionWebAuthnTest%23cancelSetupWebAuthn&labels=flaky-test%2Carea%2Fci%2Ckind%2Fbug&body=%23%23+org.keycloak.testsuite.webauthn.AppInitiatedActionWebAuthnTest%23cancelSetupWebAuthn%0A%5BKeycloak+CI+-+WebAuthn+IT+%28chrome%29%5D%28https%3A%2F%2Fgithub.com%2Fkeycloak%2Fkeycloak%2Factions%2Fruns%2F13817959203%2Fjob%2F38656264302%29+%2F+%5BPull+Request+%2337797%5D%28https%3A%2F%2Fgithub.com%2Fkeycloak%2Fkeycloak%2Fpull%2F37797%29%0A%0A%60%60%60%0Ajava.lang.AssertionError%3A+Event+expected%0A%09at+org.junit.Assert.fail%28Assert.java%3A89%29%0A%09at+org.junit.Assert.assertTrue%28Assert.java%3A42%29%0A%09at+org.junit.Assert.assertNotNull%28Assert.java%3A713%29%0A%09at+org.keycloak.testsuite.AssertEvents.poll%28AssertEvents.java%3A95%29%0A...%0A%60%60%60%0A%0A%60%60%60%0Ajava.lang.AssertionError%3A+Event+expected%0A%09at+org.junit.Assert.fail%28Assert.java%3A89%29%0A%09at+org.junit.Assert.assertTrue%28Assert.java%3A42%29%0A%09at+org.junit.Assert.assertNotNull%28Assert.java%3A713%29%0A%09at+org.keycloak.testsuite.AssertEvents.poll%28AssertEvents.java%3A95%29%0A...%0A%60%60%60%0A)
### org.keycloak.testsuite.webauthn.passwordless.AppInitiatedActionPwdLessTest#cancelSetupWebAuthn

[Keycloak CI - WebAuthn IT (chrome)](https://github.com/keycloak/keycloak/actions/runs/13817959203/job/38656264302)


```
java.lang.AssertionError: Event expected
	at org.junit.Assert.fail(Assert.java:89)
	at org.junit.Assert.assertTrue(Assert.java:42)
	at org.junit.Assert.assertNotNull(Assert.java:713)
	at org.keycloak.testsuite.AssertEvents.poll(AssertEvents.java:95)
...
```
[Report flaky test](https://github.com/keycloak/keycloak/issues/new?title=Flaky+test%3A+org.keycloak.testsuite.webauthn.passwordless.AppInitiatedActionPwdLessTest%23cancelSetupWebAuthn&labels=flaky-test%2Carea%2Fci%2Ckind%2Fbug&body=%23%23+org.keycloak.testsuite.webauthn.passwordless.AppInitiatedActionPwdLessTest%23cancelSetupWebAuthn%0A%5BKeycloak+CI+-+WebAuthn+IT+%28chrome%29%5D%28https%3A%2F%2Fgithub.com%2Fkeycloak%2Fkeycloak%2Factions%2Fruns%2F13817959203%2Fjob%2F38656264302%29+%2F+%5BPull+Request+%2337797%5D%28https%3A%2F%2Fgithub.com%2Fkeycloak%2Fkeycloak%2Fpull%2F37797%29%0A%0A%60%60%60%0Ajava.lang.AssertionError%3A+Event+expected%0A%09at+org.junit.Assert.fail%28Assert.java%3A89%29%0A%09at+org.junit.Assert.assertTrue%28Assert.java%3A42%29%0A%09at+org.junit.Assert.assertNotNull%28Assert.java%3A713%29%0A%09at+org.keycloak.testsuite.AssertEvents.poll%28AssertEvents.java%3A95%29%0A...%0A%60%60%60%0A)

author_association: NONE
user: login: keycloak-github-bot[bot]
created_at: 2025-03-12T21:05:22Z
body: ## Unreported flaky test detected
If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

### org.keycloak.testsuite.webauthn.AppInitiatedActionWebAuthnTest#cancelSetupWebAuthn

[Keycloak CI - WebAuthn IT (chrome)](https://github.com/keycloak/keycloak/actions/runs/13817959203/job/38656264302)


```
java.lang.AssertionError: Event expected
	at org.junit.Assert.fail(Assert.java:89)
	at org.junit.Assert.assertTrue(Assert.java:42)
	at org.junit.Assert.assertNotNull(Assert.java:713)
	at org.keycloak.testsuite.AssertEvents.poll(AssertEvents.java:95)
...
```

```
java.lang.AssertionError: Event expected
	at org.junit.Assert.fail(Assert.java:89)
	at org.junit.Assert.assertTrue(Assert.java:42)
	at org.junit.Assert.assertNotNull(Assert.java:713)
	at org.keycloak.testsuite.AssertEvents.poll(AssertEvents.java:95)
...
```
[Report flaky test](https://github.com/keycloak/keycloak/issues/new?title=Flaky+test%3A+org.keycloak.testsuite.webauthn.AppInitiatedActionWebAuthnTest%23cancelSetupWebAuthn&labels=flaky-test%2Carea%2Fci%2Ckind%2Fbug&body=%23%23+org.keycloak.testsuite.webauthn.AppInitiatedActionWebAuthnTest%23cancelSetupWebAuthn%0A%5BKeycloak+CI+-+WebAuthn+IT+%28chrome%29%5D%28https%3A%2F%2Fgithub.com%2Fkeycloak%2Fkeycloak%2Factions%2Fruns%2F13817959203%2Fjob%2F38656264302%29+%2F+%5BPull+Request+%2337797%5D%28https%3A%2F%2Fgithub.com%2Fkeycloak%2Fkeycloak%2Fpull%2F37797%29%0A%0A%60%60%60%0Ajava.lang.AssertionError%3A+Event+expected%0A%09at+org.junit.Assert.fail%28Assert.java%3A89%29%0A%09at+org.junit.Assert.assertTrue%28Assert.java%3A42%29%0A%09at+org.junit.Assert.assertNotNull%28Assert.java%3A713%29%0A%09at+org.keycloak.testsuite.AssertEvents.poll%28AssertEvents.java%3A95%29%0A...%0A%60%60%60%0A%0A%60%60%60%0Ajava.lang.AssertionError%3A+Event+expected%0A%09at+org.junit.Assert.fail%28Assert.java%3A89%29%0A%09at+org.junit.Assert.assertTrue%28Assert.java%3A42%29%0A%09at+org.junit.Assert.assertNotNull%28Assert.java%3A713%29%0A%09at+org.keycloak.testsuite.AssertEvents.poll%28AssertEvents.java%3A95%29%0A...%0A%60%60%60%0A)
### org.keycloak.testsuite.webauthn.passwordless.AppInitiatedActionPwdLessTest#cancelSetupWebAuthn

[Keycloak CI - WebAuthn IT (chrome)](https://github.com/keycloak/keycloak/actions/runs/13817959203/job/38656264302)


```
java.lang.AssertionError: Event expected
	at org.junit.Assert.fail(Assert.java:89)
	at org.junit.Assert.assertTrue(Assert.java:42)
	at org.junit.Assert.assertNotNull(Assert.java:713)
	at org.keycloak.testsuite.AssertEvents.poll(AssertEvents.java:95)
...
```
[Report flaky test](https://github.com/keycloak/keycloak/issues/new?title=Flaky+test%3A+org.keycloak.testsuite.webauthn.passwordless.AppInitiatedActionPwdLessTest%23cancelSetupWebAuthn&labels=flaky-test%2Carea%2Fci%2Ckind%2Fbug&body=%23%23+org.keycloak.testsuite.webauthn.passwordless.AppInitiatedActionPwdLessTest%23cancelSetupWebAuthn%0A%5BKeycloak+CI+-+WebAuthn+IT+%28chrome%29%5D%28https%3A%2F%2Fgithub.com%2Fkeycloak%2Fkeycloak%2Factions%2Fruns%2F13817959203%2Fjob%2F38656264302%29+%2F+%5BPull+Request+%2337797%5D%28https%3A%2F%2Fgithub.com%2Fkeycloak%2Fkeycloak%2Fpull%2F37797%29%0A%0A%60%60%60%0Ajava.lang.AssertionError%3A+Event+expected%0A%09at+org.junit.Assert.fail%28Assert.java%3A89%29%0A%09at+org.junit.Assert.assertTrue%28Assert.java%3A42%29%0A%09at+org.junit.Assert.assertNotNull%28Assert.java%3A713%29%0A%09at+org.keycloak.testsuite.AssertEvents.poll%28AssertEvents.java%3A95%29%0A...%0A%60%60%60%0A)

author_association: NONE
user: login: keycloak-github-bot[bot]
created_at: 2025-03-13T14:31:26Z
body: ## Unreported flaky test detected
If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

### org.keycloak.testsuite.url.HostnameV2Test#testAdminLocal

[Keycloak CI - Base IT (6)](https://github.com/keycloak/keycloak/actions/runs/13835846647/job/38710931253)


```
java.net.UnknownHostException: 127.0.0.1.nip.io: Temporary failure in name resolution
	at java.base/java.net.Inet6AddressImpl.lookupAllHostAddr(Native Method)
	at java.base/java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:52)
	at java.base/java.net.InetAddress$PlatformResolver.lookupByName(InetAddress.java:1211)
	at java.base/java.net.InetAddress.getAddressesFromNameService(InetAddress.java:1828)
...
```
[Report flaky test](https://github.com/keycloak/keycloak/issues/new?title=Flaky+test%3A+org.keycloak.testsuite.url.HostnameV2Test%23testAdminLocal&labels=flaky-test%2Carea%2Fci%2Ckind%2Fbug&body=%23%23+org.keycloak.testsuite.url.HostnameV2Test%23testAdminLocal%0A%5BKeycloak+CI+-+Base+IT+%286%29%5D%28https%3A%2F%2Fgithub.com%2Fkeycloak%2Fkeycloak%2Factions%2Fruns%2F13835846647%2Fjob%2F38710931253%29+%2F+%5BPull+Request+%2337797%5D%28https%3A%2F%2Fgithub.com%2Fkeycloak%2Fkeycloak%2Fpull%2F37797%29%0A%0A%60%60%60%0Ajava.net.UnknownHostException%3A+127.0.0.1.nip.io%3A+Temporary+failure+in+name+resolution%0A%09at+java.base%2Fjava.net.Inet6AddressImpl.lookupAllHostAddr%28Native+Method%29%0A%09at+java.base%2Fjava.net.Inet6AddressImpl.lookupAllHostAddr%28Inet6AddressImpl.java%3A52%29%0A%09at+java.base%2Fjava.net.InetAddress%24PlatformResolver.lookupByName%28InetAddress.java%3A1211%29%0A%09at+java.base%2Fjava.net.InetAddress.getAddressesFromNameService%28InetAddress.java%3A1828%29%0A...%0A%60%60%60%0A)

author_association: NONE
user: login: mabartos
created_at: 2025-03-14T09:21:26Z
body: @mposolda @pedroigor Guys, would you find a little of your time to check this PR? Thanks!
author_association: CONTRIBUTOR
user: login: shawkins
created_at: 2025-03-25T17:27:52Z
body: @pedroigor @mposolda should this still be considered for 26.2?
author_association: CONTRIBUTOR
user: login: vmuzikar
created_at: 2025-03-27T16:27:07Z
body: @shawkins Can you please rebase?
author_association: CONTRIBUTOR