keycloak-aplcache

merge

10/17/2014 5:54:25 PM

Changes

Details

diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.0.0.Final.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.0.0.Final.xml
index 856baee..fe169ee 100644
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.0.0.Final.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.0.0.Final.xml
@@ -16,29 +16,29 @@
             <column name="ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
-            <column name="ALLOWED_CLAIMS_MASK" type="BIGINT(19)"/>
-            <column name="ENABLED" type="BOOLEAN(1)"/>
-            <column name="FULL_SCOPE_ALLOWED" type="BOOLEAN(1)"/>
+            <column name="ALLOWED_CLAIMS_MASK" type="BIGINT"/>
+            <column name="ENABLED" type="BOOLEAN"/>
+            <column name="FULL_SCOPE_ALLOWED" type="BOOLEAN"/>
             <column name="NAME" type="VARCHAR(255)"/>
-            <column name="NOT_BEFORE" type="INT(10)"/>
-            <column name="PUBLIC_CLIENT" type="BOOLEAN(1)"/>
+            <column name="NOT_BEFORE" type="INT"/>
+            <column name="PUBLIC_CLIENT" type="BOOLEAN"/>
             <column name="SECRET" type="VARCHAR(255)"/>
             <column name="BASE_URL" type="VARCHAR(255)"/>
-            <column name="BEARER_ONLY" type="BOOLEAN(1)"/>
+            <column name="BEARER_ONLY" type="BOOLEAN"/>
             <column name="MANAGEMENT_URL" type="VARCHAR(255)"/>
-            <column name="SURROGATE_AUTH_REQUIRED" type="BOOLEAN(1)"/>
-            <column name="DIRECT_GRANTS_ONLY" type="BOOLEAN(1)"/>
+            <column name="SURROGATE_AUTH_REQUIRED" type="BOOLEAN"/>
+            <column name="DIRECT_GRANTS_ONLY" type="BOOLEAN"/>
             <column name="REALM_ID" type="VARCHAR(36)"/>
         </createTable>
         <createTable tableName="CLIENT_SESSION">
             <column name="ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
-            <column name="ACTION" type="INT(10)"/>
+            <column name="ACTION" type="INT"/>
             <column name="CLIENT_ID" type="VARCHAR(36)"/>
             <column name="REDIRECT_URI" type="VARCHAR(255)"/>
             <column name="STATE" type="VARCHAR(255)"/>
-            <column name="TIMESTAMP" type="INT(10)"/>
+            <column name="TIMESTAMP" type="INT"/>
             <column name="SESSION_ID" type="VARCHAR(36)"/>
         </createTable>
         <createTable tableName="CLIENT_SESSION_ROLE">
@@ -62,7 +62,7 @@
                 <constraints nullable="false"/>
             </column>
             <column name="DEVICE" type="VARCHAR(255)"/>
-            <column name="HASH_ITERATIONS" type="INT(10)"/>
+            <column name="HASH_ITERATIONS" type="INT"/>
             <column name="SALT" type="VARBINARY(16)"/>
             <column name="TYPE" type="VARCHAR(255)"/>
             <column name="VALUE" type="VARCHAR(255)"/>
@@ -78,7 +78,7 @@
             <column name="IP_ADDRESS" type="VARCHAR(255)"/>
             <column name="REALM_ID" type="VARCHAR(255)"/>
             <column name="SESSION_ID" type="VARCHAR(255)"/>
-            <column name="TIME" type="BIGINT(19)"/>
+            <column name="TIME" type="BIGINT"/>
             <column name="TYPE" type="VARCHAR(255)"/>
             <column name="USER_ID" type="VARCHAR(255)"/>
         </createTable>
@@ -95,7 +95,7 @@
                 <constraints nullable="false"/>
             </column>
             <column name="APP_REALM_CONSTRAINT" type="VARCHAR(36)"/>
-            <column name="APPLICATION_ROLE" type="BOOLEAN(1)"/>
+            <column name="APPLICATION_ROLE" type="BOOLEAN"/>
             <column name="DESCRIPTION" type="VARCHAR(255)"/>
             <column name="NAME" type="VARCHAR(255)"/>
             <column name="REALM_ID" type="VARCHAR(255)"/>
@@ -106,31 +106,31 @@
             <column name="ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
-            <column name="ACCESS_CODE_LIFESPAN" type="INT(10)"/>
-            <column name="USER_ACTION_LIFESPAN" type="INT(10)"/>
-            <column name="ACCESS_TOKEN_LIFESPAN" type="INT(10)"/>
+            <column name="ACCESS_CODE_LIFESPAN" type="INT"/>
+            <column name="USER_ACTION_LIFESPAN" type="INT"/>
+            <column name="ACCESS_TOKEN_LIFESPAN" type="INT"/>
             <column name="ACCOUNT_THEME" type="VARCHAR(255)"/>
             <column name="ADMIN_THEME" type="VARCHAR(255)"/>
             <column name="EMAIL_THEME" type="VARCHAR(255)"/>
-            <column name="ENABLED" type="BOOLEAN(1)"/>
-            <column name="EVENTS_ENABLED" type="BOOLEAN(1)"/>
-            <column name="EVENTS_EXPIRATION" type="BIGINT(19)"/>
+            <column name="ENABLED" type="BOOLEAN"/>
+            <column name="EVENTS_ENABLED" type="BOOLEAN"/>
+            <column name="EVENTS_EXPIRATION" type="BIGINT"/>
             <column name="LOGIN_THEME" type="VARCHAR(255)"/>
             <column name="NAME" type="VARCHAR(255)"/>
-            <column name="NOT_BEFORE" type="INT(10)"/>
-            <column name="PASSWORD_CRED_GRANT_ALLOWED" type="BOOLEAN(1)"/>
+            <column name="NOT_BEFORE" type="INT"/>
+            <column name="PASSWORD_CRED_GRANT_ALLOWED" type="BOOLEAN"/>
             <column name="PASSWORD_POLICY" type="VARCHAR(255)"/>
             <column name="PRIVATE_KEY" type="VARCHAR(2048)"/>
             <column name="PUBLIC_KEY" type="VARCHAR(2048)"/>
-            <column name="REGISTRATION_ALLOWED" type="BOOLEAN(1)"/>
-            <column name="REMEMBER_ME" type="BOOLEAN(1)"/>
-            <column name="RESET_PASSWORD_ALLOWED" type="BOOLEAN(1)"/>
-            <column name="SOCIAL" type="BOOLEAN(1)"/>
+            <column name="REGISTRATION_ALLOWED" type="BOOLEAN"/>
+            <column name="REMEMBER_ME" type="BOOLEAN"/>
+            <column name="RESET_PASSWORD_ALLOWED" type="BOOLEAN"/>
+            <column name="SOCIAL" type="BOOLEAN"/>
             <column name="SSL_REQUIRED" type="VARCHAR(255)"/>
-            <column name="SSO_IDLE_TIMEOUT" type="INT(10)"/>
-            <column name="SSO_MAX_LIFESPAN" type="INT(10)"/>
-            <column name="UPDATE_PROFILE_ON_SOC_LOGIN" type="BOOLEAN(1)"/>
-            <column name="VERIFY_EMAIL" type="BOOLEAN(1)"/>
+            <column name="SSO_IDLE_TIMEOUT" type="INT"/>
+            <column name="SSO_MAX_LIFESPAN" type="INT"/>
+            <column name="UPDATE_PROFILE_ON_SOC_LOGIN" type="BOOLEAN"/>
+            <column name="VERIFY_EMAIL" type="BOOLEAN"/>
             <column name="MASTER_ADMIN_APP" type="VARCHAR(36)"/>
         </createTable>
         <createTable tableName="REALM_APPLICATION">
@@ -169,8 +169,8 @@
                 <constraints nullable="false"/>
             </column>
             <column name="FORM_LABEL" type="VARCHAR(255)"/>
-            <column name="INPUT" type="BOOLEAN(1)"/>
-            <column name="SECRET" type="BOOLEAN(1)"/>
+            <column name="INPUT" type="BOOLEAN"/>
+            <column name="SECRET" type="BOOLEAN"/>
             <column name="REALM_ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
@@ -214,10 +214,10 @@
             <column name="USERNAME" type="VARCHAR(200)">
                 <constraints nullable="false"/>
             </column>
-            <column name="FAILED_LOGIN_NOT_BEFORE" type="INT(10)"/>
-            <column name="LAST_FAILURE" type="BIGINT(19)"/>
+            <column name="FAILED_LOGIN_NOT_BEFORE" type="INT"/>
+            <column name="LAST_FAILURE" type="BIGINT"/>
             <column name="LAST_IP_FAILURE" type="VARCHAR(255)"/>
-            <column name="NUM_FAILURES" type="INT(10)"/>
+            <column name="NUM_FAILURES" type="INT"/>
         </createTable>
         <createTable tableName="USER_ATTRIBUTE">
             <column name="NAME" type="VARCHAR(255)">
@@ -234,13 +234,13 @@
             </column>
             <column name="EMAIL" type="VARCHAR(255)"/>
             <column name="EMAIL_CONSTRAINT" type="VARCHAR(255)"/>
-            <column name="EMAIL_VERIFIED" type="BOOLEAN(1)"/>
-            <column name="ENABLED" type="BOOLEAN(1)"/>
+            <column name="EMAIL_VERIFIED" type="BOOLEAN"/>
+            <column name="ENABLED" type="BOOLEAN"/>
             <column name="FEDERATION_LINK" type="VARCHAR(255)"/>
             <column name="FIRST_NAME" type="VARCHAR(255)"/>
             <column name="LAST_NAME" type="VARCHAR(255)"/>
             <column name="REALM_ID" type="VARCHAR(255)"/>
-            <column name="TOTP" type="BOOLEAN(1)"/>
+            <column name="TOTP" type="BOOLEAN"/>
             <column name="USERNAME" type="VARCHAR(255)"/>
         </createTable>
         <createTable tableName="USER_FEDERATION_CONFIG">
@@ -256,16 +256,16 @@
             <column name="ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
-            <column name="CHANGED_SYNC_PERIOD" type="INT(10)"/>
+            <column name="CHANGED_SYNC_PERIOD" type="INT"/>
             <column name="DISPLAY_NAME" type="VARCHAR(255)"/>
-            <column name="FULL_SYNC_PERIOD" type="INT(10)"/>
-            <column name="LAST_SYNC" type="INT(10)"/>
-            <column name="PRIORITY" type="INT(10)"/>
+            <column name="FULL_SYNC_PERIOD" type="INT"/>
+            <column name="LAST_SYNC" type="INT"/>
+            <column name="PRIORITY" type="INT"/>
             <column name="PROVIDER_NAME" type="VARCHAR(255)"/>
             <column name="REALM_ID" type="VARCHAR(36)"/>
         </createTable>
         <createTable tableName="USER_REQUIRED_ACTION">
-            <column name="ACTION" type="INT(10)">
+            <column name="ACTION" type="INT">
                 <constraints nullable="false"/>
             </column>
             <column name="USER_ID" type="VARCHAR(36)">
@@ -286,11 +286,11 @@
             </column>
             <column name="AUTH_METHOD" type="VARCHAR(255)"/>
             <column name="IP_ADDRESS" type="VARCHAR(255)"/>
-            <column name="LAST_SESSION_REFRESH" type="INT(10)"/>
+            <column name="LAST_SESSION_REFRESH" type="INT"/>
             <column name="LOGIN_USERNAME" type="VARCHAR(255)"/>
             <column name="REALM_ID" type="VARCHAR(255)"/>
-            <column name="REMEMBER_ME" type="BOOLEAN(1)"/>
-            <column name="STARTED" type="INT(10)"/>
+            <column name="REMEMBER_ME" type="BOOLEAN"/>
+            <column name="STARTED" type="INT"/>
             <column name="USER_ID" type="VARCHAR(255)"/>
         </createTable>
         <createTable tableName="USER_SOCIAL_LINK">
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.1.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.1.0.Beta1.xml
index 98eecbd..3f95c43 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.1.0.Beta1.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.1.0.Beta1.xml
@@ -23,6 +23,15 @@
                 <constraints nullable="false"/>
             </column>
         </createTable>
+        <createTable tableName="APP_NODE_REGISTRATIONS">
+            <column name="APPLICATION_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="VALUE" type="INT"/>
+            <column name="NAME" type="VARCHAR(255)">
+                <constraints nullable="false"/>
+            </column>
+        </createTable>
         <addColumn tableName="CLIENT_SESSION">
             <column name="AUTH_METHOD" type="VARCHAR(255)"/>
         </addColumn>
@@ -35,9 +44,14 @@
         <addColumn tableName="REALM">
             <column name="CERTIFICATE" type="VARCHAR(2048)"/>
         </addColumn>
+        <addColumn tableName="CLIENT">
+            <column name="NODE_REREG_TIMEOUT" type="INT"/>
+        </addColumn>
         <addPrimaryKey columnNames="CLIENT_ID, NAME" constraintName="CONSTRAINT_3C" tableName="CLIENT_ATTRIBUTES"/>
         <addPrimaryKey columnNames="CLIENT_SESSION, NAME" constraintName="CONSTRAINT_5E" tableName="CLIENT_SESSION_NOTE"/>
+        <addPrimaryKey columnNames="APPLICATION_ID, NAME" constraintName="CONSTRAINT_84" tableName="APP_NODE_REGISTRATIONS"/>
         <addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="CLIENT_ATTRIBUTES" constraintName="FK3C47C64BEACCA966" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="CLIENT"/>
         <addForeignKeyConstraint baseColumnNames="CLIENT_SESSION" baseTableName="CLIENT_SESSION_NOTE" constraintName="FK5EDFB00FF51C2736" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="CLIENT_SESSION"/>
+        <addForeignKeyConstraint baseColumnNames="APPLICATION_ID" baseTableName="APP_NODE_REGISTRATIONS" constraintName="FK8454723BA992F594" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="CLIENT"/>
     </changeSet>
 </databaseChangeLog>
\ No newline at end of file
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBObjectToMapMapper.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBObjectToMapMapper.java
index cfcf9bb..1de0867 100644
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBObjectToMapMapper.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBObjectToMapMapper.java
@@ -8,7 +8,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 /**
- * For now, there is support just for convert to Map<String, String>
+ * For now, there is support just for convert to Map<String, simpleType>
  *
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
@@ -18,10 +18,10 @@ public class BasicDBObjectToMapMapper implements Mapper<BasicDBObject, Map> {
     public Map convertObject(MapperContext<BasicDBObject, Map> context) {
         BasicDBObject dbObjectToConvert = context.getObjectToConvert();
 
-        HashMap<String, String> result = new HashMap<String, String>();
+        HashMap<String, Object> result = new HashMap<String, Object>();
         for (Map.Entry<String, Object> entry : dbObjectToConvert.entrySet()) {
             String key = entry.getKey();
-            String value = (String)entry.getValue();
+            Object value = entry.getValue();
 
             if (key.contains(MapMapper.DOT_PLACEHOLDER)) {
                 key = key.replaceAll(MapMapper.DOT_PLACEHOLDER, ".");
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/MapMapper.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/MapMapper.java
index 80cf9d7..988ce34 100644
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/MapMapper.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/MapMapper.java
@@ -8,7 +8,7 @@ import java.util.Map;
 import java.util.Set;
 
 /**
- * For now, we support just convert from Map<String, String>
+ * For now, we support just convert from Map<String, simpleType>
  *
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
@@ -31,7 +31,7 @@ public class MapMapper<T extends Map> implements Mapper<T, BasicDBObject> {
         Set<Map.Entry> entries = objectToConvert.entrySet();
         for (Map.Entry entry : entries) {
             String key = (String)entry.getKey();
-            String value = (String)entry.getValue();
+            Object value = entry.getValue();
 
             if (key.contains(".")) {
                 key = key.replaceAll("\\.", DOT_PLACEHOLDER);
diff --git a/core/src/main/java/org/keycloak/adapters/AdapterConstants.java b/core/src/main/java/org/keycloak/adapters/AdapterConstants.java
index a4f7f51..ddc68aa 100755
--- a/core/src/main/java/org/keycloak/adapters/AdapterConstants.java
+++ b/core/src/main/java/org/keycloak/adapters/AdapterConstants.java
@@ -20,8 +20,11 @@ public interface AdapterConstants {
     String AUTH_DATA_PARAM_NAME = "org.keycloak.json.adapterConfig";
 
     // Attribute passed in codeToToken request from adapter to Keycloak and saved in ClientSession. Contains ID of HttpSession on adapter
-    public static final String HTTP_SESSION_ID = "http_session_id";
+    public static final String APPLICATION_SESSION_STATE = "application_session_state";
 
     // Attribute passed in codeToToken request from adapter to Keycloak and saved in ClientSession. Contains hostname of adapter where HttpSession is served
-    public static final String HTTP_SESSION_HOST = "http_session_host";
+    public static final String APPLICATION_SESSION_HOST = "application_session_host";
+
+    // Attribute passed in registerNode request for register new application cluster node once he joined cluster
+    public static final String APPLICATION_CLUSTER_HOST = "application_cluster_host";
 }
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
index 7f62fd1..778f9f5 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
@@ -17,7 +17,8 @@ import org.codehaus.jackson.annotate.JsonPropertyOrder;
         "connection-pool-size",
         "allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",
         "client-keystore", "client-keystore-password", "client-key-password",
-        "auth-server-url-for-backend-requests", "always-refresh-token"
+        "auth-server-url-for-backend-requests", "always-refresh-token",
+        "register-node-at-startup", "register-node-period"
 })
 public class AdapterConfig extends BaseAdapterConfig {
 
@@ -41,6 +42,10 @@ public class AdapterConfig extends BaseAdapterConfig {
     protected String authServerUrlForBackendRequests;
     @JsonProperty("always-refresh-token")
     protected boolean alwaysRefreshToken = false;
+    @JsonProperty("register-node-at-startup")
+    protected boolean registerNodeAtStartup = false;
+    @JsonProperty("register-node-period")
+    protected int registerNodePeriod = -1;
 
     public boolean isAllowAnyHostname() {
         return allowAnyHostname;
@@ -121,4 +126,20 @@ public class AdapterConfig extends BaseAdapterConfig {
     public void setAlwaysRefreshToken(boolean alwaysRefreshToken) {
         this.alwaysRefreshToken = alwaysRefreshToken;
     }
+
+    public boolean isRegisterNodeAtStartup() {
+        return registerNodeAtStartup;
+    }
+
+    public void setRegisterNodeAtStartup(boolean registerNodeAtStartup) {
+        this.registerNodeAtStartup = registerNodeAtStartup;
+    }
+
+    public int getRegisterNodePeriod() {
+        return registerNodePeriod;
+    }
+
+    public void setRegisterNodePeriod(int registerNodePeriod) {
+        this.registerNodePeriod = registerNodePeriod;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
index 5a3a8fd..faee3c8 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
@@ -25,7 +25,8 @@ public class ApplicationRepresentation {
     protected String protocol;
     protected Map<String, String> attributes;
     protected Boolean fullScopeAllowed;
-
+    protected Integer nodeReRegistrationTimeout;
+    protected Map<String, Integer> registeredNodes;
 
     public String getId() {
         return id;
@@ -162,4 +163,20 @@ public class ApplicationRepresentation {
     public void setAttributes(Map<String, String> attributes) {
         this.attributes = attributes;
     }
+
+    public Integer getNodeReRegistrationTimeout() {
+        return nodeReRegistrationTimeout;
+    }
+
+    public void setNodeReRegistrationTimeout(Integer nodeReRegistrationTimeout) {
+        this.nodeReRegistrationTimeout = nodeReRegistrationTimeout;
+    }
+
+    public Map<String, Integer> getRegisteredNodes() {
+        return registeredNodes;
+    }
+
+    public void setRegisteredNodes(Map<String, Integer> registeredNodes) {
+        this.registeredNodes = registeredNodes;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/ServiceUrlConstants.java b/core/src/main/java/org/keycloak/ServiceUrlConstants.java
index 7ff3254..90e7170 100755
--- a/core/src/main/java/org/keycloak/ServiceUrlConstants.java
+++ b/core/src/main/java/org/keycloak/ServiceUrlConstants.java
@@ -13,5 +13,7 @@ public interface ServiceUrlConstants {
     public static final String TOKEN_SERVICE_DIRECT_GRANT_PATH = "/realms/{realm-name}/protocol/openid-connect/grants/access";
     public static final String ACCOUNT_SERVICE_PATH = "/realms/{realm-name}/account";
     public static final String REALM_INFO_PATH = "/realms/{realm-name}";
+    public static final String CLIENTS_MANAGEMENT_REGISTER_NODE_PATH = "/realms/{realm-name}/clients-managements/register-node";
+    public static final String CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH = "/realms/{realm-name}/clients-managements/unregister-node";
 
 }
diff --git a/events/api/src/main/java/org/keycloak/events/EventType.java b/events/api/src/main/java/org/keycloak/events/EventType.java
index 049d001..bdfdb0e 100755
--- a/events/api/src/main/java/org/keycloak/events/EventType.java
+++ b/events/api/src/main/java/org/keycloak/events/EventType.java
@@ -42,6 +42,7 @@ public enum EventType {
     SEND_RESET_PASSWORD_ERROR,
     SOCIAL_LOGIN,
     SOCIAL_LOGIN_ERROR,
-    INVALID_SIGNATURE_ERROR
-
+    INVALID_SIGNATURE_ERROR,
+    REGISTER_NODE,
+    UNREGISTER_NODE
 }
diff --git a/examples/demo-template/customer-app/src/main/java/org/keycloak/example/AdminClient.java b/examples/demo-template/customer-app/src/main/java/org/keycloak/example/AdminClient.java
index 611292b..c1ccc38 100755
--- a/examples/demo-template/customer-app/src/main/java/org/keycloak/example/AdminClient.java
+++ b/examples/demo-template/customer-app/src/main/java/org/keycloak/example/AdminClient.java
@@ -43,7 +43,7 @@ public class AdminClient {
         HttpClient client = new HttpClientBuilder()
                 .disableTrustManager().build();
         try {
-            HttpGet get = new HttpGet(AdapterUtils.getBaseUrl(req.getRequestURL().toString(), session) + "/auth/admin/realms/demo/roles");
+            HttpGet get = new HttpGet(AdapterUtils.getOrigin(req.getRequestURL().toString(), session) + "/auth/admin/realms/demo/roles");
             get.addHeader("Authorization", "Bearer " + session.getTokenString());
             try {
                 HttpResponse response = client.execute(get);
diff --git a/examples/demo-template/customer-app/src/main/java/org/keycloak/example/CustomerDatabaseClient.java b/examples/demo-template/customer-app/src/main/java/org/keycloak/example/CustomerDatabaseClient.java
index ec1ed04..0cb400f 100755
--- a/examples/demo-template/customer-app/src/main/java/org/keycloak/example/CustomerDatabaseClient.java
+++ b/examples/demo-template/customer-app/src/main/java/org/keycloak/example/CustomerDatabaseClient.java
@@ -50,7 +50,7 @@ public class CustomerDatabaseClient {
         HttpClient client = new HttpClientBuilder()
                 .disableTrustManager().build();
         try {
-            HttpGet get = new HttpGet(AdapterUtils.getBaseUrl(req.getRequestURL().toString(), session) + "/database/customers");
+            HttpGet get = new HttpGet(AdapterUtils.getOrigin(req.getRequestURL().toString(), session) + "/database/customers");
             get.addHeader("Authorization", "Bearer " + session.getTokenString());
             try {
                 HttpResponse response = client.execute(get);
diff --git a/examples/demo-template/product-app/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java b/examples/demo-template/product-app/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java
index 9557f28..c8e9cf0 100755
--- a/examples/demo-template/product-app/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java
+++ b/examples/demo-template/product-app/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java
@@ -40,7 +40,7 @@ public class ProductDatabaseClient
         HttpClient client = new HttpClientBuilder()
                 .disableTrustManager().build();
         try {
-            HttpGet get = new HttpGet(AdapterUtils.getBaseUrl(req.getRequestURL().toString(), session) + "/database/products");
+            HttpGet get = new HttpGet(AdapterUtils.getOrigin(req.getRequestURL().toString(), session) + "/database/products");
             get.addHeader("Authorization", "Bearer " + session.getTokenString());
             try {
                 HttpResponse response = client.execute(get);
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
index 69e61aa..95e31d7 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
@@ -158,6 +158,16 @@ public class AdapterDeploymentContext {
         }
 
         @Override
+        public String getRegisterNodeUrl() {
+            return (this.registerNodeUrl != null) ? this.registerNodeUrl : delegate.getRegisterNodeUrl();
+        }
+
+        @Override
+        public String getUnregisterNodeUrl() {
+            return (this.unregisterNodeUrl != null) ? this.unregisterNodeUrl : delegate.getUnregisterNodeUrl();
+        }
+
+        @Override
         public String getResourceName() {
             return delegate.getResourceName();
         }
@@ -336,6 +346,26 @@ public class AdapterDeploymentContext {
         public void setAlwaysRefreshToken(boolean alwaysRefreshToken) {
             delegate.setAlwaysRefreshToken(alwaysRefreshToken);
         }
+
+        @Override
+        public int getRegisterNodePeriod() {
+            return delegate.getRegisterNodePeriod();
+        }
+
+        @Override
+        public void setRegisterNodePeriod(int registerNodePeriod) {
+            delegate.setRegisterNodePeriod(registerNodePeriod);
+        }
+
+        @Override
+        public void setRegisterNodeAtStartup(boolean registerNodeAtStartup) {
+            delegate.setRegisterNodeAtStartup(registerNodeAtStartup);
+        }
+
+        @Override
+        public boolean isRegisterNodeAtStartup() {
+            return delegate.isRegisterNodeAtStartup();
+        }
     }
 
     protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java
index 0048d6a..e41a8b5 100644
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java
@@ -8,7 +8,7 @@ import org.keycloak.util.UriUtils;
  */
 public class AdapterUtils {
 
-    public static String getBaseUrl(String browserRequestURL, KeycloakSecurityContext session) {
+    public static String getOrigin(String browserRequestURL, KeycloakSecurityContext session) {
         if (session instanceof RefreshableKeycloakSecurityContext) {
             KeycloakDeployment deployment = ((RefreshableKeycloakSecurityContext)session).getDeployment();
             switch (deployment.getRelativeUrls()) {
@@ -16,10 +16,9 @@ public class AdapterUtils {
                     // Resolve baseURI from the request
                     return UriUtils.getOrigin(browserRequestURL);
                 case BROWSER_ONLY:
+                case NEVER:
                     // Resolve baseURI from the codeURL (This is already non-relative and based on our hostname)
                     return UriUtils.getOrigin(deployment.getCodeUrl());
-                case NEVER:
-                    return "";
                 default:
                     return "";
             }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
index c376112..1095b47 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
@@ -31,6 +31,8 @@ public class KeycloakDeployment {
     protected String refreshUrl;
     protected KeycloakUriBuilder logoutUrl;
     protected String accountUrl;
+    protected String registerNodeUrl;
+    protected String unregisterNodeUrl;
 
     protected String resourceName;
     protected boolean bearerOnly;
@@ -48,6 +50,8 @@ public class KeycloakDeployment {
     protected String corsAllowedMethods;
     protected boolean exposeToken;
     protected boolean alwaysRefreshToken;
+    protected boolean registerNodeAtStartup;
+    protected int registerNodePeriod;
     protected volatile int notBefore;
 
     public KeycloakDeployment() {
@@ -136,6 +140,8 @@ public class KeycloakDeployment {
         accountUrl = authUrlBuilder.clone().path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH).build(getRealm()).toString();
         realmInfoUrl = authUrlBuilder.clone().path(ServiceUrlConstants.REALM_INFO_PATH).build(getRealm()).toString();
         codeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_ACCESS_CODE_PATH).build(getRealm()).toString();
+        registerNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_REGISTER_NODE_PATH).build(getRealm()).toString();
+        unregisterNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH).build(getRealm()).toString();
     }
 
     public RelativeUrlsUsed getRelativeUrls() {
@@ -166,6 +172,14 @@ public class KeycloakDeployment {
         return accountUrl;
     }
 
+    public String getRegisterNodeUrl() {
+        return registerNodeUrl;
+    }
+
+    public String getUnregisterNodeUrl() {
+        return unregisterNodeUrl;
+    }
+
     public void setResourceName(String resourceName) {
         this.resourceName = resourceName;
     }
@@ -289,4 +303,20 @@ public class KeycloakDeployment {
     public void setAlwaysRefreshToken(boolean alwaysRefreshToken) {
         this.alwaysRefreshToken = alwaysRefreshToken;
     }
+
+    public boolean isRegisterNodeAtStartup() {
+        return registerNodeAtStartup;
+    }
+
+    public void setRegisterNodeAtStartup(boolean registerNodeAtStartup) {
+        this.registerNodeAtStartup = registerNodeAtStartup;
+    }
+
+    public int getRegisterNodePeriod() {
+        return registerNodePeriod;
+    }
+
+    public void setRegisterNodePeriod(int registerNodePeriod) {
+        this.registerNodePeriod = registerNodePeriod;
+    }
 }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
index 6e5c29f..cbb5fef 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
@@ -61,11 +61,13 @@ public class KeycloakDeploymentBuilder {
 
         deployment.setBearerOnly(adapterConfig.isBearerOnly());
         deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken());
+        deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
+        deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
 
         if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
             throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
         }
-        if (realmKeyPem == null || !deployment.isBearerOnly()) {
+        if (realmKeyPem == null || !deployment.isBearerOnly() || deployment.isRegisterNodeAtStartup() || deployment.getRegisterNodePeriod() != -1) {
             deployment.setClient(new HttpClientBuilder().build(adapterConfig));
         }
         if (adapterConfig.getAuthServerUrl() == null && (!deployment.isBearerOnly() || realmKeyPem == null)) {
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/NodesRegistrationLifecycle.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/NodesRegistrationLifecycle.java
new file mode 100644
index 0000000..b344702
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/NodesRegistrationLifecycle.java
@@ -0,0 +1,120 @@
+package org.keycloak.adapters;
+
+import java.io.IOException;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import org.jboss.logging.Logger;
+import org.keycloak.enums.RelativeUrlsUsed;
+import org.keycloak.util.HostUtils;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class NodesRegistrationLifecycle {
+
+    private static final Logger log = Logger.getLogger(NodesRegistrationLifecycle.class);
+
+    private final KeycloakDeployment deployment;
+    private final Timer timer;
+
+    // True if at least one event was successfully sent
+    private volatile boolean registered = false;
+
+    public NodesRegistrationLifecycle(KeycloakDeployment deployment) {
+        this.deployment = deployment;
+        this.timer =  new Timer();
+    }
+
+    public void start() {
+        if (!deployment.isRegisterNodeAtStartup() && deployment.getRegisterNodePeriod() <= 0) {
+            log.info("Skip registration of cluster nodes at startup");
+            return;
+        }
+
+        if (deployment.getRelativeUrls() == RelativeUrlsUsed.ALL_REQUESTS) {
+            log.warn("Skip registration of cluster nodes at startup as Keycloak node can't be contacted. Make sure to not use relative URI in adapters configuration!");
+            return;
+        }
+
+        if (deployment.isRegisterNodeAtStartup()) {
+            boolean success = sendRegistrationEvent();
+            if (!success) {
+                throw new IllegalStateException("Failed to register node to keycloak at startup");
+            }
+        }
+
+        if (deployment.getRegisterNodePeriod() > 0) {
+            addPeriodicListener();
+        }
+    }
+
+    public void stop() {
+        removePeriodicListener();
+        if (registered) {
+            sendUnregistrationEvent();
+        }
+    }
+
+    protected void addPeriodicListener() {
+        TimerTask task = new TimerTask() {
+
+            @Override
+            public void run() {
+                sendRegistrationEvent();
+            }
+        };
+
+        long interval = deployment.getRegisterNodePeriod() * 1000;
+        log.info("Setup of periodic re-registration event sending each " + interval + " ms");
+        timer.schedule(task, interval, interval);
+    }
+
+    protected void removePeriodicListener() {
+        timer.cancel();
+    }
+
+    protected boolean sendRegistrationEvent() {
+        log.info("Sending registration event right now");
+
+        String host = HostUtils.getIpAddress();
+        try {
+            ServerRequest.invokeRegisterNode(deployment, host);
+            log.infof("Node '%s' successfully registered in Keycloak", host);
+            registered = true;
+            return true;
+        } catch (ServerRequest.HttpFailure failure) {
+            log.error("failed to register node to keycloak");
+            log.error("status from server: " + failure.getStatus());
+            if (failure.getError() != null) {
+                log.error("   " + failure.getError());
+            }
+            return false;
+        } catch (IOException e) {
+            log.error("failed to register node to keycloak", e);
+            return false;
+        }
+    }
+
+    protected boolean sendUnregistrationEvent() {
+        log.info("Sending UNregistration event right now");
+
+        String host = HostUtils.getIpAddress();
+        try {
+            ServerRequest.invokeUnregisterNode(deployment, host);
+            log.infof("Node '%s' successfully unregistered from Keycloak", host);
+            return true;
+        } catch (ServerRequest.HttpFailure failure) {
+            log.error("failed to unregister node from keycloak");
+            log.error("status from server: " + failure.getStatus());
+            if (failure.getError() != null) {
+                log.error("   " + failure.getError());
+            }
+            return false;
+        } catch (IOException e) {
+            log.error("failed to unregister node from keycloak", e);
+            return false;
+        }
+    }
+
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
index b20697d..438ad63 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
@@ -101,8 +101,8 @@ public class ServerRequest {
         formparams.add(new BasicNameValuePair(OAuth2Constants.CODE, code));
         formparams.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, redirectUri));
         if (sessionId != null) {
-            formparams.add(new BasicNameValuePair(AdapterConstants.HTTP_SESSION_ID, sessionId));
-            formparams.add(new BasicNameValuePair(AdapterConstants.HTTP_SESSION_HOST, HostUtils.getIpAddress()));
+            formparams.add(new BasicNameValuePair(AdapterConstants.APPLICATION_SESSION_STATE, sessionId));
+            formparams.add(new BasicNameValuePair(AdapterConstants.APPLICATION_SESSION_HOST, HostUtils.getIpAddress()));
         }
         HttpResponse response = null;
         HttpPost post = new HttpPost(codeUrl);
@@ -212,6 +212,46 @@ public class ServerRequest {
         }
     }
 
+    public static void invokeRegisterNode(KeycloakDeployment deployment, String host) throws HttpFailure, IOException {
+        String registerNodeUrl = deployment.getRegisterNodeUrl();
+        String client_id = deployment.getResourceName();
+        Map<String, String> credentials = deployment.getResourceCredentials();
+        HttpClient client = deployment.getClient();
+
+        invokeClientManagementRequest(client, host, registerNodeUrl, client_id, credentials);
+    }
+
+    public static void invokeUnregisterNode(KeycloakDeployment deployment, String host) throws HttpFailure, IOException {
+        String unregisterNodeUrl = deployment.getUnregisterNodeUrl();
+        String client_id = deployment.getResourceName();
+        Map<String, String> credentials = deployment.getResourceCredentials();
+        HttpClient client = deployment.getClient();
+
+        invokeClientManagementRequest(client, host, unregisterNodeUrl, client_id, credentials);
+    }
+
+    public static void invokeClientManagementRequest(HttpClient client, String host, String endpointUrl, String clientId, Map<String, String> credentials) throws HttpFailure, IOException {
+        List<NameValuePair> formparams = new ArrayList<NameValuePair>();
+        formparams.add(new BasicNameValuePair(AdapterConstants.APPLICATION_CLUSTER_HOST, host));
+
+        HttpPost post = new HttpPost(endpointUrl);
+
+        String clientSecret = credentials.get(CredentialRepresentation.SECRET);
+        if (clientSecret != null) {
+            String authorization = BasicAuthHelper.createHeader(clientId, clientSecret);
+            post.setHeader("Authorization", authorization);
+        }
+
+        UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
+        post.setEntity(form);
+        HttpResponse response = client.execute(post);
+        int status = response.getStatusLine().getStatusCode();
+        if (status != 204) {
+            HttpEntity entity = response.getEntity();
+            error(status, entity);
+        }
+    }
+
     public static void error(int status, HttpEntity entity) throws HttpFailure, IOException {
         String body = null;
         if (entity != null) {
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
index 9bf2c42..ab4fb3f 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
@@ -21,6 +21,7 @@ import org.keycloak.adapters.AuthOutcome;
 import org.keycloak.adapters.HttpFacade;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.adapters.NodesRegistrationLifecycle;
 import org.keycloak.adapters.PreAuthActionsHandler;
 import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
 
@@ -46,6 +47,7 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
     private static final Logger log = Logger.getLogger(KeycloakAuthenticatorValve.class);
     protected CatalinaUserSessionManagement userSessionManagement = new CatalinaUserSessionManagement();
     protected AdapterDeploymentContext deploymentContext;
+    protected NodesRegistrationLifecycle nodesRegistrationLifecycle;
 
 
     @Override
@@ -74,7 +76,11 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
 
     @Override
     public void lifecycleEvent(LifecycleEvent event) {
-        if (event.getType() == Lifecycle.AFTER_START_EVENT) init();
+        if (event.getType() == Lifecycle.AFTER_START_EVENT) {
+            init();
+        } else if (event.getType() == Lifecycle.BEFORE_STOP_EVENT) {
+            beforeStop();
+        }
     }
 
     private static InputStream getJSONFromServletContext(ServletContext servletContext) {
@@ -119,6 +125,13 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
         context.getServletContext().setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
         AuthenticatedActionsValve actions = new AuthenticatedActionsValve(deploymentContext, getNext(), getContainer(), getController());
         setNext(actions);
+
+        nodesRegistrationLifecycle = new NodesRegistrationLifecycle(kd);
+        nodesRegistrationLifecycle.start();
+    }
+
+    protected void beforeStop() {
+        nodesRegistrationLifecycle.stop();
     }
 
     @Override
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java
index 208882c..de12389 100755
--- a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java
@@ -20,6 +20,7 @@ import org.keycloak.adapters.AuthOutcome;
 import org.keycloak.adapters.HttpFacade;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.adapters.NodesRegistrationLifecycle;
 import org.keycloak.adapters.PreAuthActionsHandler;
 import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
 import org.keycloak.adapters.ServerRequest;
@@ -47,6 +48,7 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
 	private final static Logger log = Logger.getLogger(""+KeycloakAuthenticatorValve.class);
 	protected CatalinaUserSessionManagement userSessionManagement = new CatalinaUserSessionManagement();
     protected AdapterDeploymentContext deploymentContext;
+    protected NodesRegistrationLifecycle nodesRegistrationLifecycle;
 
     @Override
     public void lifecycleEvent(LifecycleEvent event) {
@@ -58,6 +60,8 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
             }
         } else if (Lifecycle.AFTER_START_EVENT.equals(event.getType())) {
         	initInternal();
+        } else if (event.getType() == Lifecycle.BEFORE_STOP_EVENT) {
+            beforeStop();
         }
     }
     
@@ -99,6 +103,13 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
         context.getServletContext().setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
         AuthenticatedActionsValve actions = new AuthenticatedActionsValve(deploymentContext, getNext(), getContainer(), getObjectName());
         setNext(actions);
+
+        nodesRegistrationLifecycle = new NodesRegistrationLifecycle(kd);
+        nodesRegistrationLifecycle.start();
+    }
+
+    protected void beforeStop() {
+        nodesRegistrationLifecycle.stop();
     }
 
     private static InputStream getJSONFromServletContext(ServletContext servletContext) {
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
index 4c02456..9919dfa 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
@@ -25,13 +25,18 @@ import io.undertow.server.handlers.form.FormParserFactory;
 import io.undertow.servlet.ServletExtension;
 import io.undertow.servlet.api.AuthMethodConfig;
 import io.undertow.servlet.api.DeploymentInfo;
+import io.undertow.servlet.api.InstanceFactory;
+import io.undertow.servlet.api.InstanceHandle;
+import io.undertow.servlet.api.ListenerInfo;
 import io.undertow.servlet.api.LoginConfig;
 import io.undertow.servlet.api.ServletSessionConfig;
+import io.undertow.servlet.util.ImmediateInstanceHandle;
 import org.jboss.logging.Logger;
 import org.keycloak.adapters.AdapterConstants;
 import org.keycloak.adapters.AdapterDeploymentContext;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.adapters.NodesRegistrationLifecycle;
 
 import javax.servlet.ServletContext;
 import java.io.ByteArrayInputStream;
@@ -96,7 +101,7 @@ public class KeycloakServletExtension implements ServletExtension {
         }
         log.debug("KeycloakServletException initialization");
         InputStream is = getConfigInputStream(servletContext);
-        KeycloakDeployment deployment = null;
+        final KeycloakDeployment deployment;
         if (is == null) {
             log.warn("No adapter configuration.  Keycloak is unconfigured and will deny all requests.");
             deployment = new KeycloakDeployment();
@@ -143,6 +148,17 @@ public class KeycloakServletExtension implements ServletExtension {
         ServletSessionConfig cookieConfig = new ServletSessionConfig();
         cookieConfig.setPath(deploymentInfo.getContextPath());
         deploymentInfo.setServletSessionConfig(cookieConfig);
+
+        deploymentInfo.addListener(new ListenerInfo(UndertowNodesRegistrationLifecycleWrapper.class, new InstanceFactory<UndertowNodesRegistrationLifecycleWrapper>() {
+
+            @Override
+            public InstanceHandle<UndertowNodesRegistrationLifecycleWrapper> createInstance() throws InstantiationException {
+                NodesRegistrationLifecycle nodesRegistration = new NodesRegistrationLifecycle(deployment);
+                UndertowNodesRegistrationLifecycleWrapper listener = new UndertowNodesRegistrationLifecycleWrapper(nodesRegistration);
+                return new ImmediateInstanceHandle<UndertowNodesRegistrationLifecycleWrapper>(listener);
+            }
+
+        }));
     }
 
     protected ServletKeycloakAuthMech createAuthenticationMechanism(DeploymentInfo deploymentInfo, AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement userSessionManagement) {
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowNodesRegistrationLifecycleWrapper.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowNodesRegistrationLifecycleWrapper.java
new file mode 100644
index 0000000..5c97f6b
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowNodesRegistrationLifecycleWrapper.java
@@ -0,0 +1,28 @@
+package org.keycloak.adapters.undertow;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.keycloak.adapters.NodesRegistrationLifecycle;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UndertowNodesRegistrationLifecycleWrapper implements ServletContextListener {
+
+    private final NodesRegistrationLifecycle delegate;
+
+    public UndertowNodesRegistrationLifecycleWrapper(NodesRegistrationLifecycle delegate) {
+        this.delegate = delegate;
+    }
+
+    @Override
+    public void contextInitialized(ServletContextEvent sce) {
+        delegate.start();
+    }
+
+    @Override
+    public void contextDestroyed(ServletContextEvent sce) {
+        delegate.stop();
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/ApplicationModel.java b/model/api/src/main/java/org/keycloak/models/ApplicationModel.java
index 2433fad..14cd081 100755
--- a/model/api/src/main/java/org/keycloak/models/ApplicationModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ApplicationModel.java
@@ -1,6 +1,7 @@
 package org.keycloak.models;
 
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -37,4 +38,20 @@ public interface ApplicationModel extends RoleContainerModel, ClientModel {
     boolean isBearerOnly();
     void setBearerOnly(boolean only);
 
+    int getNodeReRegistrationTimeout();
+
+    void setNodeReRegistrationTimeout(int timeout);
+
+    Map<String, Integer> getRegisteredNodes();
+
+    /**
+     * Register node or just update the 'lastReRegistration' time if this node is already registered
+     *
+     * @param nodeHost
+     * @param registrationTime
+     */
+    void registerNode(String nodeHost, int registrationTime);
+
+    void unregisterNode(String nodeHost);
+
 }
diff --git a/model/api/src/main/java/org/keycloak/models/entities/ApplicationEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ApplicationEntity.java
index 95fefb9..c39ede4 100644
--- a/model/api/src/main/java/org/keycloak/models/entities/ApplicationEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/ApplicationEntity.java
@@ -2,6 +2,7 @@ package org.keycloak.models.entities;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -12,10 +13,13 @@ public class ApplicationEntity extends ClientEntity {
     private String managementUrl;
     private String baseUrl;
     private boolean bearerOnly;
+    private int nodeReRegistrationTimeout;
 
     // We are using names of defaultRoles (not ids)
     private List<String> defaultRoles = new ArrayList<String>();
 
+    private Map<String, Integer> registeredNodes;
+
     public boolean isSurrogateAuthRequired() {
         return surrogateAuthRequired;
     }
@@ -55,5 +59,21 @@ public class ApplicationEntity extends ClientEntity {
     public void setDefaultRoles(List<String> defaultRoles) {
         this.defaultRoles = defaultRoles;
     }
+
+    public int getNodeReRegistrationTimeout() {
+        return nodeReRegistrationTimeout;
+    }
+
+    public void setNodeReRegistrationTimeout(int nodeReRegistrationTimeout) {
+        this.nodeReRegistrationTimeout = nodeReRegistrationTimeout;
+    }
+
+    public Map<String, Integer> getRegisteredNodes() {
+        return registeredNodes;
+    }
+
+    public void setRegisteredNodes(Map<String, Integer> registeredNodes) {
+        this.registeredNodes = registeredNodes;
+    }
 }
 
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 0baa697..85331ac 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -222,6 +222,7 @@ public class ModelToRepresentation {
         rep.setSurrogateAuthRequired(applicationModel.isSurrogateAuthRequired());
         rep.setBaseUrl(applicationModel.getBaseUrl());
         rep.setNotBefore(applicationModel.getNotBefore());
+        rep.setNodeReRegistrationTimeout(applicationModel.getNodeReRegistrationTimeout());
 
         Set<String> redirectUris = applicationModel.getRedirectUris();
         if (redirectUris != null) {
@@ -237,6 +238,10 @@ public class ModelToRepresentation {
             rep.setDefaultRoles(applicationModel.getDefaultRoles().toArray(new String[0]));
         }
 
+        if (!applicationModel.getRegisteredNodes().isEmpty()) {
+            rep.setRegisteredNodes(new HashMap<String, Integer>(applicationModel.getRegisteredNodes()));
+        }
+
         return rep;
     }
 
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 2d45360..31e26c3 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -374,8 +374,16 @@ public class RepresentationToModel {
         if (resourceRep.isBearerOnly() != null) applicationModel.setBearerOnly(resourceRep.isBearerOnly());
         if (resourceRep.isPublicClient() != null) applicationModel.setPublicClient(resourceRep.isPublicClient());
         if (resourceRep.getProtocol() != null) applicationModel.setProtocol(resourceRep.getProtocol());
-        if (resourceRep.isFullScopeAllowed() != null) applicationModel.setFullScopeAllowed(resourceRep.isFullScopeAllowed());
-        else applicationModel.setFullScopeAllowed(true);
+        if (resourceRep.isFullScopeAllowed() != null) {
+            applicationModel.setFullScopeAllowed(resourceRep.isFullScopeAllowed());
+        } else {
+            applicationModel.setFullScopeAllowed(true);
+        }
+        if (resourceRep.getNodeReRegistrationTimeout() != null) {
+            applicationModel.setNodeReRegistrationTimeout(resourceRep.getNodeReRegistrationTimeout());
+        } else {
+            applicationModel.setNodeReRegistrationTimeout(-1);
+        }
         applicationModel.updateApplication();
 
         if (resourceRep.getNotBefore() != null) {
@@ -426,6 +434,12 @@ public class RepresentationToModel {
             }
         }
 
+        if (resourceRep.getRegisteredNodes() != null) {
+            for (Map.Entry<String, Integer> entry : resourceRep.getRegisteredNodes().entrySet()) {
+                applicationModel.registerNode(entry.getKey(), entry.getValue());
+            }
+        }
+
         if (addDefaultRoles && resourceRep.getDefaultRoles() != null) {
             applicationModel.updateDefaultRoles(resourceRep.getDefaultRoles());
         }
@@ -448,6 +462,7 @@ public class RepresentationToModel {
         if (rep.getAdminUrl() != null) resource.setManagementUrl(rep.getAdminUrl());
         if (rep.getBaseUrl() != null) resource.setBaseUrl(rep.getBaseUrl());
         if (rep.isSurrogateAuthRequired() != null) resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
+        if (rep.getNodeReRegistrationTimeout() != null) resource.setNodeReRegistrationTimeout(rep.getNodeReRegistrationTimeout());
         resource.updateApplication();
 
         if (rep.getProtocol() != null) resource.setProtocol(rep.getProtocol());
@@ -475,6 +490,12 @@ public class RepresentationToModel {
             resource.setWebOrigins(new HashSet<String>(webOrigins));
         }
 
+        if (rep.getRegisteredNodes() != null) {
+            for (Map.Entry<String, Integer> entry : rep.getRegisteredNodes().entrySet()) {
+                resource.registerNode(entry.getKey(), entry.getValue());
+            }
+        }
+
         if (rep.getClaims() != null) {
             setClaims(resource, rep.getClaims());
         }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ApplicationAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ApplicationAdapter.java
index 50e3ccc..fd23881 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ApplicationAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ApplicationAdapter.java
@@ -9,6 +9,7 @@ import org.keycloak.models.cache.entities.CachedApplication;
 
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -186,6 +187,36 @@ public class ApplicationAdapter extends ClientAdapter implements ApplicationMode
     }
 
     @Override
+    public int getNodeReRegistrationTimeout() {
+        if (updated != null) return updated.getNodeReRegistrationTimeout();
+        return cached.getNodeReRegistrationTimeout();
+    }
+
+    @Override
+    public void setNodeReRegistrationTimeout(int timeout) {
+        getDelegateForUpdate();
+        updated.setNodeReRegistrationTimeout(timeout);
+    }
+
+    @Override
+    public Map<String, Integer> getRegisteredNodes() {
+        if (updated != null) return updated.getRegisteredNodes();
+        return cached.getRegisteredNodes();
+    }
+
+    @Override
+    public void registerNode(String nodeHost, int registrationTime) {
+        getDelegateForUpdate();
+        updated.registerNode(nodeHost, registrationTime);
+    }
+
+    @Override
+    public void unregisterNode(String nodeHost) {
+        getDelegateForUpdate();
+        updated.unregisterNode(nodeHost);
+    }
+
+    @Override
     public boolean hasScope(RoleModel role) {
         if (super.hasScope(role)) {
             return true;
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedApplication.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedApplication.java
index 004c7ff..e537ea7 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedApplication.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedApplication.java
@@ -10,6 +10,7 @@ import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.TreeMap;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -22,6 +23,8 @@ public class CachedApplication extends CachedClient {
     private List<String> defaultRoles = new LinkedList<String>();
     private boolean bearerOnly;
     private Map<String, String> roles = new HashMap<String, String>();
+    private int nodeReRegistrationTimeout;
+    private Map<String, Integer> registeredNodes;
 
     public CachedApplication(RealmCache cache, RealmProvider delegate, RealmModel realm, ApplicationModel model) {
         super(cache, delegate, realm, model);
@@ -35,7 +38,8 @@ public class CachedApplication extends CachedClient {
             cache.addCachedRole(new CachedApplicationRole(id, role, realm));
         }
 
-
+        nodeReRegistrationTimeout = model.getNodeReRegistrationTimeout();
+        registeredNodes = new TreeMap<String, Integer>(model.getRegisteredNodes());
     }
 
     public boolean isSurrogateAuthRequired() {
@@ -62,4 +66,11 @@ public class CachedApplication extends CachedClient {
         return roles;
     }
 
+    public int getNodeReRegistrationTimeout() {
+        return nodeReRegistrationTimeout;
+    }
+
+    public Map<String, Integer> getRegisteredNodes() {
+        return registeredNodes;
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
index 05c4d5c..9eab924 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
@@ -9,6 +9,7 @@ import org.keycloak.models.RoleModel;
 import org.keycloak.models.jpa.entities.ApplicationEntity;
 import org.keycloak.models.jpa.entities.RoleEntity;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.util.Time;
 
 import javax.persistence.EntityManager;
 import javax.persistence.TypedQuery;
@@ -16,6 +17,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -261,6 +263,35 @@ public class ApplicationAdapter extends ClientAdapter implements ApplicationMode
     }
 
     @Override
+    public int getNodeReRegistrationTimeout() {
+        return applicationEntity.getNodeReRegistrationTimeout();
+    }
+
+    @Override
+    public void setNodeReRegistrationTimeout(int timeout) {
+        applicationEntity.setNodeReRegistrationTimeout(timeout);
+    }
+
+    @Override
+    public Map<String, Integer> getRegisteredNodes() {
+        return applicationEntity.getRegisteredNodes();
+    }
+
+    @Override
+    public void registerNode(String nodeHost, int registrationTime) {
+        Map<String, Integer> currentNodes = getRegisteredNodes();
+        currentNodes.put(nodeHost, registrationTime);
+        em.flush();
+    }
+
+    @Override
+    public void unregisterNode(String nodeHost) {
+        Map<String, Integer> currentNodes = getRegisteredNodes();
+        currentNodes.remove(nodeHost);
+        em.flush();
+    }
+
+    @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || !(o instanceof ApplicationModel)) return false;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ApplicationEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ApplicationEntity.java
index a20ca3d..48a9ff0 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ApplicationEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ApplicationEntity.java
@@ -1,14 +1,19 @@
 package org.keycloak.models.jpa.entities;
 
 import javax.persistence.CascadeType;
+import javax.persistence.CollectionTable;
 import javax.persistence.Column;
+import javax.persistence.ElementCollection;
 import javax.persistence.Entity;
 import javax.persistence.FetchType;
 import javax.persistence.JoinColumn;
 import javax.persistence.JoinTable;
+import javax.persistence.MapKeyColumn;
 import javax.persistence.OneToMany;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -29,6 +34,9 @@ public class ApplicationEntity extends ClientEntity {
     @Column(name="BEARER_ONLY")
     private boolean bearerOnly;
 
+    @Column(name="NODE_REREG_TIMEOUT")
+    private int nodeReRegistrationTimeout;
+
     @OneToMany(fetch = FetchType.EAGER, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "application")
     Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
 
@@ -36,6 +44,12 @@ public class ApplicationEntity extends ClientEntity {
     @JoinTable(name="APPLICATION_DEFAULT_ROLES", joinColumns = { @JoinColumn(name="APPLICATION_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")})
     Collection<RoleEntity> defaultRoles = new ArrayList<RoleEntity>();
 
+    @ElementCollection
+    @MapKeyColumn(name="NAME")
+    @Column(name="VALUE")
+    @CollectionTable(name="APP_NODE_REGISTRATIONS", joinColumns={ @JoinColumn(name="APPLICATION_ID") })
+    Map<String, Integer> registeredNodes = new HashMap<String, Integer>();
+
     public boolean isSurrogateAuthRequired() {
         return surrogateAuthRequired;
     }
@@ -83,4 +97,20 @@ public class ApplicationEntity extends ClientEntity {
     public void setBearerOnly(boolean bearerOnly) {
         this.bearerOnly = bearerOnly;
     }
+
+    public int getNodeReRegistrationTimeout() {
+        return nodeReRegistrationTimeout;
+    }
+
+    public void setNodeReRegistrationTimeout(int nodeReRegistrationTimeout) {
+        this.nodeReRegistrationTimeout = nodeReRegistrationTimeout;
+    }
+
+    public Map<String, Integer> getRegisteredNodes() {
+        return registeredNodes;
+    }
+
+    public void setRegisteredNodes(Map<String, Integer> registeredNodes) {
+        this.registeredNodes = registeredNodes;
+    }
 }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
index 2b6ef4c..3f84c2b 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
@@ -11,10 +11,14 @@ import org.keycloak.models.RoleModel;
 import org.keycloak.models.mongo.keycloak.entities.MongoApplicationEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
 import org.keycloak.models.mongo.utils.MongoModelUtils;
+import org.keycloak.util.Time;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -218,6 +222,41 @@ public class ApplicationAdapter extends ClientAdapter<MongoApplicationEntity> im
         updateMongoEntity();
     }
 
+    @Override
+    public int getNodeReRegistrationTimeout() {
+        return getMongoEntity().getNodeReRegistrationTimeout();
+    }
+
+    @Override
+    public void setNodeReRegistrationTimeout(int timeout) {
+        getMongoEntity().setNodeReRegistrationTimeout(timeout);
+        updateMongoEntity();
+    }
+
+    @Override
+    public Map<String, Integer> getRegisteredNodes() {
+        return getMongoEntity().getRegisteredNodes() == null ? Collections.<String, Integer>emptyMap() : Collections.unmodifiableMap(getMongoEntity().getRegisteredNodes());
+    }
+
+    @Override
+    public void registerNode(String nodeHost, int registrationTime) {
+        MongoApplicationEntity entity = getMongoEntity();
+        if (entity.getRegisteredNodes() == null) {
+            entity.setRegisteredNodes(new HashMap<String, Integer>());
+        }
+
+        entity.getRegisteredNodes().put(nodeHost, registrationTime);
+        updateMongoEntity();
+    }
+
+    @Override
+    public void unregisterNode(String nodeHost) {
+        MongoApplicationEntity entity = getMongoEntity();
+        if (entity.getRegisteredNodes() == null) return;
+
+        entity.getRegisteredNodes().remove(nodeHost);
+        updateMongoEntity();
+    }
 
     @Override
     public boolean equals(Object o) {
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index 7c6ba7b..da26379 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -137,7 +137,7 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
 
     @Override
     public Map<String, String> getAttributes() {
-        return user.getAttributes()==null ? Collections.EMPTY_MAP : Collections.unmodifiableMap(user.getAttributes());
+        return user.getAttributes()==null ? Collections.<String, String>emptyMap() : Collections.unmodifiableMap(user.getAttributes());
     }
 
     public MongoUserEntity getUser() {
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureEntity.java
index 09bfca6..e742bbc 100644
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureEntity.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureEntity.java
@@ -12,10 +12,6 @@ public class LoginFailureEntity {
     private long lastFailure;
     private String lastIPFailure;
 
-    public String getId() {
-        return realm + ":" + username;
-    }
-
     public String getUsername() {
         return username;
     }
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureKey.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureKey.java
new file mode 100644
index 0000000..f8dbe87
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureKey.java
@@ -0,0 +1,36 @@
+package org.keycloak.models.sessions.infinispan.entities;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LoginFailureKey {
+
+    private final String realm;
+    private final String username;
+
+    public LoginFailureKey(String realm, String username) {
+        this.realm = realm;
+        this.username = username;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        LoginFailureKey key = (LoginFailureKey) o;
+
+        if (realm != null ? !realm.equals(key.realm) : key.realm != null) return false;
+        if (username != null ? !username.equals(key.username) : key.username != null) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = realm != null ? realm.hashCode() : 0;
+        result = 31 * result + (username != null ? username.hashCode() : 0);
+        return result;
+    }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 60bcb59..617f686 100644
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -13,6 +13,7 @@ import org.keycloak.models.UserSessionProvider;
 import org.keycloak.models.UsernameLoginFailureModel;
 import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
 import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
+import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
 import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
 import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
 import org.keycloak.models.sessions.infinispan.mapreduce.ClientSessionMapper;
@@ -39,10 +40,10 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
 
     private final KeycloakSession session;
     private final Cache<String, SessionEntity> sessionCache;
-    private final Cache<String, LoginFailureEntity> loginFailureCache;
+    private final Cache<LoginFailureKey, LoginFailureEntity> loginFailureCache;
     private final InfinispanKeycloakTransaction tx;
 
-    public InfinispanUserSessionProvider(KeycloakSession session, Cache<String, SessionEntity> sessionCache, Cache<String, LoginFailureEntity> loginFailureCache) {
+    public InfinispanUserSessionProvider(KeycloakSession session, Cache<String, SessionEntity> sessionCache, Cache<LoginFailureKey, LoginFailureEntity> loginFailureCache) {
         this.session = session;
         this.sessionCache = sessionCache;
         this.loginFailureCache = loginFailureCache;
@@ -225,16 +226,18 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
 
     @Override
     public UsernameLoginFailureModel getUserLoginFailure(RealmModel realm, String username) {
-        return wrap(loginFailureCache.get(realm.getId() + ":" + username));
+        LoginFailureKey key = new LoginFailureKey(realm.getId(), username);
+        return wrap(key, loginFailureCache.get(key));
     }
 
     @Override
     public UsernameLoginFailureModel addUserLoginFailure(RealmModel realm, String username) {
+        LoginFailureKey key = new LoginFailureKey(realm.getId(), username);
         LoginFailureEntity entity = new LoginFailureEntity();
         entity.setRealm(realm.getId());
         entity.setUsername(username);
-        tx.put(loginFailureCache, entity.getId(), entity);
-        return wrap(entity);
+        tx.put(loginFailureCache, key, entity);
+        return wrap(key, entity);
     }
 
     @Override
@@ -257,6 +260,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
     @Override
     public void onUserRemoved(RealmModel realm, UserModel user) {
         removeUserSessions(realm, user);
+
+        loginFailureCache.remove(new LoginFailureKey(realm.getId(), user.getUsername()));
+        loginFailureCache.remove(new LoginFailureKey(realm.getId(), user.getEmail()));
     }
 
     @Override
@@ -321,8 +327,8 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
     }
 
 
-    UsernameLoginFailureModel wrap(LoginFailureEntity entity) {
-        return entity != null ? new UsernameLoginFailureAdapter(this, loginFailureCache, entity) : null;
+    UsernameLoginFailureModel wrap(LoginFailureKey key, LoginFailureEntity entity) {
+        return entity != null ? new UsernameLoginFailureAdapter(this, loginFailureCache, key, entity) : null;
     }
 
     List<ClientSessionModel> wrapClientSessions(RealmModel realm, Collection<ClientSessionEntity> entities) {
@@ -337,7 +343,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
 
         private boolean active;
         private boolean rollback;
-        private Map<String, CacheTask> tasks = new HashMap<String, CacheTask>();
+        private Map<Object, CacheTask> tasks = new HashMap<Object, CacheTask>();
 
         @Override
         public void begin() {
@@ -375,7 +381,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
             return active;
         }
 
-        public void put(Cache cache, String key, Object value) {
+        public void put(Cache cache, Object key, Object value) {
             if (tasks.containsKey(key)) {
                 throw new IllegalStateException("Can't add session: task in progress for session");
             } else {
@@ -383,7 +389,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
             }
         }
 
-        public void replace(Cache cache, String key, Object value) {
+        public void replace(Cache cache, Object key, Object value) {
             CacheTask current = tasks.get(key);
             if (current != null) {
                 switch (current.operation) {
@@ -406,10 +412,10 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         public class CacheTask {
             private Cache cache;
             private CacheOperation operation;
-            private String key;
+            private Object key;
             private Object value;
 
-            public CacheTask(Cache cache, CacheOperation operation, String key, Object value) {
+            public CacheTask(Cache cache, CacheOperation operation, Object key, Object value) {
                 this.cache = cache;
                 this.operation = operation;
                 this.key = key;
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
index 6098f3f..977eb3d 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
@@ -7,6 +7,7 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.UserSessionProvider;
 import org.keycloak.models.UserSessionProviderFactory;
 import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
+import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
 import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
 
 /**
@@ -21,7 +22,7 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
     public UserSessionProvider create(KeycloakSession session) {
         InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
         Cache<String, SessionEntity> cache = connections.getCache(SESSION_CACHE_NAME);
-        Cache<String, LoginFailureEntity> loginFailures = connections.getCache(LOGIN_FAILURE_CACHE_NAME);
+        Cache<LoginFailureKey, LoginFailureEntity> loginFailures = connections.getCache(LOGIN_FAILURE_CACHE_NAME);
         return new InfinispanUserSessionProvider(session, cache, loginFailures);
     }
 
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UsernameLoginFailureAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UsernameLoginFailureAdapter.java
index 2de7089..fed8f28 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UsernameLoginFailureAdapter.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UsernameLoginFailureAdapter.java
@@ -3,6 +3,7 @@ package org.keycloak.models.sessions.infinispan;
 import org.infinispan.Cache;
 import org.keycloak.models.UsernameLoginFailureModel;
 import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
+import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -10,12 +11,14 @@ import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
 public class UsernameLoginFailureAdapter implements UsernameLoginFailureModel {
 
     private InfinispanUserSessionProvider provider;
-    private Cache<String, LoginFailureEntity> cache;
+    private Cache<LoginFailureKey, LoginFailureEntity> cache;
+    private LoginFailureKey key;
     private LoginFailureEntity entity;
 
-    public UsernameLoginFailureAdapter(InfinispanUserSessionProvider provider, Cache<String, LoginFailureEntity> cache, LoginFailureEntity entity) {
+    public UsernameLoginFailureAdapter(InfinispanUserSessionProvider provider, Cache<LoginFailureKey, LoginFailureEntity> cache, LoginFailureKey key, LoginFailureEntity entity) {
         this.provider = provider;
         this.cache = cache;
+        this.key = key;
         this.entity = entity;
     }
 
@@ -75,7 +78,7 @@ public class UsernameLoginFailureAdapter implements UsernameLoginFailureModel {
     }
 
     void update() {
-        provider.getTx().replace(cache, entity.getId(), entity);
+        provider.getTx().replace(cache, key, entity);
     }
 
 }
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UsernameLoginFailureEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UsernameLoginFailureEntity.java
index 899d80e..c62e474 100755
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UsernameLoginFailureEntity.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UsernameLoginFailureEntity.java
@@ -18,7 +18,7 @@ import java.io.Serializable;
 @NamedQueries({
         @NamedQuery(name="getAllFailures", query="select failure from UsernameLoginFailureEntity failure"),
         @NamedQuery(name = "removeLoginFailuresByRealm", query = "delete from UsernameLoginFailureEntity f where f.realmId = :realmId"),
-        @NamedQuery(name = "removeLoginFailuresByUser", query = "delete from UsernameLoginFailureEntity f where f.realmId = :realmId and f.username = :username")
+        @NamedQuery(name = "removeLoginFailuresByUser", query = "delete from UsernameLoginFailureEntity f where f.realmId = :realmId and (f.username = :username or f.username = :email)")
 })
 @IdClass(UsernameLoginFailureEntity.Key.class)
 public class UsernameLoginFailureEntity {
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java
index a54891d..6708a2d 100755
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java
@@ -242,7 +242,7 @@ public class JpaUserSessionProvider implements UserSessionProvider {
     @Override
     public void onUserRemoved(RealmModel realm, UserModel user) {
         removeUserSessions(realm, user);
-        em.createNamedQuery("removeLoginFailuresByUser").setParameter("username", user.getUsername()).executeUpdate();
+        em.createNamedQuery("removeLoginFailuresByUser").setParameter("realmId", realm.getId()).setParameter("username", user.getUsername()).setParameter("email", user.getEmail()).executeUpdate();
     }
 
     @Override
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
index 53cf7f3..a19af58 100755
--- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
@@ -224,13 +224,13 @@ public class MemUserSessionProvider implements UserSessionProvider {
 
     @Override
     public UsernameLoginFailureModel getUserLoginFailure(RealmModel realm, String username) {
-        UsernameLoginFailureEntity entity = loginFailures.get(new UsernameLoginFailureKey(username, realm.getId()));
+        UsernameLoginFailureEntity entity = loginFailures.get(new UsernameLoginFailureKey(realm.getId(), username));
         return entity != null ? new UsernameLoginFailureAdapter(entity) : null;
     }
 
     @Override
     public UsernameLoginFailureModel addUserLoginFailure(RealmModel realm, String username) {
-        UsernameLoginFailureKey key = new UsernameLoginFailureKey(username, realm.getId());
+        UsernameLoginFailureKey key = new UsernameLoginFailureKey(realm.getId(), username);
         UsernameLoginFailureEntity entity = new UsernameLoginFailureEntity(username, realm.getId());
         if (loginFailures.putIfAbsent(key, entity) != null) {
             throw new ModelDuplicateException();
@@ -265,6 +265,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
         removeUserSessions(realm, user);
 
         loginFailures.remove(new UsernameLoginFailureKey(realm.getId(), user.getUsername()));
+        loginFailures.remove(new UsernameLoginFailureKey(realm.getId(), user.getEmail()));
     }
 
     @Override
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
index e2e9478..6ddebb9 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
@@ -250,6 +250,12 @@ public class MongoUserSessionProvider implements UserSessionProvider {
     @Override
     public void onUserRemoved(RealmModel realm, UserModel user) {
         removeUserSessions(realm, user);
+
+        DBObject query = new QueryBuilder()
+                .or(new BasicDBObject("username", user.getUsername()), new BasicDBObject("username", user.getEmail()))
+                .and("realmId").is(realm.getId())
+                .get();
+        mongoStore.removeEntities(MongoUsernameLoginFailureEntity.class, query, invocationContext);
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnect.java b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnect.java
index e520d8d..73ac4b5 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnect.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnect.java
@@ -23,8 +23,6 @@ package org.keycloak.protocol.oidc;
 
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
-import org.jboss.resteasy.spi.HttpRequest;
-import org.keycloak.ClientConnection;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.ClientSessionModel;
@@ -142,7 +140,7 @@ public class OpenIDConnect implements LoginProtocol {
         ApacheHttpClient4Executor executor = ResourceAdminManager.createExecutor();
 
         try {
-            new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, app, clientSession, executor, 0);
+            new ResourceAdminManager().logoutClientSession(uriInfo.getRequestUri(), realm, app, clientSession, executor);
         } finally {
             executor.getHttpClient().getConnectionManager().shutdown();
         }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
index 44b3934..d713450 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
@@ -37,7 +37,6 @@ import org.keycloak.services.ForbiddenException;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus;
 import org.keycloak.services.managers.ClientSessionCode;
-import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.resources.Cors;
 import org.keycloak.services.resources.RealmsResource;
 import org.keycloak.services.resources.flows.Flows;
@@ -52,12 +51,10 @@ import javax.ws.rs.HeaderParam;
 import javax.ws.rs.OPTIONS;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.CacheControl;
 import javax.ws.rs.core.Context;
-import javax.ws.rs.core.Cookie;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
@@ -612,15 +609,15 @@ public class OpenIDConnectService {
                     .build();
         }
 
-        String httpSessionId = formData.getFirst(AdapterConstants.HTTP_SESSION_ID);
-        if (httpSessionId != null) {
-            String httpSessionHost = formData.getFirst(AdapterConstants.HTTP_SESSION_HOST);
-            logger.infof("Http Session '%s' saved in ClientSession for client '%s'. Host is '%s'", httpSessionId, client.getClientId(), httpSessionHost);
+        String adapterSessionId = formData.getFirst(AdapterConstants.APPLICATION_SESSION_STATE);
+        if (adapterSessionId != null) {
+            String adapterSessionHost = formData.getFirst(AdapterConstants.APPLICATION_SESSION_HOST);
+            logger.infof("Adapter Session '%s' saved in ClientSession for client '%s'. Host is '%s'", adapterSessionId, client.getClientId(), adapterSessionHost);
 
-            event.detail(AdapterConstants.HTTP_SESSION_ID, httpSessionId);
-            clientSession.setNote(AdapterConstants.HTTP_SESSION_ID, httpSessionId);
-            event.detail(AdapterConstants.HTTP_SESSION_HOST, httpSessionHost);
-            clientSession.setNote(AdapterConstants.HTTP_SESSION_HOST, httpSessionHost);
+            event.detail(AdapterConstants.APPLICATION_SESSION_STATE, adapterSessionId);
+            clientSession.setNote(AdapterConstants.APPLICATION_SESSION_STATE, adapterSessionId);
+            event.detail(AdapterConstants.APPLICATION_SESSION_HOST, adapterSessionHost);
+            clientSession.setNote(AdapterConstants.APPLICATION_SESSION_HOST, adapterSessionHost);
         }
 
         AccessToken token = tokenManager.createClientAccessToken(accessCode.getRequestedRoles(), realm, client, user, userSession);
@@ -646,6 +643,21 @@ public class OpenIDConnectService {
     }
 
     protected ClientModel authorizeClient(String authorizationHeader, MultivaluedMap<String, String> formData, EventBuilder event) {
+        ClientModel client = authorizeClientBase(authorizationHeader, formData, event, realm);
+
+        if ( (client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) {
+            Map<String, String> error = new HashMap<String, String>();
+            error.put(OAuth2Constants.ERROR, "invalid_client");
+            error.put(OAuth2Constants.ERROR_DESCRIPTION, "Bearer-only not allowed");
+            event.error(Errors.INVALID_CLIENT);
+            throw new BadRequestException("Bearer-only not allowed", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
+        }
+
+        return client;
+    }
+
+    // Just authorize client without further checking about client type
+    public static ClientModel authorizeClientBase(String authorizationHeader, MultivaluedMap<String, String> formData, EventBuilder event, RealmModel realm) {
         String client_id;
         String clientSecret;
         if (authorizationHeader != null) {
@@ -686,14 +698,6 @@ public class OpenIDConnectService {
             throw new BadRequestException("Client is not enabled", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
         }
 
-        if ( (client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) {
-            Map<String, String> error = new HashMap<String, String>();
-            error.put(OAuth2Constants.ERROR, "invalid_client");
-            error.put(OAuth2Constants.ERROR_DESCRIPTION, "Bearer-only not allowed");
-            event.error(Errors.INVALID_CLIENT);
-            throw new BadRequestException("Bearer-only not allowed", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
-        }
-
         if (!client.isPublicClient()) {
             if (clientSecret == null || !client.validateSecret(clientSecret)) {
                 Map<String, String> error = new HashMap<String, String>();
@@ -702,6 +706,7 @@ public class OpenIDConnectService {
                 throw new BadRequestException("Unauthorized Client", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
             }
         }
+
         return client;
     }
 
diff --git a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
index 3f5de4d..5463f39 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
@@ -9,10 +9,16 @@ import org.keycloak.models.UserSessionProvider;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.adapters.config.BaseRealmConfig;
 import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.util.Time;
 
 import java.net.URI;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -46,6 +52,38 @@ public class ApplicationManager {
         }
     }
 
+    public Set<String> validateRegisteredNodes(ApplicationModel application) {
+        Map<String, Integer> registeredNodes = application.getRegisteredNodes();
+        if (registeredNodes == null || registeredNodes.isEmpty()) {
+            return Collections.emptySet();
+        }
+
+        int currentTime = Time.currentTime();
+
+        Set<String> validatedNodes = new TreeSet<String>();
+        if (application.getNodeReRegistrationTimeout() > 0) {
+            List<String> toRemove = new LinkedList<String>();
+            for (Map.Entry<String, Integer> entry : registeredNodes.entrySet()) {
+                Integer lastReRegistration = entry.getValue();
+                if (lastReRegistration + application.getNodeReRegistrationTimeout() < currentTime) {
+                    toRemove.add(entry.getKey());
+                } else {
+                    validatedNodes.add(entry.getKey());
+                }
+            }
+
+            // Remove time-outed nodes
+            for (String node : toRemove) {
+                application.unregisterNode(node);
+            }
+        } else {
+            // Periodic node reRegistration is disabled, so allow all nodes
+            validatedNodes.addAll(registeredNodes.keySet());
+        }
+
+        return validatedNodes;
+    }
+
     @JsonPropertyOrder({"realm", "realm-public-key", "bearer-only", "auth-server-url", "ssl-required",
             "resource", "public-client", "credentials",
             "use-resource-role-mappings"})
diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
index 2a4576f..d959883 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
@@ -20,6 +20,7 @@ import org.keycloak.representations.adapters.action.PushNotBeforeAction;
 import org.keycloak.representations.adapters.action.UserStats;
 import org.keycloak.services.util.HttpClientBuilder;
 import org.keycloak.services.util.ResolveRelative;
+import org.keycloak.util.KeycloakUriBuilder;
 import org.keycloak.util.MultivaluedHashMap;
 import org.keycloak.util.StringPropertyReplacer;
 import org.keycloak.util.Time;
@@ -29,11 +30,11 @@ import javax.ws.rs.core.UriBuilder;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
+import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.TreeMap;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -41,7 +42,7 @@ import java.util.TreeMap;
  */
 public class ResourceAdminManager {
     protected static Logger logger = Logger.getLogger(ResourceAdminManager.class);
-    private static final String KC_SESSION_HOST = "${kc_session_host}";
+    private static final String APPLICATION_SESSION_HOST_PROPERTY = "${application.session.host}";
 
     public static ApacheHttpClient4Executor createExecutor() {
         HttpClient client = new HttpClientBuilder()
@@ -63,6 +64,29 @@ public class ResourceAdminManager {
         return StringPropertyReplacer.replaceProperties(absoluteURI);
     }
 
+    private List<String> getAllManagementUrls(URI requestUri, ApplicationModel application) {
+        String baseMgmtUrl = getManagementUrl(requestUri, application);
+        if (baseMgmtUrl == null) {
+            return Collections.emptyList();
+        }
+
+        Set<String> registeredNodesHosts = new ApplicationManager().validateRegisteredNodes(application);
+
+        // No-cluster setup
+        if (registeredNodesHosts.isEmpty()) {
+            return Arrays.asList(baseMgmtUrl);
+        }
+
+        List<String> result = new LinkedList<String>();
+        KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(baseMgmtUrl);
+        for (String nodeHost : registeredNodesHosts) {
+            String currentNodeUri = uriBuilder.clone().host(nodeHost).build().toString();
+            result.add(currentNodeUri);
+        }
+
+        return result;
+    }
+
     public void logoutUser(URI requestUri, RealmModel realm, UserModel user, KeycloakSession keycloakSession) {
         List<UserSessionModel> userSessions = keycloakSession.sessions().getUserSessions(realm, user);
         logoutUserSessions(requestUri, realm, userSessions);
@@ -82,7 +106,7 @@ public class ResourceAdminManager {
             logger.infov("logging out resources: " + clientSessions);
 
             for (Map.Entry<ApplicationModel, List<ClientSessionModel>> entry : clientSessions.entrySet()) {
-                logoutApplication(requestUri, realm, entry.getKey(), entry.getValue(), executor, 0);
+                logoutClientSessions(requestUri, realm, entry.getKey(), entry.getValue(), executor);
             }
         } finally {
             executor.getHttpClient().getConnectionManager().shutdown();
@@ -108,34 +132,18 @@ public class ResourceAdminManager {
 
             logger.debugv("logging out {0} resources ", clientSessions.size());
             for (Map.Entry<ApplicationModel, List<ClientSessionModel>> entry : clientSessions.entrySet()) {
-                logoutApplication(requestUri, realm, entry.getKey(), entry.getValue(), executor, 0);
-            }
-        } finally {
-            executor.getHttpClient().getConnectionManager().shutdown();
-        }
-    }
-
-    public void logoutAll(URI requestUri, RealmModel realm) {
-        ApacheHttpClient4Executor executor = createExecutor();
-
-        try {
-            realm.setNotBefore(Time.currentTime());
-            List<ApplicationModel> resources = realm.getApplications();
-            logger.debugv("logging out {0} resources ", resources.size());
-            for (ApplicationModel resource : resources) {
-                logoutApplication(requestUri, realm, resource, (List<ClientSessionModel>)null, executor, realm.getNotBefore());
+                logoutClientSessions(requestUri, realm, entry.getKey(), entry.getValue(), executor);
             }
         } finally {
             executor.getHttpClient().getConnectionManager().shutdown();
         }
     }
 
-    public void logoutApplication(URI requestUri, RealmModel realm, ApplicationModel resource, List<UserSessionModel> userSessions) {
+    public void logoutUserFromApplication(URI requestUri, RealmModel realm, ApplicationModel resource, UserModel user, KeycloakSession session) {
         ApacheHttpClient4Executor executor = createExecutor();
 
         try {
-            resource.setNotBefore(Time.currentTime());
-
+            List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
             List<ClientSessionModel> ourAppClientSessions = null;
             if (userSessions != null) {
                 MultivaluedHashMap<ApplicationModel, ClientSessionModel> clientSessions = new MultivaluedHashMap<ApplicationModel, ClientSessionModel>();
@@ -145,18 +153,18 @@ public class ResourceAdminManager {
                 ourAppClientSessions = clientSessions.get(resource);
             }
 
-            logoutApplication(requestUri, realm, resource, ourAppClientSessions, executor, resource.getNotBefore());
+            logoutClientSessions(requestUri, realm, resource, ourAppClientSessions, executor);
         } finally {
             executor.getHttpClient().getConnectionManager().shutdown();
         }
 
     }
 
-    public boolean logoutApplication(URI requestUri, RealmModel realm, ApplicationModel resource, ClientSessionModel clientSession, ApacheHttpClient4Executor client, int notBefore) {
-        return logoutApplication(requestUri, realm, resource, Arrays.asList(clientSession), client, notBefore);
+    public boolean logoutClientSession(URI requestUri, RealmModel realm, ApplicationModel resource, ClientSessionModel clientSession, ApacheHttpClient4Executor client) {
+        return logoutClientSessions(requestUri, realm, resource, Arrays.asList(clientSession), client);
     }
 
-    protected boolean logoutApplication(URI requestUri, RealmModel realm, ApplicationModel resource, List<ClientSessionModel> clientSessions, ApacheHttpClient4Executor client, int notBefore) {
+    protected boolean logoutClientSessions(URI requestUri, RealmModel realm, ApplicationModel resource, List<ClientSessionModel> clientSessions, ApacheHttpClient4Executor client) {
         String managementUrl = getManagementUrl(requestUri, resource);
         if (managementUrl != null) {
 
@@ -165,22 +173,22 @@ public class ResourceAdminManager {
             if (clientSessions != null && clientSessions.size() > 0) {
                 adapterSessionIds = new MultivaluedHashMap<String, String>();
                 for (ClientSessionModel clientSession : clientSessions) {
-                    String adapterSessionId = clientSession.getNote(AdapterConstants.HTTP_SESSION_ID);
+                    String adapterSessionId = clientSession.getNote(AdapterConstants.APPLICATION_SESSION_STATE);
                     if (adapterSessionId != null) {
-                        String host = clientSession.getNote(AdapterConstants.HTTP_SESSION_HOST);
+                        String host = clientSession.getNote(AdapterConstants.APPLICATION_SESSION_HOST);
                         adapterSessionIds.add(host, adapterSessionId);
                     }
                 }
             }
 
-            if (managementUrl.contains(KC_SESSION_HOST) && adapterSessionIds != null) {
+            if (managementUrl.contains(APPLICATION_SESSION_HOST_PROPERTY) && adapterSessionIds != null) {
                 boolean allPassed = true;
                 // Send logout separately to each host (needed for single-sign-out in cluster for non-distributable apps - KEYCLOAK-748)
                 for (Map.Entry<String, List<String>> entry : adapterSessionIds.entrySet()) {
                     String host = entry.getKey();
                     List<String> sessionIds = entry.getValue();
-                    String currentHostMgmtUrl = managementUrl.replace(KC_SESSION_HOST, host);
-                    allPassed = logoutApplicationOnHost(realm, resource, sessionIds, client, notBefore, currentHostMgmtUrl) && allPassed;
+                    String currentHostMgmtUrl = managementUrl.replace(APPLICATION_SESSION_HOST_PROPERTY, host);
+                    allPassed = sendLogoutRequest(realm, resource, sessionIds, client, 0, currentHostMgmtUrl) && allPassed;
                 }
 
                 return allPassed;
@@ -193,7 +201,7 @@ public class ResourceAdminManager {
                         allSessionIds.addAll(currentIds);
                     }
                 }
-                return logoutApplicationOnHost(realm, resource, allSessionIds, client, notBefore, managementUrl);
+                return sendLogoutRequest(realm, resource, allSessionIds, client, 0, managementUrl);
             }
         } else {
             logger.debugv("Can't logout {0}: no management url", resource.getName());
@@ -201,7 +209,54 @@ public class ResourceAdminManager {
         }
     }
 
-    protected boolean logoutApplicationOnHost(RealmModel realm, ApplicationModel resource, List<String> adapterSessionIds, ApacheHttpClient4Executor client, int notBefore, String managementUrl) {
+    // Methods for logout all
+
+    public void logoutAll(URI requestUri, RealmModel realm) {
+        ApacheHttpClient4Executor executor = createExecutor();
+
+        try {
+            realm.setNotBefore(Time.currentTime());
+            List<ApplicationModel> resources = realm.getApplications();
+            logger.debugv("logging out {0} resources ", resources.size());
+            for (ApplicationModel resource : resources) {
+                logoutApplication(requestUri, realm, resource, executor, realm.getNotBefore());
+            }
+        } finally {
+            executor.getHttpClient().getConnectionManager().shutdown();
+        }
+    }
+
+    public void logoutApplication(URI requestUri, RealmModel realm, ApplicationModel resource) {
+        ApacheHttpClient4Executor executor = createExecutor();
+        try {
+            resource.setNotBefore(Time.currentTime());
+            logoutApplication(requestUri, realm, resource, executor, resource.getNotBefore());
+        } finally {
+            executor.getHttpClient().getConnectionManager().shutdown();
+        }
+    }
+
+
+    protected boolean logoutApplication(URI requestUri, RealmModel realm, ApplicationModel resource, ApacheHttpClient4Executor executor, int notBefore) {
+        List<String> mgmtUrls = getAllManagementUrls(requestUri, resource);
+        if (mgmtUrls.isEmpty()) {
+            logger.debug("No management URL or no registered cluster nodes for the application " + resource.getName());
+            return false;
+        }
+
+        logger.info("Send logoutApplication for URLs: " + mgmtUrls);
+
+        // Propagate this to all hosts
+        boolean anyFailed = false;
+        for (String mgmtUrl : mgmtUrls) {
+            if (!sendLogoutRequest(realm, resource, null, executor, notBefore, mgmtUrl)) {
+                anyFailed = true;
+            }
+        }
+        return !anyFailed;
+    }
+
+    protected boolean sendLogoutRequest(RealmModel realm, ApplicationModel resource, List<String> adapterSessionIds, ApacheHttpClient4Executor client, int notBefore, String managementUrl) {
         LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), adapterSessionIds, notBefore);
         String token = new TokenManager().encodeToken(realm, adminAction);
         logger.infov("logout resource {0} url: {1} sessionIds: " + adapterSessionIds, resource.getName(), managementUrl);
@@ -245,33 +300,43 @@ public class ResourceAdminManager {
     }
 
 
-    protected boolean pushRevocationPolicy(URI requestUri, RealmModel realm, ApplicationModel resource, int notBefore, ApacheHttpClient4Executor client) {
-        if (notBefore <= 0) return false;
-        String managementUrl = getManagementUrl(requestUri, resource);
-        if (managementUrl != null) {
-            PushNotBeforeAction adminAction = new PushNotBeforeAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), notBefore);
-            String token = new TokenManager().encodeToken(realm, adminAction);
-            logger.infov("pushRevocation resource: {0} url: {1}", resource.getName(), managementUrl);
-            ClientRequest request = client.createRequest(UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).build().toString());
-            ClientResponse response;
-            try {
-                response = request.body(MediaType.TEXT_PLAIN_TYPE, token).post();
-            } catch (Exception e) {
-                throw new RuntimeException(e);
-            }
-
-            try {
-                boolean success = response.getStatus() == 204;
-                logger.debug("pushRevocation success.");
-                return success;
-            } finally {
-                response.releaseConnection();
-            }
-        } else {
-            logger.debug("no management URL for application: " + resource.getName());
+    protected boolean pushRevocationPolicy(URI requestUri, RealmModel realm, ApplicationModel resource, int notBefore, ApacheHttpClient4Executor executor) {
+        List<String> mgmtUrls = getAllManagementUrls(requestUri, resource);
+        if (mgmtUrls.isEmpty()) {
+            logger.debug("No management URL or no registered cluster nodes for the application " + resource.getName());
             return false;
         }
 
+        logger.info("Sending push revocation to URLS: " + mgmtUrls);
+
+        // Propagate this to all hosts
+        boolean anyFailed= false;
+        for (String mgmtUrl : mgmtUrls) {
+            if (!sendPushRevocationPolicyRequest(realm, resource, notBefore, executor, mgmtUrl)) {
+                anyFailed = true;
+            }
+        }
+        return !anyFailed;
+    }
 
+    protected boolean sendPushRevocationPolicyRequest(RealmModel realm, ApplicationModel resource, int notBefore, ApacheHttpClient4Executor client, String managementUrl) {
+        PushNotBeforeAction adminAction = new PushNotBeforeAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), notBefore);
+        String token = new TokenManager().encodeToken(realm, adminAction);
+        logger.infov("pushRevocation resource: {0} url: {1}", resource.getName(), managementUrl);
+        ClientRequest request = client.createRequest(UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).build().toString());
+        ClientResponse response;
+        try {
+            response = request.body(MediaType.TEXT_PLAIN_TYPE, token).post();
+        } catch (Exception e) {
+            logger.warn("Failed to send revocation request", e);
+            return false;
+        }
+        try {
+            boolean success = response.getStatus() == 204;
+            logger.debug("pushRevocation success.");
+            return success;
+        } finally {
+            response.releaseConnection();
+        }
     }
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java
index 15a2773..0c84d11 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java
@@ -335,7 +335,7 @@ public class ApplicationResource {
     @POST
     public void logoutAll() {
         auth.requireManage();
-        new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, application, null);
+        new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, application);
     }
 
     /**
@@ -351,8 +351,7 @@ public class ApplicationResource {
             throw new NotFoundException("User not found");
         }
 
-        List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
-        new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, application, userSessions);
+        new ResourceAdminManager().logoutUserFromApplication(uriInfo.getRequestUri(), realm, application, user, session);
     }
 
 
diff --git a/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java b/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java
new file mode 100644
index 0000000..689d892
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java
@@ -0,0 +1,193 @@
+package org.keycloak.services.resources;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.ext.Providers;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.spi.BadRequestException;
+import org.jboss.resteasy.spi.HttpRequest;
+import org.jboss.resteasy.spi.UnauthorizedException;
+import org.keycloak.ClientConnection;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.adapters.AdapterConstants;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.oidc.OpenIDConnectService;
+import org.keycloak.services.ForbiddenException;
+import org.keycloak.util.Time;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ClientsManagementService {
+
+    protected static final Logger logger = Logger.getLogger(ClientsManagementService.class);
+
+    private RealmModel realm;
+
+    private EventBuilder event;
+
+    @Context
+    private HttpRequest request;
+
+    @Context
+    protected HttpHeaders headers;
+
+    @Context
+    private UriInfo uriInfo;
+
+    @Context
+    private ClientConnection clientConnection;
+
+    @Context
+    protected Providers providers;
+
+    @Context
+    protected KeycloakSession session;
+
+    public ClientsManagementService(RealmModel realm, EventBuilder event) {
+        this.realm = realm;
+        this.event = event;
+    }
+
+    public static UriBuilder clientsManagementBaseUrl(UriBuilder baseUriBuilder) {
+        return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getClientsManagementService");
+    }
+
+    public static UriBuilder registerNodeUrl(UriBuilder baseUriBuilder) {
+        UriBuilder uriBuilder = clientsManagementBaseUrl(baseUriBuilder);
+        return uriBuilder.path(ClientsManagementService.class, "registerNode");
+    }
+
+    public static UriBuilder unregisterNodeUrl(UriBuilder baseUriBuilder) {
+        UriBuilder uriBuilder = clientsManagementBaseUrl(baseUriBuilder);
+        return uriBuilder.path(ClientsManagementService.class, "unregisterNode");
+    }
+
+    /**
+     * URL invoked by adapter to register new application cluster node. Each application cluster node will invoke this URL once it joins cluster
+     *
+     * @param authorizationHeader
+     * @param formData
+     * @return
+     */
+    @Path("register-node")
+    @POST
+    @Produces("application/json")
+    public Response registerNode(@HeaderParam(HttpHeaders.AUTHORIZATION) String authorizationHeader, final MultivaluedMap<String, String> formData) {
+        if (!checkSsl()) {
+            throw new ForbiddenException("HTTPS required");
+        }
+
+        event.event(EventType.REGISTER_NODE);
+
+        if (!realm.isEnabled()) {
+            event.error(Errors.REALM_DISABLED);
+            throw new UnauthorizedException("Realm not enabled");
+        }
+
+        ApplicationModel application = authorizeApplication(authorizationHeader, formData);
+        String nodeHost = getApplicationClusterHost(formData);
+
+        logger.infof("Registering cluster host '%s' for client '%s'", nodeHost, application.getName());
+
+        application.registerNode(nodeHost, Time.currentTime());
+
+        return Response.noContent().build();
+    }
+
+
+    /**
+     * URL invoked by adapter to register new application cluster node. Each application cluster node will invoke this URL once it joins cluster
+     *
+     * @param authorizationHeader
+     * @param formData
+     * @return
+     */
+    @Path("unregister-node")
+    @POST
+    @Produces("application/json")
+    public Response unregisterNode(@HeaderParam(HttpHeaders.AUTHORIZATION) String authorizationHeader, final MultivaluedMap<String, String> formData) {
+        if (!checkSsl()) {
+            throw new ForbiddenException("HTTPS required");
+        }
+
+        event.event(EventType.UNREGISTER_NODE);
+
+        if (!realm.isEnabled()) {
+            event.error(Errors.REALM_DISABLED);
+            throw new UnauthorizedException("Realm not enabled");
+        }
+
+        ApplicationModel application = authorizeApplication(authorizationHeader, formData);
+        String nodeHost = getApplicationClusterHost(formData);
+
+        logger.infof("Unregistering cluster host '%s' for client '%s'", nodeHost, application.getName());
+
+        application.unregisterNode(nodeHost);
+
+        return Response.noContent().build();
+    }
+
+    protected ApplicationModel authorizeApplication(String authorizationHeader, MultivaluedMap<String, String> formData) {
+        ClientModel client = OpenIDConnectService.authorizeClientBase(authorizationHeader, formData, event, realm);
+
+        if (client.isPublicClient()) {
+            Map<String, String> error = new HashMap<String, String>();
+            error.put(OAuth2Constants.ERROR, "invalid_client");
+            error.put(OAuth2Constants.ERROR_DESCRIPTION, "Public clients not allowed");
+            event.error(Errors.INVALID_CLIENT);
+            throw new BadRequestException("Public clients not allowed", javax.ws.rs.core.Response.status(javax.ws.rs.core.Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
+        }
+
+        if (!(client instanceof ApplicationModel)) {
+            Map<String, String> error = new HashMap<String, String>();
+            error.put(OAuth2Constants.ERROR, "invalid_client");
+            error.put(OAuth2Constants.ERROR_DESCRIPTION, "Just applications are allowed");
+            event.error(Errors.INVALID_CLIENT);
+            throw new BadRequestException("ust applications are allowed", javax.ws.rs.core.Response.status(javax.ws.rs.core.Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
+        }
+
+        return (ApplicationModel)client;
+    }
+
+    protected String getApplicationClusterHost(MultivaluedMap<String, String> formData) {
+        String applicationClusterHost = formData.getFirst(AdapterConstants.APPLICATION_CLUSTER_HOST);
+        if (applicationClusterHost == null || applicationClusterHost.length() == 0) {
+            Map<String, String> error = new HashMap<String, String>();
+            error.put(OAuth2Constants.ERROR, "invalid_request");
+            error.put(OAuth2Constants.ERROR_DESCRIPTION, "application cluster host not specified");
+            event.error(Errors.INVALID_CODE);
+            throw new BadRequestException("Cluster host not specified", javax.ws.rs.core.Response.status(javax.ws.rs.core.Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
+        }
+
+        return applicationClusterHost;
+    }
+
+
+
+    private boolean checkSsl() {
+        if (uriInfo.getBaseUri().getScheme().equals("https")) {
+            return true;
+        } else {
+            return !realm.getSslRequired().isRequired(clientConnection);
+        }
+    }
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index 785b101..2feeaaa 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -134,6 +134,16 @@ public class RealmsResource {
         return service;
     }
 
+    @Path("{realm}/clients-managements")
+    public ClientsManagementService getClientsManagementService(final @PathParam("realm") String name) {
+        RealmManager realmManager = new RealmManager(session);
+        RealmModel realm = locateRealm(name, realmManager);
+        EventBuilder event = new EventsManager(realm, session, clientConnection).createEventBuilder();
+        ClientsManagementService service = new ClientsManagementService(realm, event);
+        ResteasyProviderFactory.getInstance().injectProperties(service);
+        return service;
+    }
+
 
     protected RealmModel locateRealm(String name, RealmManager realmManager) {
         RealmModel realm = realmManager.getRealmByName(name);
diff --git a/testsuite/docker-cluster/shared-files/deploy-examples.sh b/testsuite/docker-cluster/shared-files/deploy-examples.sh
index f71f697..d2e3287 100644
--- a/testsuite/docker-cluster/shared-files/deploy-examples.sh
+++ b/testsuite/docker-cluster/shared-files/deploy-examples.sh
@@ -29,7 +29,8 @@ sed -i -e 's/false/true/' admin-access.war/WEB-INF/web.xml
 
 # Configure other examples
 for I in *.war/WEB-INF/keycloak.json; do
-  sed -i -e 's/\"auth-server-url\".*: \"\/auth\",/&\n    \"auth-server-url-for-backend-requests\": \"http:\/\/\$\{jboss.host.name\}:8080\/auth\",/' $I;
+  sed -i -e 's/\"auth-server-url\".*: \"\/auth\",/&\n    \"auth-server-url-for-backend-requests\": \"http:\/\/\$\{jboss.host.name\}:8080\/auth\",\
+  \n    \"register-node-at-startup\": false,\n    \"register-node-period\": 30,/' $I;
 done;
 
 # Enable distributable for customer-portal
@@ -37,6 +38,6 @@ sed -i -e 's/<\/module-name>/&\n    <distributable \/>/' customer-portal.war/WEB
 
 # Configure testrealm.json - Enable adminUrl to access adapters on local machine
 sed -i -e 's/\"adminUrl\": \"\/customer-portal/\"adminUrl\": \"http:\/\/\$\{jboss.host.name\}:8080\/customer-portal/' /keycloak-docker-cluster/examples/testrealm.json
-sed -i -e 's/\"adminUrl\": \"\/product-portal/\"adminUrl\": \"http:\/\/\$\{kc_session_host\}:8080\/product-portal/' /keycloak-docker-cluster/examples/testrealm.json
+sed -i -e 's/\"adminUrl\": \"\/product-portal/\"adminUrl\": \"http:\/\/\$\{application.session.host\}:8080\/product-portal/' /keycloak-docker-cluster/examples/testrealm.json
 
 
diff --git a/testsuite/docker-cluster/shared-files/keycloak-run-node.sh b/testsuite/docker-cluster/shared-files/keycloak-run-node.sh
index 7d350aa..bc0d1e6 100644
--- a/testsuite/docker-cluster/shared-files/keycloak-run-node.sh
+++ b/testsuite/docker-cluster/shared-files/keycloak-run-node.sh
@@ -23,9 +23,8 @@ function prepareHost
   cp -r /keycloak-docker-cluster/deployments/* $JBOSS_HOME/standalone/deployments/
 
   # Enable Infinispan provider
-  sed -i "s|keycloak.userSessions.provider:mem|keycloak.userSessions.provider:infinispan|" $JBOSS_HOME/standalone/deployments/auth-server.war/WEB-INF/classes/META-INF/keycloak-server.json
-  sed -i "s|keycloak.realm.cache.provider:mem|keycloak.realm.cache.provider:infinispan|" $JBOSS_HOME/standalone/deployments/auth-server.war/WEB-INF/classes/META-INF/keycloak-server.json
-  sed -i "s|keycloak.user.cache.provider:mem|keycloak.user.cache.provider:infinispan|" $JBOSS_HOME/standalone/deployments/auth-server.war/WEB-INF/classes/META-INF/keycloak-server.json
+  sed -i "s|\"provider\".*: \"mem\"|\"provider\": \"infinispan\"|" $JBOSS_HOME/standalone/deployments/auth-server.war/WEB-INF/classes/META-INF/keycloak-server.json
+  sed -i -e "s/\"connectionsJpa\"/\n \"connectionsInfinispan\": \{\n  \"default\" : \{\n   \"cacheContainer\" : \"java:jboss\/infinispan\/Keycloak\"\n  \}\n \},\n     &/" $JBOSS_HOME/standalone/deployments/auth-server.war/WEB-INF/classes/META-INF/keycloak-server.json
 
   # Deploy and configure examples
   /keycloak-docker-cluster/shared-files/deploy-examples.sh
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ApplicationModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ApplicationModelTest.java
index 84bd57c..bb8c651 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ApplicationModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ApplicationModelTest.java
@@ -45,6 +45,9 @@ public class ApplicationModelTest extends AbstractModelTest {
         application.addWebOrigin("origin-1");
         application.addWebOrigin("origin-2");
 
+        application.registerNode("node1", 10);
+        application.registerNode("10.20.30.40", 50);
+
         application.updateApplication();
     }
 
@@ -84,6 +87,7 @@ public class ApplicationModelTest extends AbstractModelTest {
 
         Assert.assertTrue(expected.getRedirectUris().containsAll(actual.getRedirectUris()));
         Assert.assertTrue(expected.getWebOrigins().containsAll(actual.getWebOrigins()));
+        Assert.assertTrue(expected.getRegisteredNodes().equals(actual.getRegisteredNodes()));
     }
 
     public static void assertEquals(List<RoleModel> expected, List<RoleModel> actual) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
index ac0eb6a..97c433e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
@@ -97,6 +97,12 @@ public class ImportTest extends AbstractModelTest {
         Assert.assertTrue(apps.values().contains(accountApp));
         realm.getApplications().containsAll(apps.values());
 
+        Assert.assertEquals(50, application.getNodeReRegistrationTimeout());
+        Map<String, Integer> appRegisteredNodes = application.getRegisteredNodes();
+        Assert.assertEquals(2, appRegisteredNodes.size());
+        Assert.assertTrue(10 == appRegisteredNodes.get("node1"));
+        Assert.assertTrue(20 == appRegisteredNodes.get("172.10.15.20"));
+
         // Test finding applications by ID
         Assert.assertNull(realm.getApplicationById("982734"));
         Assert.assertEquals(application, realm.getApplicationById(application.getId()));
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
index 2d9983e..c68ae7d 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
@@ -12,6 +12,7 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.UsernameLoginFailureModel;
 import org.keycloak.protocol.oidc.OpenIDConnect;
+import org.keycloak.services.managers.UserManager;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.util.Time;
 
@@ -38,8 +39,8 @@ public class UserSessionProviderTest {
     public void before() {
         session = kc.startSession();
         realm = session.realms().getRealm("test");
-        session.users().addUser(realm, "user1");
-        session.users().addUser(realm, "user2");
+        session.users().addUser(realm, "user1").setEmail("user1@localhost");
+        session.users().addUser(realm, "user2").setEmail("user2@localhost");
     }
 
     @After
@@ -48,8 +49,10 @@ public class UserSessionProviderTest {
         session.sessions().removeUserSessions(realm);
         UserModel user1 = session.users().getUserByUsername("user1", realm);
         UserModel user2 = session.users().getUserByUsername("user2", realm);
-        session.users().removeUser(realm, user1);
-        session.users().removeUser(realm, user2);
+
+        UserManager um = new UserManager(session);
+        um.removeUser(realm, user1);
+        um.removeUser(realm, user2);
         kc.stopSession(session, true);
     }
 
@@ -360,6 +363,28 @@ public class UserSessionProviderTest {
         assertEquals(0, failure1.getNumFailures());
     }
 
+    @Test
+    public void testOnUserRemoved() {
+        createSessions();
+
+        session.sessions().addUserLoginFailure(realm, "user1");
+        session.sessions().addUserLoginFailure(realm, "user1@localhost");
+        session.sessions().addUserLoginFailure(realm, "user2");
+
+        resetSession();
+
+        session.sessions().onUserRemoved(realm, session.users().getUserByUsername("user1", realm));
+
+        resetSession();
+
+        assertTrue(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user1", realm)).isEmpty());
+        assertFalse(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user2", realm)).isEmpty());
+
+        assertNull(session.sessions().getUserLoginFailure(realm, "user1"));
+        assertNull(session.sessions().getUserLoginFailure(realm, "user1@localhost"));
+        assertNotNull(session.sessions().getUserLoginFailure(realm, "user2"));
+    }
+
     private ClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles) {
         ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
         if (userSession != null) clientSession.setUserSession(userSession);
diff --git a/testsuite/integration/src/test/resources/model/testrealm.json b/testsuite/integration/src/test/resources/model/testrealm.json
index a5bf380..fdb8f82 100755
--- a/testsuite/integration/src/test/resources/model/testrealm.json
+++ b/testsuite/integration/src/test/resources/model/testrealm.json
@@ -94,7 +94,12 @@
     "applications": [
         {
             "name": "Application",
-            "enabled": true
+            "enabled": true,
+            "nodeReRegistrationTimeout": 50,
+            "registeredNodes": {
+                "node1": 10,
+                "172.10.15.20": 20
+            }
         },
         {
             "name": "OtherApp",