keycloak-aplcache

client user-session association

5/17/2014 3:24:32 PM

Changes

Details

diff --git a/core/src/main/java/org/keycloak/representations/idm/UserSessionRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserSessionRepresentation.java
new file mode 100755
index 0000000..b3f3c5a
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/UserSessionRepresentation.java
@@ -0,0 +1,67 @@
+package org.keycloak.representations.idm;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UserSessionRepresentation {
+    private String id;
+    private String user;
+    private String ipAddress;
+    private long start;
+    private Set<String> applications = new HashSet<String>();
+    private Map<String, String> clients = new HashMap<String, String>();
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getUser() {
+        return user;
+    }
+
+    public void setUser(String user) {
+        this.user = user;
+    }
+
+    public String getIpAddress() {
+        return ipAddress;
+    }
+
+    public void setIpAddress(String ipAddress) {
+        this.ipAddress = ipAddress;
+    }
+
+    public long getStart() {
+        return start;
+    }
+
+    public void setStart(long start) {
+        this.start = start;
+    }
+
+    public Set<String> getApplications() {
+        return applications;
+    }
+
+    public void setApplications(Set<String> applications) {
+        this.applications = applications;
+    }
+
+    public Map<String, String> getClients() {
+        return clients;
+    }
+
+    public void setClients(Map<String, String> clients) {
+        this.clients = clients;
+    }
+}
diff --git a/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json b/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json
index c64ff41..066aec8 100755
--- a/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json
@@ -3,5 +3,6 @@
   "resource" : "database-service",
   "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "bearer-only" : true,
+  "ssl-not-required": true,
   "enable-cors": true
 }
diff --git a/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json
index 2e02cc3..adf176c 100755
--- a/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json
@@ -3,6 +3,5 @@
   "resource" : "database-service",
   "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "bearer-only" : true,
-  "enable-cors" : true
-
+  "ssl-not-required": true
 }
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
index f73d947..e633dca 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
@@ -290,8 +290,8 @@ module.config([ '$routeProvider', function($routeProvider) {
                 user : function(UserLoader) {
                     return UserLoader();
                 },
-                stats : function(UserSessionStatsLoader) {
-                    return UserSessionStatsLoader();
+                sessions : function(UserSessionsLoader) {
+                    return UserSessionsLoader();
                 }
             },
             controller : 'UserSessionsCtrl'
@@ -436,8 +436,8 @@ module.config([ '$routeProvider', function($routeProvider) {
                 application : function(ApplicationLoader) {
                     return ApplicationLoader();
                 },
-                stats : function(ApplicationSessionStatsLoader) {
-                    return ApplicationSessionStatsLoader();
+                sessionCount : function(ApplicationSessionCountLoader) {
+                    return ApplicationSessionCountLoader();
                 }
             },
             controller : 'ApplicationSessionsCtrl'
@@ -705,8 +705,8 @@ module.config([ '$routeProvider', function($routeProvider) {
                 realm : function(RealmLoader) {
                     return RealmLoader();
                 },
-                stats : function(RealmSessionStatsLoader) {
-                    return RealmSessionStatsLoader();
+                stats : function(RealmApplicationSessionStatsLoader) {
+                    return RealmApplicationSessionStatsLoader();
                 }
             },
             controller : 'RealmSessionStatsCtrl'
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js
index 9f7615d..ef54e1e 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js
@@ -43,15 +43,12 @@ module.controller('ApplicationCredentialsCtrl', function($scope, $location, real
     });
 });
 
-module.controller('ApplicationSessionsCtrl', function($scope, realm, stats, application,
-                                                      ApplicationLogoutUser,
-                                                      ApplicationLogoutAll,
-                                                      ApplicationSessionStats,
-                                                      ApplicationSessionStatsWithUsers,
+module.controller('ApplicationSessionsCtrl', function($scope, realm, sessionCount, application,
+                                                      ApplicationUserSessions,
                                                     $location, Dialog, Notifications) {
     $scope.realm = realm;
-    $scope.stats = stats;
-    $scope.users = {};
+    $scope.count = sessionCount.count;
+    $scope.sessions = [];
     $scope.application = application;
 
     $scope.toDate = function(val) {
@@ -59,27 +56,11 @@ module.controller('ApplicationSessionsCtrl', function($scope, realm, stats, appl
     };
 
     $scope.loadUsers = function() {
-        ApplicationSessionStatsWithUsers.get({ realm : realm.realm, application: $scope.application.name }, function(updated) {
-            $scope.stats = updated;
-            $scope.users = updated.users;
+        ApplicationUserSessions.query({ realm : realm.realm, application: $scope.application.name }, function(updated) {
+            $scope.count = updated.length;
+            $scope.sessions = updated;
         })
     };
-
-    $scope.logoutAll = function() {
-        ApplicationLogoutAll.save({realm : realm.realm, application: $scope.application.name}, function () {
-            Notifications.success('Logged out all users');
-            $scope.loadUsers();
-        });
-    };
-
-    $scope.logoutUser = function(user) {
-        console.log('Trying to logout user: ' + user);
-        ApplicationLogoutUser.save({realm : realm.realm, application: $scope.application.name, user: user}, function () {
-            Notifications.success('Logged out user' + user);
-            $scope.loadUsers();
-        });
-    };
-
 });
 
 module.controller('ApplicationClaimsCtrl', function($scope, realm, application, claims,
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js
index ee05261..35e4fb0 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js
@@ -99,32 +99,29 @@ module.controller('UserRoleMappingCtrl', function($scope, $http, realm, user, ap
 
 });
 
-module.controller('UserSessionsCtrl', function($scope, realm, user, stats, UserLogout, ApplicationLogoutUser, UserSessionStats, Notifications) {
+module.controller('UserSessionsCtrl', function($scope, realm, user, sessions, UserSessions, UserLogout, UserSessionLogout, Notifications) {
     $scope.realm = realm;
     $scope.user = user;
-    $scope.stats = stats;
+    $scope.sessions = sessions;
 
     $scope.logoutAll = function() {
         UserLogout.save({realm : realm.realm, user: user.username}, function () {
             Notifications.success('Logged out user in all applications');
-            UserSessionStats.get({realm: realm.realm, user: user.username}, function(updated) {
-                $scope.stats = updated;
+            UserSessions.get({realm: realm.realm, user: user.username}, function(updated) {
+                $scope.sessions = updated;
             })
         });
     };
 
-    $scope.logoutApplication = function(app) {
-        console.log('log user out of app: ' + app);
-        ApplicationLogoutUser.save({realm : realm.realm, application: app, user: user.username}, function () {
-            Notifications.success('Logged out user from application');
-            UserSessionStats.get({realm: realm.realm, user: user.username}, function(updated) {
-                $scope.stats = updated;
+    $scope.logoutSession = function(sessionId) {
+        console.log('here in logoutSession');
+        UserSessionLogout.delete({realm : realm.realm, session: sessionId}, function() {
+            Notifications.success('Logged out session');
+            UserSessions.get({realm: realm.realm, user: user.username}, function(updated) {
+                $scope.sessions = updated;
             })
         });
-    };
-
-
-
+    }
 });
 
 module.controller('UserSocialCtrl', function($scope, realm, user, socialLinks) {
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/loaders.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/loaders.js
index 53101e4..81a77b7 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/loaders.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/loaders.js
@@ -71,6 +71,14 @@ module.factory('RealmSessionStatsLoader', function(Loader, RealmSessionStats, $r
     });
 });
 
+module.factory('RealmApplicationSessionStatsLoader', function(Loader, RealmApplicationSessionStats, $route, $q) {
+    return Loader.get(RealmApplicationSessionStats, function() {
+        return {
+            realm : $route.current.params.realm
+        }
+    });
+});
+
 module.factory('UserLoader', function(Loader, User, $route, $q) {
     return Loader.get(User, function() {
         return {
@@ -89,6 +97,15 @@ module.factory('UserSessionStatsLoader', function(Loader, UserSessionStats, $rou
     });
 });
 
+module.factory('UserSessionsLoader', function(Loader, UserSessions, $route, $q) {
+    return Loader.query(UserSessions, function() {
+        return {
+            realm : $route.current.params.realm,
+            user : $route.current.params.user
+        }
+    });
+});
+
 module.factory('UserSocialLinksLoader', function(Loader, UserSocialLinks, $route, $q) {
     return Loader.query(UserSocialLinks, function() {
         return {
@@ -134,6 +151,15 @@ module.factory('ApplicationSessionStatsLoader', function(Loader, ApplicationSess
     });
 });
 
+module.factory('ApplicationSessionCountLoader', function(Loader, ApplicationSessionCount, $route, $q) {
+    return Loader.get(ApplicationSessionCount, function() {
+        return {
+            realm : $route.current.params.realm,
+            application : $route.current.params.application
+        }
+    });
+});
+
 module.factory('ApplicationClaimsLoader', function(Loader, ApplicationClaims, $route, $q) {
     return Loader.get(ApplicationClaims, function() {
         return {
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js
index 6d33070..0294169 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js
@@ -183,6 +183,20 @@ module.factory('UserSessionStats', function($resource) {
         user : '@user'
     });
 });
+module.factory('UserSessions', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/users/:user/sessions', {
+        realm : '@realm',
+        user : '@user'
+    });
+});
+
+module.factory('UserSessionLogout', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/sessions/:session', {
+        realm : '@realm',
+        session : '@session'
+    });
+});
+
 module.factory('UserLogout', function($resource) {
     return $resource(authUrl + '/admin/realms/:realm/users/:user/logout', {
         realm : '@realm',
@@ -347,6 +361,12 @@ module.factory('RealmSessionStats', function($resource) {
     });
 });
 
+module.factory('RealmApplicationSessionStats', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/application-session-stats', {
+        realm : '@realm'
+    });
+});
+
 
 module.factory('RoleApplicationComposites', function($resource) {
     return $resource(authUrl + '/admin/realms/:realm/roles-by-id/:role/composites/applications/:application', {
@@ -589,6 +609,20 @@ module.factory('ApplicationSessionStatsWithUsers', function($resource) {
     });
 });
 
+module.factory('ApplicationSessionCount', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/applications/:application/session-count', {
+        realm : '@realm',
+        application : "@application"
+    });
+});
+
+module.factory('ApplicationUserSessions', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/applications/:application/user-sessions', {
+        realm : '@realm',
+        application : "@application"
+    });
+});
+
 module.factory('ApplicationLogoutAll', function($resource) {
     return $resource(authUrl + '/admin/realms/:realm/applications/:application/logout-all', {
         realm : '@realm',
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-sessions.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-sessions.html
index 1bb3682..93c388e 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-sessions.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-sessions.html
@@ -21,38 +21,31 @@
                 <div class="form-group">
                     <label class="col-sm-2 control-label" for="activeSessions">Active Sessions</label>
                     <div class="col-sm-4">
-                        <input class="form-control" type="text" id="activeSessions" name="activeSessions" data-ng-model="stats.activeSessions" ng-disabled="true">
-                    </div>
-                </div>
-                <div class="form-group">
-                    <label class="col-sm-2 control-label" for="activeUsers">Active Users</label>
-                    <div class="col-sm-4">
-                        <input class="form-control" type="text" id="activeUsers" name="activeUsers" data-ng-model="stats.activeUsers" ng-disabled="true">
+                        <input class="form-control" type="text" id="activeSessions" name="activeSessions" data-ng-model="count" ng-disabled="true">
                     </div>
                 </div>
             </fieldset>
         </form>
-        <table class="table table-striped table-bordered" data-ng-show="stats.activeSessions > 0">
+        <table class="table table-striped table-bordered" data-ng-show="count > 0">
             <thead>
             <tr>
                 <th class="kc-table-actions" colspan="3">
                     <div class="pull-right">
-                        <a class="btn btn-primary" ng-click="logoutAll()">Invalidate All Sessions</a>
                         <a class="btn btn-primary" ng-click="loadUsers()">Show Users</a>
                     </div>
                 </th>
             </tr>
             <tr>
                 <th>User</th>
-                <th>Login Time</th>
-                <th></th>
+                <th>From IP</th>
+                <th>Session Start</th>
             </tr>
             </thead>
             <tbody>
-            <tr data-ng-repeat="(user, data) in users">
-                <td><a href="#/realms/{{realm.realm}}/users/{{user}}">{{user}}</a></td>
-                <td>{{data.whenLoggedIn | date:'medium'}}</td>
-                <td><a ng-click="logoutUser(user)">invalidate session</a> </td>
+            <tr data-ng-repeat="session in sessions">
+                <td><a href="#/realms/{{realm.realm}}/users/{{session.user}}">{{session.user}}</a></td>
+                <td>{{session.ipAddress}}</td>
+                <td>{{session.start | date:'medium'}}</td>
             </tr>
             </tbody>
         </table>
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/session-realm.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/session-realm.html
index c50a192..bea3c02 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/session-realm.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/session-realm.html
@@ -24,14 +24,12 @@
             <tr>
                 <th>Application</th>
                 <th>Active Sessions</th>
-                <th>Active Users</th>
             </tr>
             </thead>
             <tbody>
             <tr data-ng-repeat="(application, data) in stats">
                 <td><a href="#/realms/{{realm.realm}}/applications/{{application}}/sessions">{{application}}</a></td>
-                <td>{{data.activeSessions}}</td>
-                <td>{{data.activeUsers}}</td>
+                <td>{{data}}</td>
             </tr>
             </tbody>
         </table>
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-sessions.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-sessions.html
index ef90aa2..cb84ccb 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-sessions.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-sessions.html
@@ -18,23 +18,37 @@
         <table class="table table-striped table-bordered">
             <thead>
             <tr>
-                <th class="kc-table-actions" colspan="3">
+                <th class="kc-table-actions" colspan="5">
                     <div class="pull-right">
-                        <a class="btn btn-primary" ng-click="logoutAll()">Logout</a>
+                        <a class="btn btn-primary" ng-click="logoutAll()">Logout All Sessions</a>
                     </div>
                 </th>
             </tr>
             <tr>
-                <th>Application</th>
+                <th>IP Address</th>
                 <th>Login Time</th>
-                <th></th>
+                <th>Applications</th>
+                <th>OAuth Clients</th>
+                <th>Action</th>
             </tr>
             </thead>
             <tbody>
-            <tr data-ng-repeat="(application, data) in stats">
-                <td><a href="#/realms/{{realm.realm}}/applications/{{application}}/sessions">{{application}}</a></td>
-                <td>{{data.whenLoggedIn | date:'medium'}}</td>
-                <td><a ng-click="logoutApplication(application)">invalidate session</a> </td>
+            <tr data-ng-repeat="session in sessions">
+                <td>{{session.ipAddress}}</td>
+                <td>{{session.start | date:'medium'}}</td>
+                <td><ul style="list-style: none; ">
+                    <li data-ng-repeat="app in session.applications">
+                        <a href="#/realms/{{realm.realm}}/applications/{{app}}/sessions">{{app}}</a>
+                    </li>
+                </ul>
+                </td>
+                <td><ul style="list-style: none; ">
+                    <li data-ng-repeat="(clientId, clientName) in session.clients">
+                        <a href="#/realms/{{realm.realm}}/oauth-clients/{{clientId}}">{{clientName}}</a>
+                    </li>
+                </ul>
+                </td>
+                <td><a ng-click="logoutSession(session.id)">logout</a> </td>
             </tr>
             </tbody>
         </table>
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
index 0842b8d..16203a8 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
@@ -120,7 +120,9 @@ public class PreAuthActionsHandler {
             String user = action.getUser();
             if (user != null) {
                 log.info("logout of session for: " + user);
-                userSessionManagement.logout(user);
+                userSessionManagement.logoutUser(user);
+            } else if (action.getSession() != null) {
+                userSessionManagement.logoutKeycloakSession(action.getSession());
             } else {
                 log.info("logout of all sessions");
                 if (action.getNotBefore() > deployment.getNotBefore()) {
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/UserSessionManagement.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/UserSessionManagement.java
index 94da30f..ca4653a 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/UserSessionManagement.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/UserSessionManagement.java
@@ -15,5 +15,7 @@ public interface UserSessionManagement {
 
     void logoutAll();
 
-    void logout(String user);
+    void logoutUser(String user);
+
+    void logoutKeycloakSession(String id);
 }
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java
index 9e211b2..9a5ea4e 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java
@@ -62,7 +62,7 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
         session.setNote(KeycloakSecurityContext.class.getName(), securityContext);
         String username = securityContext.getToken().getSubject();
         log.debug("userSessionManage.login: " + username);
-        userSessionManagement.login(session, username);
+        userSessionManagement.login(session, username, securityContext.getToken().getSessionState());
     }
 
     @Override
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaUserSessionManagement.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaUserSessionManagement.java
index 52af993..ecca57e 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaUserSessionManagement.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaUserSessionManagement.java
@@ -8,6 +8,7 @@ import org.jboss.logging.Logger;
 import org.keycloak.adapters.UserSessionManagement;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -23,31 +24,21 @@ import java.util.concurrent.ConcurrentHashMap;
 public class CatalinaUserSessionManagement implements SessionListener, UserSessionManagement {
     private static final Logger log = Logger.getLogger(CatalinaUserSessionManagement.class);
     protected ConcurrentHashMap<String, UserSessions> userSessionMap = new ConcurrentHashMap<String, UserSessions>();
+    protected ConcurrentHashMap<String, UserSessions> keycloakSessionMap = new ConcurrentHashMap<String, UserSessions>();
 
     public static class UserSessions {
-        protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();
+        protected String user;
         protected long loggedIn = System.currentTimeMillis();
-
-
-        public Map<String, Session> getSessions() {
-            return sessions;
-        }
-
+        protected Map<String, String>  keycloakSessionToHttpSession = new HashMap<String, String>();
+        protected Map<String, String>  httpSessionToKeycloakSession = new HashMap<String, String>();
+        protected Map<String, Session> sessions = new HashMap<String, Session>();
         public long getLoggedIn() {
             return loggedIn;
         }
     }
 
-    @Override
-    public int getActiveSessions() {
-        int active = 0;
-        synchronized (userSessionMap) {
-            for (UserSessions sessions : userSessionMap.values()) {
-                active += sessions.getSessions().size();
-            }
-
-        }
-        return active;
+    public synchronized int getActiveSessions() {
+        return keycloakSessionMap.size();
     }
 
     /**
@@ -56,60 +47,85 @@ public class CatalinaUserSessionManagement implements SessionListener, UserSessi
      * @return null if user not logged in
      */
     @Override
-    public Long getUserLoginTime(String username) {
+    public synchronized Long getUserLoginTime(String username) {
         UserSessions sessions = userSessionMap.get(username);
         if (sessions == null) return null;
         return sessions.getLoggedIn();
     }
 
     @Override
-    public Set<String> getActiveUsers() {
+    public synchronized Set<String> getActiveUsers() {
         HashSet<String> set = new HashSet<String>();
         set.addAll(userSessionMap.keySet());
         return set;
     }
 
 
-    protected void login(Session session, String username) {
-        synchronized (userSessionMap) {
-            UserSessions userSessions = userSessionMap.get(username);
-            if (userSessions == null) {
-                userSessions = new UserSessions();
-                userSessionMap.put(username, userSessions);
-            }
-            userSessions.getSessions().put(session.getId(), session);
+    public synchronized void login(Session session, String username, String keycloakSessionId) {
+        String sessionId = session.getId();
+
+        UserSessions sessions = userSessionMap.get(username);
+        if (sessions == null) {
+            sessions = new UserSessions();
+            sessions.user = username;
+            userSessionMap.put(username, sessions);
         }
+        keycloakSessionMap.put(keycloakSessionId, sessions);
+        sessions.httpSessionToKeycloakSession.put(sessionId, keycloakSessionId);
+        sessions.keycloakSessionToHttpSession.put(keycloakSessionId, sessionId);
         session.addSessionListener(this);
     }
 
     @Override
     public void logoutAll() {
-        List<String> users = new ArrayList<String>();
-        users.addAll(userSessionMap.keySet());
-        for (String user : users) logout(user);
+        for (String user : userSessionMap.keySet()) logoutUser(user);
     }
 
     @Override
-    public void logout(String user) {
+    public void logoutUser(String user) {
         log.debug("logoutUser: " + user);
         UserSessions sessions = null;
-        synchronized (userSessionMap) {
-            sessions = userSessionMap.remove(user);
-
-        }
+        sessions = userSessionMap.remove(user);
         if (sessions == null) {
             log.debug("no session for user: " + user);
             return;
-
         }
         log.debug("found session for user");
-        for (Session session : sessions.getSessions().values()) {
+        for (Map.Entry<String, String> entry : sessions.httpSessionToKeycloakSession.entrySet()) {
+            log.debug("invalidating session for user: " + user);
+            String sessionId = entry.getKey();
+            String keycloakSessionId = entry.getValue();
+            Session session = sessions.sessions.get(sessionId);
             session.setPrincipal(null);
             session.setAuthType(null);
             session.getSession().invalidate();
+            keycloakSessionMap.remove(keycloakSessionId);
         }
     }
 
+    public synchronized void logoutKeycloakSession(String keycloakSessionId) {
+        log.debug("logoutKeycloakSession: " + keycloakSessionId);
+        UserSessions sessions = keycloakSessionMap.remove(keycloakSessionId);
+        if (sessions == null) {
+            log.debug("no session for keycloak session id: " + keycloakSessionId);
+            return;
+        }
+        String sessionId = sessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
+        if (sessionId == null) {
+            log.debug("no session for keycloak session id: " + keycloakSessionId);
+
+        }
+        sessions.httpSessionToKeycloakSession.remove(sessionId);
+        Session session = sessions.sessions.remove(sessionId);
+        session.setPrincipal(null);
+        session.setAuthType(null);
+        session.getSession().invalidate();
+        if (sessions.keycloakSessionToHttpSession.size() == 0) {
+            userSessionMap.remove(sessions.user);
+        }
+    }
+
+
     public void sessionEvent(SessionEvent event) {
         // We only care about session destroyed events
         if (!Session.SESSION_DESTROYED_EVENT.equals(event.getType())
@@ -124,14 +140,22 @@ public class CatalinaUserSessionManagement implements SessionListener, UserSessi
         session.setAuthType(null);
 
         String username = principal.getUserPrincipal().getName();
-        synchronized (userSessionMap) {
-            UserSessions sessions = userSessionMap.get(username);
-            if (sessions != null) {
-                sessions.getSessions().remove(session.getId());
-                if (sessions.getSessions().isEmpty()) {
-                    userSessionMap.remove(username);
-                }
+        UserSessions userSessions = userSessionMap.get(username);
+        if (userSessions == null) {
+            return;
+        }
+        String sessionid = session.getId();
+        synchronized (this) {
+            String keycloakSessionId = userSessions.httpSessionToKeycloakSession.remove(sessionid);
+            if (keycloakSessionId != null) {
+                userSessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
+                keycloakSessionMap.remove(keycloakSessionId);
+            }
+            userSessions.sessions.remove(sessionid);
+            if (userSessions.httpSessionToKeycloakSession.size() == 0) {
+                userSessionMap.remove(username);
             }
+
         }
     }
 }
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java
index 7ed1f1d..27a003d 100755
--- a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java
@@ -62,7 +62,7 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
         session.setNote(KeycloakSecurityContext.class.getName(), securityContext);
         String username = securityContext.getToken().getSubject();
         log.finer("userSessionManage.login: " + username);
-        userSessionManagement.login(session, username);
+        userSessionManagement.login(session, username, securityContext.getToken().getSessionState());
     }
 
     @Override
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java
index a84284e..8b521e8 100755
--- a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java
@@ -1,6 +1,7 @@
 package org.keycloak.adapters.tomcat7;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -23,31 +24,21 @@ import org.keycloak.adapters.UserSessionManagement;
 public class CatalinaUserSessionManagement implements SessionListener, UserSessionManagement {
     private static final Logger log = Logger.getLogger(""+CatalinaUserSessionManagement.class);
     protected ConcurrentHashMap<String, UserSessions> userSessionMap = new ConcurrentHashMap<String, UserSessions>();
+    protected ConcurrentHashMap<String, UserSessions> keycloakSessionMap = new ConcurrentHashMap<String, UserSessions>();
 
     public static class UserSessions {
-        protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();
+        protected String user;
         protected long loggedIn = System.currentTimeMillis();
-
-
-        public Map<String, Session> getSessions() {
-            return sessions;
-        }
-
+        protected Map<String, String>  keycloakSessionToHttpSession = new HashMap<String, String>();
+        protected Map<String, String>  httpSessionToKeycloakSession = new HashMap<String, String>();
+        protected Map<String, Session> sessions = new HashMap<String, Session>();
         public long getLoggedIn() {
             return loggedIn;
         }
     }
 
-    @Override
-    public int getActiveSessions() {
-        int active = 0;
-        synchronized (userSessionMap) {
-            for (UserSessions sessions : userSessionMap.values()) {
-                active += sessions.getSessions().size();
-            }
-
-        }
-        return active;
+    public synchronized int getActiveSessions() {
+        return keycloakSessionMap.size();
     }
 
     /**
@@ -56,60 +47,78 @@ public class CatalinaUserSessionManagement implements SessionListener, UserSessi
      * @return null if user not logged in
      */
     @Override
-    public Long getUserLoginTime(String username) {
+    public synchronized Long getUserLoginTime(String username) {
         UserSessions sessions = userSessionMap.get(username);
         if (sessions == null) return null;
         return sessions.getLoggedIn();
     }
 
     @Override
-    public Set<String> getActiveUsers() {
+    public synchronized Set<String> getActiveUsers() {
         HashSet<String> set = new HashSet<String>();
         set.addAll(userSessionMap.keySet());
         return set;
     }
 
-    protected void login(Session session, String username) {
-        synchronized (userSessionMap) {
-            UserSessions userSessions = userSessionMap.get(username);
-            if (userSessions == null) {
-                userSessions = new UserSessions();
-                userSessionMap.put(username, userSessions);
-            }
-            userSessions.getSessions().put(session.getId(), session);
+
+    public synchronized void login(Session session, String username, String keycloakSessionId) {
+        String sessionId = session.getId();
+
+        UserSessions sessions = userSessionMap.get(username);
+        if (sessions == null) {
+            sessions = new UserSessions();
+            sessions.user = username;
+            userSessionMap.put(username, sessions);
         }
+        keycloakSessionMap.put(keycloakSessionId, sessions);
+        sessions.httpSessionToKeycloakSession.put(sessionId, keycloakSessionId);
+        sessions.keycloakSessionToHttpSession.put(keycloakSessionId, sessionId);
         session.addSessionListener(this);
     }
 
     @Override
     public void logoutAll() {
-        List<String> users = new ArrayList<String>();
-        users.addAll(userSessionMap.keySet());
-        for (String user : users) logout(user);
+        for (String user : userSessionMap.keySet()) logoutUser(user);
     }
 
     @Override
-    public void logout(String user) {
-        log.finer("logoutUser: " + user);
+    public void logoutUser(String user) {
         UserSessions sessions = null;
-        synchronized (userSessionMap) {
-            sessions = userSessionMap.remove(user);
-
-        }
+        sessions = userSessionMap.remove(user);
         if (sessions == null) {
-            log.finer("no session for user: " + user);
             return;
-
         }
-        
-        log.finer("found session for user");
-        for (Session session : sessions.getSessions().values()) {
+        for (Map.Entry<String, String> entry : sessions.httpSessionToKeycloakSession.entrySet()) {
+            String sessionId = entry.getKey();
+            String keycloakSessionId = entry.getValue();
+            Session session = sessions.sessions.get(sessionId);
             session.setPrincipal(null);
             session.setAuthType(null);
             session.getSession().invalidate();
+            keycloakSessionMap.remove(keycloakSessionId);
         }
     }
 
+    public synchronized void logoutKeycloakSession(String keycloakSessionId) {
+        UserSessions sessions = keycloakSessionMap.remove(keycloakSessionId);
+        if (sessions == null) {
+            return;
+        }
+        String sessionId = sessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
+        if (sessionId == null) {
+
+        }
+        sessions.httpSessionToKeycloakSession.remove(sessionId);
+        Session session = sessions.sessions.remove(sessionId);
+        session.setPrincipal(null);
+        session.setAuthType(null);
+        session.getSession().invalidate();
+        if (sessions.keycloakSessionToHttpSession.size() == 0) {
+            userSessionMap.remove(sessions.user);
+        }
+    }
+
+
     public void sessionEvent(SessionEvent event) {
         // We only care about session destroyed events
         if (!Session.SESSION_DESTROYED_EVENT.equals(event.getType())
@@ -124,14 +133,22 @@ public class CatalinaUserSessionManagement implements SessionListener, UserSessi
         session.setAuthType(null);
 
         String username = principal.getUserPrincipal().getName();
-        synchronized (userSessionMap) {
-            UserSessions sessions = userSessionMap.get(username);
-            if (sessions != null) {
-                sessions.getSessions().remove(session.getId());
-                if (sessions.getSessions().isEmpty()) {
-                    userSessionMap.remove(username);
-                }
+        UserSessions userSessions = userSessionMap.get(username);
+        if (userSessions == null) {
+            return;
+        }
+        String sessionid = session.getId();
+        synchronized (this) {
+            String keycloakSessionId = userSessions.httpSessionToKeycloakSession.remove(sessionid);
+            if (keycloakSessionId != null) {
+                userSessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
+                keycloakSessionMap.remove(keycloakSessionId);
+            }
+            userSessions.sessions.remove(sessionid);
+            if (userSessions.httpSessionToKeycloakSession.size() == 0) {
+                userSessionMap.remove(username);
             }
+
         }
     }
 }
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
index f9c2edf..f73227f 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
@@ -66,7 +66,7 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
         HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
         HttpSession session = req.getSession(true);
         session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
-        userSessionManagement.login(servletRequestContext.getDeployment().getSessionManager(), session, account.getPrincipal().getName());
+        userSessionManagement.login(servletRequestContext.getDeployment().getSessionManager(), session, account.getPrincipal().getName(), account.getKeycloakSecurityContext().getToken().getSessionState());
 
     }
 }
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java
index 4a5a4c2..11682f0 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java
@@ -1,6 +1,5 @@
 package org.keycloak.adapters.undertow;
 
-import io.undertow.server.HttpServerExchange;
 import io.undertow.server.session.SessionManager;
 import org.keycloak.adapters.UserSessionManagement;
 
@@ -41,7 +40,12 @@ public class SessionManagementBridge implements UserSessionManagement {
     }
 
     @Override
-    public void logout(String user) {
-        userSessionManagement.logout(sessionManager, user);
+    public void logoutUser(String user) {
+        userSessionManagement.logoutUser(sessionManager, user);
+    }
+
+    @Override
+    public void logoutKeycloakSession(String id) {
+        userSessionManagement.logoutKeycloakSession(sessionManager, id);
     }
 }
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java
index ff75800..1c11d37 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java
@@ -6,19 +6,14 @@ import io.undertow.server.session.Session;
 import io.undertow.server.session.SessionListener;
 import io.undertow.server.session.SessionManager;
 import io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler;
-import io.undertow.util.StatusCodes;
 import org.jboss.logging.Logger;
-import org.keycloak.adapters.KeycloakDeployment;
-import org.keycloak.jose.jws.JWSInput;
-import org.keycloak.representations.adapters.action.LogoutAction;
-import org.keycloak.util.JsonSerialization;
 
-import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
-import java.io.IOException;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -32,30 +27,22 @@ public class UndertowUserSessionManagement implements SessionListener {
     private static final Logger log = Logger.getLogger(UndertowUserSessionManagement.class);
     private static final String AUTH_SESSION_NAME = CachedAuthenticatedSessionHandler.class.getName() + ".AuthenticatedSession";
     protected ConcurrentHashMap<String, UserSessions> userSessionMap = new ConcurrentHashMap<String, UserSessions>();
+    protected ConcurrentHashMap<String, UserSessions> keycloakSessionMap = new ConcurrentHashMap<String, UserSessions>();
+    protected volatile boolean registered;
 
 
     public static class UserSessions {
-        protected Set<String> sessionIds = new HashSet<String>();
+        protected String user;
         protected long loggedIn = System.currentTimeMillis();
-
-        public Set<String> getSessionIds() {
-            return sessionIds;
-        }
-
+        protected Map<String, String>  keycloakSessionToHttpSession = new HashMap<String, String>();
+        protected Map<String, String>  httpSessionToKeycloakSession = new HashMap<String, String>();
         public long getLoggedIn() {
             return loggedIn;
         }
     }
 
-    public int getActiveSessions() {
-        int active = 0;
-        synchronized (userSessionMap) {
-            for (UserSessions sessions : userSessionMap.values()) {
-                active += sessions.getSessionIds().size();
-            }
-
-        }
-        return active;
+    public synchronized int getActiveSessions() {
+        return keycloakSessionMap.size();
     }
 
     /**
@@ -63,74 +50,88 @@ public class UndertowUserSessionManagement implements SessionListener {
      * @param username
      * @return null if user not logged in
      */
-    public Long getUserLoginTime(String username) {
+    public synchronized Long getUserLoginTime(String username) {
         UserSessions sessions = userSessionMap.get(username);
         if (sessions == null) return null;
         return sessions.getLoggedIn();
     }
 
-    public Set<String> getActiveUsers() {
+    public synchronized Set<String> getActiveUsers() {
         HashSet<String> set = new HashSet<String>();
         set.addAll(userSessionMap.keySet());
         return set;
     }
 
-    public void login(SessionManager manager, HttpSession session, String username) {
+    public synchronized void login(SessionManager manager, HttpSession session, String username, String keycloakSessionId) {
         String sessionId = session.getId();
-        addAuthenticatedSession(username, sessionId);
-        manager.registerSessionListener(this);
-    }
 
-    protected void addAuthenticatedSession(String username, String sessionId) {
-        synchronized (userSessionMap) {
-            UserSessions sessions = userSessionMap.get(username);
-            if (sessions == null) {
-                sessions = new UserSessions();
-                userSessionMap.put(username, sessions);
-            }
-            sessions.getSessionIds().add(sessionId);
+        UserSessions sessions = userSessionMap.get(username);
+        if (sessions == null) {
+            sessions = new UserSessions();
+            sessions.user = username;
+            userSessionMap.put(username, sessions);
         }
-    }
-
-    protected void removeAuthenticatedSession(String sessionId, String username) {
-        synchronized (userSessionMap) {
-            UserSessions sessions = userSessionMap.get(username);
-            if (sessions == null) return;
-            sessions.getSessionIds().remove(sessionId);
-            if (sessions.getSessionIds().isEmpty()) {
-                userSessionMap.remove(username);
-            }
+        sessions.httpSessionToKeycloakSession.put(sessionId, keycloakSessionId);
+        sessions.keycloakSessionToHttpSession.put(keycloakSessionId, sessionId);
+        keycloakSessionMap.put(keycloakSessionId, sessions);
+        if (!registered) {
+            manager.registerSessionListener(this);
+            registered = true;
         }
     }
 
-    public void logoutAll(SessionManager manager) {
-        List<String> users = new ArrayList<String>();
-        users.addAll(userSessionMap.keySet());
-        for (String user : users) logout(manager, user);
+    public synchronized void logoutAll(SessionManager manager) {
+        for (String user : userSessionMap.keySet()) logoutUser(manager, user);
     }
 
-    public void logout(SessionManager manager, String user) {
-        log.info("logoutUser: " + user);
+    public synchronized void logoutUser(SessionManager manager, String user) {
+        log.debug("logoutUser: " + user);
         UserSessions sessions = null;
-        synchronized (userSessionMap) {
-            sessions = userSessionMap.remove(user);
-        }
+        sessions = userSessionMap.remove(user);
         if (sessions == null) {
-            log.info("no session for user: " + user);
+            log.debug("no session for user: " + user);
             return;
         }
-        log.info("found session for user");
-        for (String id : sessions.getSessionIds()) {
+        log.debug("found session for user");
+        for (Map.Entry<String, String> entry : sessions.httpSessionToKeycloakSession.entrySet()) {
             log.debug("invalidating session for user: " + user);
-            Session session = manager.getSession(id);
+            String sessionId = entry.getKey();
+            String keycloakSessionId = entry.getValue();
+            Session session = manager.getSession(sessionId);
             try {
                 session.invalidate(null);
             } catch (Exception e) {
                 log.warn("Session already invalidated.");
             }
+            keycloakSessionMap.remove(keycloakSessionId);
         }
     }
 
+    public synchronized void logoutKeycloakSession(SessionManager manager, String keycloakSessionId) {
+        log.debug("logoutKeycloakSession: " + keycloakSessionId);
+        UserSessions sessions = keycloakSessionMap.remove(keycloakSessionId);
+        if (sessions == null) {
+            log.debug("no session for keycloak session id: " + keycloakSessionId);
+            return;
+        }
+        String sessionId = sessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
+        if (sessionId == null) {
+            log.debug("no session for keycloak session id: " + keycloakSessionId);
+
+        }
+        sessions.httpSessionToKeycloakSession.remove(sessionId);
+        Session session = manager.getSession(sessionId);
+        try {
+            session.invalidate(null);
+        } catch (Exception e) {
+            log.warn("Session already invalidated.");
+        }
+        if (sessions.keycloakSessionToHttpSession.size() == 0) {
+            userSessionMap.remove(sessions.user);
+        }
+    }
+
+
     @Override
     public void sessionCreated(Session session, HttpServerExchange exchange) {
     }
@@ -141,7 +142,21 @@ public class UndertowUserSessionManagement implements SessionListener {
         String username = getUsernameFromSession(session);
         if (username == null) return;
         String sessionId = session.getId();
-        removeAuthenticatedSession(sessionId, username);
+        UserSessions userSessions = userSessionMap.get(username);
+        if (userSessions == null) {
+            return;
+        }
+        synchronized (this) {
+            String keycloakSessionId = userSessions.httpSessionToKeycloakSession.remove(sessionId);
+            if (keycloakSessionId != null) {
+                userSessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
+                keycloakSessionMap.remove(keycloakSessionId);
+            }
+            if (userSessions.httpSessionToKeycloakSession.size() == 0) {
+                userSessionMap.remove(username);
+            }
+
+        }
     }
 
     protected String getUsernameFromSession(Session session) {
@@ -156,8 +171,21 @@ public class UndertowUserSessionManagement implements SessionListener {
     public void sessionIdChanged(Session session, String oldSessionId) {
         String username = getUsernameFromSession(session);
         if (username == null) return;
-        removeAuthenticatedSession(oldSessionId, username);
-        addAuthenticatedSession(session.getId(), username);
+        String sessionId = session.getId();
+
+        UserSessions userSessions = userSessionMap.get(username);
+        if (userSessions == null) {
+            return;
+        }
+
+        synchronized (this) {
+            String keycloakSessionId = userSessions.httpSessionToKeycloakSession.remove(oldSessionId);
+            if (keycloakSessionId != null) {
+                userSessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
+                userSessions.keycloakSessionToHttpSession.put(keycloakSessionId, sessionId);
+                userSessions.httpSessionToKeycloakSession.put(sessionId, keycloakSessionId);
+            }
+        }
     }
 
     @Override
diff --git a/model/api/src/main/java/org/keycloak/models/ClientModel.java b/model/api/src/main/java/org/keycloak/models/ClientModel.java
index b824db6..d88812d 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java
@@ -1,5 +1,6 @@
 package org.keycloak.models;
 
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -64,4 +65,7 @@ public interface ClientModel {
 
     void setNotBefore(int notBefore);
 
+    Set<UserSessionModel> getUserSessions();
+
+    int getActiveUserSessions();
 }
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index c48613b..68a89ea 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -263,4 +263,7 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
 
     void removeExpiredUserSessions();
 
+    ClientModel findClientById(String id);
+
+    void removeUserSessions();
 }
diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
index e2e16a7..ada5899 100755
--- a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
@@ -1,5 +1,8 @@
 package org.keycloak.models;
 
+import java.util.List;
+import java.util.Set;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -25,4 +28,9 @@ public interface UserSessionModel {
 
     void setLastSessionRefresh(int seconds);
 
+    void associateClient(ClientModel client);
+
+    List<ClientModel> getClientAssociations();
+
+    void removeAssociatedClient(ClientModel client);
 }
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 d23a0f4..6616c36 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
@@ -27,7 +27,7 @@ public class ApplicationAdapter extends ClientAdapter implements ApplicationMode
     protected ApplicationEntity applicationEntity;
 
     public ApplicationAdapter(RealmModel realm, EntityManager em, ApplicationEntity applicationEntity) {
-        super(realm, applicationEntity);
+        super(realm, applicationEntity, em);
         this.realm = realm;
         this.em = em;
         this.applicationEntity = applicationEntity;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
index 3267344..99e5d1b 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
@@ -2,10 +2,16 @@ package org.keycloak.models.jpa;
 
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.jpa.entities.ClientEntity;
-import org.keycloak.models.jpa.entities.OAuthClientEntity;
+import org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity;
 
+import javax.persistence.EntityManager;
+import javax.persistence.Query;
+import javax.persistence.TypedQuery;
+import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -15,10 +21,12 @@ import java.util.Set;
 public class ClientAdapter implements ClientModel {
     protected ClientEntity entity;
     protected RealmModel realm;
+    protected EntityManager em;
 
-    public ClientAdapter(RealmModel realm, ClientEntity entity) {
+    public ClientAdapter(RealmModel realm, ClientEntity entity, EntityManager em) {
         this.realm = realm;
         this.entity = entity;
+        this.em = em;
     }
 
     public ClientEntity getEntity() {
@@ -142,6 +150,31 @@ public class ClientAdapter implements ClientModel {
     }
 
     @Override
+    public int getActiveUserSessions() {
+        Query query = em.createNamedQuery("getActiveClientSessions");
+        query.setParameter("clientId", getId());
+        Object count = query.getSingleResult();
+        return ((Number)count).intValue();
+    }
+
+    @Override
+    public Set<UserSessionModel> getUserSessions() {
+        Set<UserSessionModel> list = new HashSet<UserSessionModel>();
+        TypedQuery<ClientUserSessionAssociationEntity> query = em.createNamedQuery("getClientUserSessionByClient", ClientUserSessionAssociationEntity.class);
+        String id = getId();
+        query.setParameter("clientId", id);
+        List<ClientUserSessionAssociationEntity> results = query.getResultList();
+        for (ClientUserSessionAssociationEntity entity : results) {
+            list.add(new UserSessionAdapter(em, realm, entity.getSession()));
+        }
+        return list;
+    }
+
+    public void deleteUserSessionAssociation() {
+        em.createNamedQuery("removeClientUserSessionByClient").setParameter("clientId", getId()).executeUpdate();
+    }
+
+    @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (!this.getClass().equals(o.getClass())) return false;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientUserSessionAssociationEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientUserSessionAssociationEntity.java
new file mode 100755
index 0000000..3c38142
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientUserSessionAssociationEntity.java
@@ -0,0 +1,83 @@
+package org.keycloak.models.jpa.entities;
+
+import org.hibernate.annotations.GenericGenerator;
+
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@Entity
+@NamedQueries({
+        @NamedQuery(name = "getAllClientUserSessions", query = "select s from ClientUserSessionAssociationEntity s"),
+        @NamedQuery(name = "getClientUserSessionBySession", query = "select s from ClientUserSessionAssociationEntity s where s.session = :session"),
+        @NamedQuery(name = "getClientUserSessionByClient", query = "select s from ClientUserSessionAssociationEntity s where s.clientId = :clientId"),
+        @NamedQuery(name = "getActiveClientSessions", query = "select COUNT(s) from ClientUserSessionAssociationEntity s where s.clientId = :clientId"),
+        @NamedQuery(name = "removeClientUserSessionByClient", query = "delete from ClientUserSessionAssociationEntity s where s.clientId = :clientId"),
+        @NamedQuery(name = "removeClientUserSessionByUser", query = "delete from ClientUserSessionAssociationEntity s where s.user = :user"),
+        @NamedQuery(name = "removeClientUserSessionByRealm", query = "delete from ClientUserSessionAssociationEntity s where s.realm = :realm")
+})
+public class ClientUserSessionAssociationEntity {
+    @Id
+    @GenericGenerator(name="uuid_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
+    @GeneratedValue(generator = "uuid_generator")
+    private String id;
+
+    @ManyToOne(fetch= FetchType.LAZY)
+    private UserSessionEntity session;
+
+    @ManyToOne(fetch= FetchType.LAZY)
+    private UserEntity user;
+
+    @ManyToOne(fetch= FetchType.LAZY)
+    private RealmEntity realm;
+
+    private String clientId;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public UserEntity getUser() {
+        return user;
+    }
+
+    public void setUser(UserEntity user) {
+        this.user = user;
+    }
+
+    public UserSessionEntity getSession() {
+        return session;
+    }
+
+    public void setSession(UserSessionEntity session) {
+        this.session = session;
+    }
+
+    public RealmEntity getRealm() {
+        return realm;
+    }
+
+    public void setRealm(RealmEntity realm) {
+        this.realm = realm;
+    }
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public void setClientId(String clientId) {
+        this.clientId = clientId;
+    }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java
index 62769ea..976df2f 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java
@@ -3,12 +3,17 @@ package org.keycloak.models.jpa.entities;
 import org.hibernate.annotations.GenericGenerator;
 import org.keycloak.models.UserModel;
 
+import javax.persistence.CascadeType;
 import javax.persistence.Entity;
+import javax.persistence.FetchType;
 import javax.persistence.GeneratedValue;
 import javax.persistence.Id;
 import javax.persistence.ManyToOne;
 import javax.persistence.NamedQueries;
 import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import java.util.ArrayList;
+import java.util.Collection;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -16,6 +21,7 @@ import javax.persistence.NamedQuery;
 @Entity
 @NamedQueries({
         @NamedQuery(name = "getUserSessionByUser", query = "select s from UserSessionEntity s where s.user = :user"),
+        @NamedQuery(name = "removeRealmUserSessions", query = "delete from UserSessionEntity s where s.realm = :realm"),
         @NamedQuery(name = "removeUserSessionByUser", query = "delete from UserSessionEntity s where s.user = :user"),
         @NamedQuery(name = "removeUserSessionExpired", query = "delete from UserSessionEntity s where s.started < :maxTime or s.lastSessionRefresh < :idleTime")
 })
@@ -29,12 +35,19 @@ public class UserSessionEntity {
     @ManyToOne
     private UserEntity user;
 
+    @ManyToOne(fetch = FetchType.LAZY)
+    private RealmEntity realm;
+
     String ipAddress;
 
     int started;
 
     int lastSessionRefresh;
 
+    @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy="session")
+    Collection<ClientUserSessionAssociationEntity> clients = new ArrayList<ClientUserSessionAssociationEntity>();
+
+
     public String getId() {
         return id;
     }
@@ -74,4 +87,20 @@ public class UserSessionEntity {
     public void setLastSessionRefresh(int lastSessionRefresh) {
         this.lastSessionRefresh = lastSessionRefresh;
     }
+
+    public Collection<ClientUserSessionAssociationEntity> getClients() {
+        return clients;
+    }
+
+    public void setClients(Collection<ClientUserSessionAssociationEntity> clients) {
+        this.clients = clients;
+    }
+
+    public RealmEntity getRealm() {
+        return realm;
+    }
+
+    public void setRealm(RealmEntity realm) {
+        this.realm = realm;
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java
index 5614053..36aabd6 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java
@@ -80,6 +80,7 @@ public class JpaKeycloakSession implements KeycloakSession {
         }
 
         RealmAdapter adapter = new RealmAdapter(em, realm);
+        adapter.removeUserSessions();
         for (ApplicationEntity a : new LinkedList<ApplicationEntity>(realm.getApplications())) {
             adapter.removeApplication(a.getId());
         }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/OAuthClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/OAuthClientAdapter.java
index 48d0e4f..cd78093 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/OAuthClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/OAuthClientAdapter.java
@@ -5,6 +5,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.jpa.entities.OAuthClientEntity;
 
+import javax.persistence.EntityManager;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -14,8 +15,8 @@ import java.util.Set;
  */
 public class OAuthClientAdapter extends ClientAdapter implements OAuthClientModel {
 
-    public OAuthClientAdapter(RealmModel realm, OAuthClientEntity entity) {
-        super(realm, entity);
+    public OAuthClientAdapter(RealmModel realm, OAuthClientEntity entity, EntityManager em) {
+        super(realm, entity, em);
     }
 
     @Override
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 3f420c8..95e5ed0 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -72,6 +72,10 @@ public class RealmAdapter implements RealmModel {
         this.realm = realm;
     }
 
+    public RealmEntity getEntity() {
+        return realm;
+    }
+
     @Override
     public String getId() {
         return realm.getId();
@@ -583,6 +587,13 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public ClientModel findClientById(String id) {
+        ClientModel model = getApplicationById(id);
+        if (model != null) return model;
+        return getOAuthClientById(id);
+    }
+
+    @Override
     public Map<String, ApplicationModel> getApplicationNameMap() {
         Map<String, ApplicationModel> map = new HashMap<String, ApplicationModel>();
         for (ApplicationModel app : getApplications()) {
@@ -627,6 +638,7 @@ public class RealmAdapter implements RealmModel {
         ApplicationModel application = getApplicationById(id);
         if (application == null) return false;
 
+        ((ApplicationAdapter)application).deleteUserSessionAssociation();
         for (RoleModel role : application.getRoles()) {
             application.removeRole(role);
         }
@@ -846,13 +858,14 @@ public class RealmAdapter implements RealmModel {
         data.setRealm(realm);
         em.persist(data);
         em.flush();
-        return new OAuthClientAdapter(this, data);
+        return new OAuthClientAdapter(this, data, em);
     }
 
     @Override
     public boolean removeOAuthClient(String id) {
         OAuthClientModel oauth = getOAuthClientById(id);
         if (oauth == null) return false;
+        ((OAuthClientAdapter)oauth).deleteUserSessionAssociation();
         OAuthClientEntity client = (OAuthClientEntity) ((OAuthClientAdapter) oauth).getEntity();
         em.createQuery("delete from " + ScopeMappingEntity.class.getSimpleName() + " where client = :client").setParameter("client", client).executeUpdate();
         em.remove(client);
@@ -867,7 +880,7 @@ public class RealmAdapter implements RealmModel {
         query.setParameter("realm", realm);
         List<OAuthClientEntity> entities = query.getResultList();
         if (entities.size() == 0) return null;
-        return new OAuthClientAdapter(this, entities.get(0));
+        return new OAuthClientAdapter(this, entities.get(0), em);
     }
 
     @Override
@@ -876,7 +889,7 @@ public class RealmAdapter implements RealmModel {
 
         // Check if client belongs to this realm
         if (client == null || !this.realm.getId().equals(client.getRealm().getId())) return null;
-        return new OAuthClientAdapter(this, client);
+        return new OAuthClientAdapter(this, client, em);
     }
 
 
@@ -886,7 +899,7 @@ public class RealmAdapter implements RealmModel {
         query.setParameter("realm", realm);
         List<OAuthClientEntity> entities = query.getResultList();
         List<OAuthClientModel> list = new ArrayList<OAuthClientModel>();
-        for (OAuthClientEntity entity : entities) list.add(new OAuthClientAdapter(this, entity));
+        for (OAuthClientEntity entity : entities) list.add(new OAuthClientAdapter(this, entity, em));
         return list;
     }
 
@@ -1385,6 +1398,7 @@ public class RealmAdapter implements RealmModel {
     @Override
     public UserSessionModel createUserSession(UserModel user, String ipAddress) {
         UserSessionEntity entity = new UserSessionEntity();
+        entity.setRealm(realm);
         entity.setUser(((UserAdapter) user).getUser());
         entity.setIpAddress(ipAddress);
 
@@ -1394,20 +1408,20 @@ public class RealmAdapter implements RealmModel {
         entity.setLastSessionRefresh(currentTime);
 
         em.persist(entity);
-        return new UserSessionAdapter(entity);
+        return new UserSessionAdapter(em, this, entity);
     }
 
     @Override
     public UserSessionModel getUserSession(String id) {
         UserSessionEntity entity = em.find(UserSessionEntity.class, id);
-        return entity != null ? new UserSessionAdapter(entity) : null;
+        return entity != null ? new UserSessionAdapter(em, this, entity) : null;
     }
 
     @Override
     public List<UserSessionModel> getUserSessions(UserModel user) {
         List<UserSessionModel> sessions = new LinkedList<UserSessionModel>();
         for (UserSessionEntity e : em.createNamedQuery("getUserSessionByUser", UserSessionEntity.class).setParameter("user", ((UserAdapter) user).getUser()).getResultList()) {
-            sessions.add(new UserSessionAdapter(e));
+            sessions.add(new UserSessionAdapter(em, this, e));
         }
         return sessions;
     }
@@ -1418,11 +1432,19 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public void removeUserSessions() {
+        em.createNamedQuery("removeClientUserSessionByRealm").setParameter("realm", realm).executeUpdate();
+        em.createNamedQuery("removeRealmUserSessions").setParameter("realm", realm).executeUpdate();
+
+    }
+
+    @Override
     public void removeUserSessions(UserModel user) {
         removeUserSessions(((UserAdapter) user).getUser());
     }
 
     private void removeUserSessions(UserEntity user) {
+        em.createNamedQuery("removeClientUserSessionByUser").setParameter("user", user).executeUpdate();
         em.createNamedQuery("removeUserSessionByUser").setParameter("user", user).executeUpdate();
     }
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserSessionAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserSessionAdapter.java
index 0de48e5..79e1827 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserSessionAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserSessionAdapter.java
@@ -1,18 +1,30 @@
 package org.keycloak.models.jpa;
 
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity;
 import org.keycloak.models.jpa.entities.UserSessionEntity;
 
+import javax.persistence.EntityManager;
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
 public class UserSessionAdapter implements UserSessionModel {
 
+    private RealmModel realm;
     private UserSessionEntity entity;
+    private EntityManager em;
 
-    public UserSessionAdapter(UserSessionEntity entity) {
+    public UserSessionAdapter(EntityManager em, RealmModel realm, UserSessionEntity entity) {
         this.entity = entity;
+        this.em = em;
+        this.realm = realm;
     }
 
     public UserSessionEntity getEntity() {
@@ -68,4 +80,51 @@ public class UserSessionAdapter implements UserSessionModel {
     public void setLastSessionRefresh(int seconds) {
         entity.setLastSessionRefresh(seconds);
     }
+
+    @Override
+    public void associateClient(ClientModel client) {
+        for (ClientUserSessionAssociationEntity ass : entity.getClients()) {
+            if (ass.getClientId().equals(client.getId())) return;
+        }
+        ClientUserSessionAssociationEntity association = new ClientUserSessionAssociationEntity();
+        association.setClientId(client.getId());
+        association.setSession(entity);
+        association.setUser(entity.getUser());
+        association.setRealm(((RealmAdapter)realm).getEntity());
+        em.persist(association);
+        entity.getClients().add(association);
+    }
+
+    @Override
+    public void removeAssociatedClient(ClientModel client) {
+        em.createNamedQuery("removeClientUserSessionByClient").setParameter("clientId", client.getId()).executeUpdate();
+
+    }
+
+    @Override
+    public List<ClientModel> getClientAssociations() {
+        List<ClientModel> clients = new ArrayList<ClientModel>();
+        for (ClientUserSessionAssociationEntity association : entity.getClients()) {
+            ClientModel client = realm.findClientById(association.getClientId());
+            if (client == null) {
+                throw new ModelException("couldnt find client");
+            }
+            clients.add(client);
+        }
+        return clients;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        UserSessionAdapter that = (UserSessionAdapter) o;
+        return that.getId().equals(this.getId());
+    }
+
+    @Override
+    public int hashCode() {
+        return getId().hashCode();
+    }
 }
diff --git a/model/jpa/src/test/resources/META-INF/persistence.xml b/model/jpa/src/test/resources/META-INF/persistence.xml
index 900cb5d..c79ddab 100755
--- a/model/jpa/src/test/resources/META-INF/persistence.xml
+++ b/model/jpa/src/test/resources/META-INF/persistence.xml
@@ -17,6 +17,7 @@
         <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
         <class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
+        <class>org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity</class>
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
         <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
old mode 100644
new mode 100755
index f651f3a..b7404fd
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
@@ -5,11 +5,16 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+import com.mongodb.DBObject;
+import com.mongodb.QueryBuilder;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.entities.ClientEntity;
 import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
 import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.mongo.keycloak.entities.MongoClientUserSessionAssociationEntity;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -164,4 +169,25 @@ public class ClientAdapter<T extends MongoIdentifiableEntity> extends AbstractMo
         updateMongoEntity();
     }
 
+    @Override
+    public Set<UserSessionModel> getUserSessions() {
+        DBObject query = new QueryBuilder()
+                .and("clientId").is(getId())
+                .get();
+        List<MongoClientUserSessionAssociationEntity> associations = getMongoStore().loadEntities(MongoClientUserSessionAssociationEntity.class, query, invocationContext);
+
+        Set<UserSessionModel> result = new HashSet<UserSessionModel>();
+        for (MongoClientUserSessionAssociationEntity association : associations) {
+            UserSessionModel session = realm.getUserSession(association.getSessionId());
+            result.add(session);
+        }
+        return result;
+
+    }
+
+    @Override
+    public int getActiveUserSessions() {
+        // todo, something more efficient like COUNT in JPAQL?
+        return getUserSessions().size();
+    }
 }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index eb61b5b..f7e82ec 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -661,6 +661,14 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         return getOAuthClient(clientId);
     }
 
+    @Override
+    public ClientModel findClientById(String id) {
+        ClientModel model = getApplicationById(id);
+        if (model != null) return model;
+        return getOAuthClientById(id);
+    }
+
+
 
     @Override
     public ApplicationModel getApplicationById(String id) {
@@ -1352,6 +1360,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     @Override
     public UserSessionModel createUserSession(UserModel user, String ipAddress) {
         MongoUserSessionEntity entity = new MongoUserSessionEntity();
+        entity.setRealmId(getId());
         entity.setUser(user.getId());
         entity.setIpAddress(ipAddress);
 
@@ -1386,7 +1395,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
 
     @Override
     public void removeUserSession(UserSessionModel session) {
-        getMongoStore().removeEntity(((UserSessionAdapter) session).getEntity(), invocationContext);
+        getMongoStore().removeEntity(((UserSessionAdapter) session).getMongoEntity(), invocationContext);
     }
 
     @Override
@@ -1396,6 +1405,12 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     }
 
     @Override
+    public void removeUserSessions() {
+        DBObject query = new BasicDBObject("realmId", getId());
+        getMongoStore().removeEntities(MongoUserSessionEntity.class, query, invocationContext);
+    }
+
+    @Override
     public void removeExpiredUserSessions() {
         int currentTime = Time.currentTime();
         DBObject query = new QueryBuilder()
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserSessionAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserSessionAdapter.java
index 675e4ba..78dfcb8 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserSessionAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserSessionAdapter.java
@@ -1,26 +1,40 @@
 package org.keycloak.models.mongo.keycloak.adapters;
 
+import com.mongodb.DBObject;
+import com.mongodb.QueryBuilder;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.mongo.keycloak.entities.MongoClientUserSessionAssociationEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoUserSessionEntity;
 
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class UserSessionAdapter implements UserSessionModel {
+public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEntity> implements UserSessionModel {
 
     private MongoUserSessionEntity entity;
     private RealmAdapter realm;
-    private MongoStoreInvocationContext invContext;
 
-    public UserSessionAdapter(MongoUserSessionEntity entity, RealmAdapter realm, MongoStoreInvocationContext invContext) {
+    public UserSessionAdapter(MongoUserSessionEntity entity, RealmAdapter realm, MongoStoreInvocationContext invContext)
+    {
+        super(invContext);
         this.entity = entity;
         this.realm = realm;
-        this.invContext = invContext;
     }
 
-    public MongoUserSessionEntity getEntity() {
+    @Override
+    protected MongoUserSessionEntity getMongoEntity() {
         return entity;
     }
 
@@ -74,4 +88,56 @@ public class UserSessionAdapter implements UserSessionModel {
         entity.setLastSessionRefresh(seconds);
     }
 
+    @Override
+    public void associateClient(ClientModel client) {
+        List<ClientModel> clients = getClientAssociations();
+        for (ClientModel ass : clients) {
+            if (ass.getId().equals(client.getId())) return;
+        }
+
+        MongoClientUserSessionAssociationEntity association = new MongoClientUserSessionAssociationEntity();
+        association.setClientId(client.getId());
+        association.setSessionId(getId());
+
+        getMongoStore().insertEntity(association, invocationContext);
+    }
+
+    @Override
+    public List<ClientModel> getClientAssociations() {
+        DBObject query = new QueryBuilder()
+                .and("sessionId").is(getId())
+                .get();
+        List<MongoClientUserSessionAssociationEntity> associations = getMongoStore().loadEntities(MongoClientUserSessionAssociationEntity.class, query, invocationContext);
+
+        List<ClientModel> result = new ArrayList<ClientModel>();
+        for (MongoClientUserSessionAssociationEntity association : associations) {
+            ClientModel client = realm.findClientById(association.getClientId());
+            result.add(client);
+        }
+        return result;
+    }
+
+    @Override
+    public void removeAssociatedClient(ClientModel client) {
+        DBObject query = new QueryBuilder()
+                .and("sessionId").is(getId())
+                .and("clientId").is(client.getId())
+                .get();
+        getMongoStore().removeEntities(MongoClientUserSessionAssociationEntity.class, query, invocationContext);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        if (!super.equals(o)) return false;
+
+        UserSessionAdapter that = (UserSessionAdapter) o;
+        return getId().equals(that.getId());
+    }
+
+    @Override
+    public int hashCode() {
+        return getId().hashCode();
+    }
 }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoApplicationEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoApplicationEntity.java
index c4a8d1a..bb83e0e 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoApplicationEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoApplicationEntity.java
@@ -22,5 +22,11 @@ public class MongoApplicationEntity extends ApplicationEntity implements MongoId
                 .and("applicationId").is(getId())
                 .get();
         context.getMongoStore().removeEntities(MongoRoleEntity.class, query, context);
+
+        query = new QueryBuilder()
+                .and("clientId").is(getId())
+                .get();
+        context.getMongoStore().removeEntities(MongoClientUserSessionAssociationEntity.class, query, context);
+
     }
 }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoClientUserSessionAssociationEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoClientUserSessionAssociationEntity.java
new file mode 100755
index 0000000..d28e561
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoClientUserSessionAssociationEntity.java
@@ -0,0 +1,37 @@
+package org.keycloak.models.mongo.keycloak.entities;
+
+import org.keycloak.models.entities.AbstractIdentifiableEntity;
+import org.keycloak.models.mongo.api.MongoCollection;
+import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
+import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@MongoCollection(collectionName = "session-client-associations")
+public class MongoClientUserSessionAssociationEntity extends AbstractIdentifiableEntity implements MongoIdentifiableEntity {
+    private String clientId;
+    private String sessionId;
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public void setClientId(String clientId) {
+        this.clientId = clientId;
+    }
+
+    public String getSessionId() {
+        return sessionId;
+    }
+
+    public void setSessionId(String sessionId) {
+        this.sessionId = sessionId;
+    }
+
+    @Override
+    public void afterRemove(MongoStoreInvocationContext invocationContext) {
+    }
+
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOAuthClientEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOAuthClientEntity.java
index f9b6959..a499e51 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOAuthClientEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOAuthClientEntity.java
@@ -1,5 +1,7 @@
 package org.keycloak.models.mongo.keycloak.entities;
 
+import com.mongodb.DBObject;
+import com.mongodb.QueryBuilder;
 import org.keycloak.models.entities.OAuthClientEntity;
 import org.keycloak.models.mongo.api.MongoCollection;
 import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
@@ -15,5 +17,9 @@ public class MongoOAuthClientEntity extends OAuthClientEntity implements MongoId
 
     @Override
     public void afterRemove(MongoStoreInvocationContext invocationContext) {
+        DBObject query = new QueryBuilder()
+                .and("clientId").is(getId())
+                .get();
+        invocationContext.getMongoStore().removeEntities(MongoClientUserSessionAssociationEntity.class, query, invocationContext);
     }
 }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java
index 1bd2f90..d091619 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java
@@ -32,5 +32,8 @@ public class MongoRealmEntity extends RealmEntity implements MongoIdentifiableEn
 
         // Remove all clients of this realm
         context.getMongoStore().removeEntities(MongoOAuthClientEntity.class, query, context);
+
+        // Remove all sessions of this realm
+        context.getMongoStore().removeEntities(MongoUserSessionEntity.class, query, context);
     }
 }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserSessionEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserSessionEntity.java
index 2d98d19..2389840 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserSessionEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserSessionEntity.java
@@ -1,5 +1,7 @@
 package org.keycloak.models.mongo.keycloak.entities;
 
+import com.mongodb.DBObject;
+import com.mongodb.QueryBuilder;
 import org.keycloak.models.entities.AbstractIdentifiableEntity;
 import org.keycloak.models.mongo.api.MongoCollection;
 import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
@@ -11,6 +13,8 @@ import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
 @MongoCollection(collectionName = "sessions")
 public class MongoUserSessionEntity extends AbstractIdentifiableEntity implements MongoIdentifiableEntity {
 
+    private String realmId;
+
     private String user;
 
     private String ipAddress;
@@ -19,6 +23,14 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
 
     private int lastSessionRefresh;
 
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
+
     public String getUser() {
         return user;
     }
@@ -52,7 +64,12 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
     }
 
     @Override
-    public void afterRemove(MongoStoreInvocationContext invocationContext) {
+    public void afterRemove(MongoStoreInvocationContext context) {
+        // Remove all roles, which belongs to this application
+        DBObject query = new QueryBuilder()
+                .and("sessionId").is(getId())
+                .get();
+        context.getMongoStore().removeEntities(MongoClientUserSessionAssociationEntity.class, query, context);
     }
 
 }
diff --git a/project-integrations/aerogear-ups/auth-server/src/main/resources/META-INF/persistence.xml b/project-integrations/aerogear-ups/auth-server/src/main/resources/META-INF/persistence.xml
index 616f8e8..a75390a 100755
--- a/project-integrations/aerogear-ups/auth-server/src/main/resources/META-INF/persistence.xml
+++ b/project-integrations/aerogear-ups/auth-server/src/main/resources/META-INF/persistence.xml
@@ -16,6 +16,7 @@
         <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
         <class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
+        <class>org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity</class>
         <class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
diff --git a/server/src/main/resources/META-INF/persistence.xml b/server/src/main/resources/META-INF/persistence.xml
index 616f8e8..a75390a 100755
--- a/server/src/main/resources/META-INF/persistence.xml
+++ b/server/src/main/resources/META-INF/persistence.xml
@@ -16,6 +16,7 @@
         <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
         <class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
+        <class>org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity</class>
         <class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
diff --git a/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java b/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java
index 91c7686..c2d4ea6 100755
--- a/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java
+++ b/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java
@@ -5,12 +5,14 @@ import org.keycloak.models.AuthenticationProviderModel;
 import org.keycloak.models.ClaimMask;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
+import org.keycloak.models.OAuthClientModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.SocialLinkModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
 import org.keycloak.representations.idm.AuthenticationProviderRepresentation;
 import org.keycloak.representations.idm.ClaimRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
@@ -19,6 +21,7 @@ import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.representations.idm.SocialLinkRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.UserSessionRepresentation;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -179,4 +182,20 @@ public class ModelToRepresentation {
         rep.setSocialUserId(socialLink.getSocialUserId());
         return rep;
     }
+
+    public static UserSessionRepresentation toRepresentation(UserSessionModel session) {
+        UserSessionRepresentation rep = new UserSessionRepresentation();
+        rep.setId(session.getId());
+        rep.setStart(((long)session.getStarted()) * 1000);
+        rep.setUser(session.getUser().getLoginName());
+        rep.setIpAddress(session.getIpAddress());
+        for (ClientModel client : session.getClientAssociations()) {
+            if (client instanceof ApplicationModel) {
+                rep.getApplications().add(client.getClientId());
+            } else if (client instanceof OAuthClientModel) {
+                rep.getClients().put(client.getId(), client.getClientId());
+            }
+        }
+        return rep;
+    }
 }
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 ce06542..2498eda 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
@@ -160,6 +160,22 @@ public class ResourceAdminManager {
             executor.getHttpClient().getConnectionManager().shutdown();
         }
     }
+
+    public void logoutSession(URI requestUri, RealmModel realm, String session) {
+        ApacheHttpClient4Executor executor = createExecutor();
+
+        try {
+            // don't set user notBefore as we don't want a database hit on a user driven logout
+            List<ApplicationModel> resources = realm.getApplications();
+            logger.debugv("logging out {0} resources ", resources.size());
+            for (ApplicationModel resource : resources) {
+                logoutApplication(requestUri, realm, resource, null, session, executor, 0);
+            }
+        } finally {
+            executor.getHttpClient().getConnectionManager().shutdown();
+        }
+    }
+
     public void logoutAll(URI requestUri, RealmModel realm) {
         ApacheHttpClient4Executor executor = createExecutor();
 
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 d1c3706..7927fad 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
@@ -9,10 +9,12 @@ import org.keycloak.models.ModelDuplicateException;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
 import org.keycloak.representations.adapters.action.SessionStats;
 import org.keycloak.representations.adapters.action.UserStats;
 import org.keycloak.representations.idm.ApplicationRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.UserSessionRepresentation;
 import org.keycloak.services.managers.ApplicationManager;
 import org.keycloak.services.managers.ModelToRepresentation;
 import org.keycloak.services.managers.RealmManager;
@@ -36,7 +38,10 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -228,7 +233,32 @@ public class ApplicationResource {
             logger.info("activeSessions: " + stats.getActiveSessions());
         }
         return stats;
-     }
+    }
+
+    @Path("session-count")
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public Map<String, Integer> getApplicationSessionCount() {
+        auth.requireView();
+        Map<String, Integer> map = new HashMap<String, Integer>();
+        map.put("count", application.getActiveUserSessions());
+        return map;
+    }
+
+    @Path("user-sessions")
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<UserSessionRepresentation> getUserSessions() {
+        auth.requireView();
+        List<UserSessionRepresentation> sessions = new ArrayList<UserSessionRepresentation>();
+        for (UserSessionModel session : application.getUserSessions()) {
+            UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(session);
+            sessions.add(rep);
+        }
+        return sessions;
+    }
 
     @Path("logout-all")
     @POST
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 862aeb2..7a089d8 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -11,6 +11,7 @@ import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelDuplicateException;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
 import org.keycloak.provider.ProviderSession;
 import org.keycloak.representations.adapters.action.SessionStats;
 import org.keycloak.representations.idm.RealmAuditRepresentation;
@@ -27,6 +28,7 @@ import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.PUT;
 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.Context;
@@ -156,9 +158,34 @@ public class RealmAdminResource {
     @POST
     public void logoutAll() {
         auth.requireManage();
+        realm.removeUserSessions();
         new ResourceAdminManager().logoutAll(uriInfo.getRequestUri(), realm);
     }
 
+    @Path("sessions/{session}")
+    @DELETE
+    public void deleteSession(@PathParam("session") String sessionId) {
+        UserSessionModel session = realm.getUserSession(sessionId);
+        if (session == null) throw new NotFoundException("Sesssion not found");
+        realm.removeUserSession(session);
+        new ResourceAdminManager().logoutSession(uriInfo.getRequestUri(), realm, session.getId());
+    }
+
+    @Path("application-session-stats")
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public Map<String, Integer> getApplicationSessionStats() {
+        auth.requireView();
+        Map<String, Integer> stats = new HashMap<String, Integer>();
+        for (ApplicationModel applicationModel : realm.getApplications()) {
+            int size = applicationModel.getActiveUserSessions();
+            if (size == 0) continue;
+            stats.put(applicationModel.getName(), size);
+        }
+        return stats;
+    }
+
     @Path("session-stats")
     @GET
     @NoCache
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 1002fc2..feba6f9 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -14,6 +14,7 @@ import org.keycloak.models.RoleModel;
 import org.keycloak.models.SocialLinkModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
 import org.keycloak.representations.adapters.action.UserStats;
 import org.keycloak.representations.idm.ApplicationMappingsRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
@@ -21,6 +22,7 @@ import org.keycloak.representations.idm.MappingsRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.representations.idm.SocialLinkRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.UserSessionRepresentation;
 import org.keycloak.services.email.EmailException;
 import org.keycloak.services.email.EmailSender;
 import org.keycloak.services.managers.AccessCodeEntry;
@@ -181,6 +183,26 @@ public class UsersResource {
         return stats;
     }
 
+    @Path("{username}/sessions")
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<UserSessionRepresentation> getSessions(final @PathParam("username") String username) {
+        logger.info("sessions");
+        auth.requireView();
+        UserModel user = realm.getUser(username);
+        if (user == null) {
+            throw new NotFoundException("User not found");
+        }
+        List<UserSessionModel> sessions = realm.getUserSessions(user);
+        List<UserSessionRepresentation> reps = new ArrayList<UserSessionRepresentation>();
+        for (UserSessionModel session : sessions) {
+            UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(session);
+            reps.add(rep);
+        }
+        return reps;
+    }
+
     @Path("{username}/social-links")
     @GET
     @NoCache
@@ -208,6 +230,7 @@ public class UsersResource {
         if (user == null) {
             throw new NotFoundException("User not found");
         }
+        realm.removeUserSessions(user);
         // set notBefore so that user will be forced to log in.
         user.setNotBefore(Time.currentTime());
         new ResourceAdminManager().logoutUser(uriInfo.getRequestUri(), realm, user.getId(), null);
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index e9c2c80..d1e51ac 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -266,6 +266,7 @@ public class TokenService {
         String scope = form.getFirst(OAuth2Constants.SCOPE);
 
         UserSessionModel session = realm.createUserSession(user, clientConnection.getRemoteAddr());
+        session.associateClient(client);
         audit.session(session);
 
         AccessTokenResponse res = tokenManager.responseBuilder(realm, client, audit)
@@ -648,6 +649,8 @@ public class TokenService {
 
         logger.debug("accessRequest SUCCESS");
 
+        session.associateClient(client);
+
         AccessTokenResponse res = tokenManager.responseBuilder(realm, client, audit)
                 .accessToken(accessCode.getToken())
                 .generateIDToken()
diff --git a/testsuite/integration/src/main/resources/META-INF/persistence.xml b/testsuite/integration/src/main/resources/META-INF/persistence.xml
index a5a7849..6e68551 100755
--- a/testsuite/integration/src/main/resources/META-INF/persistence.xml
+++ b/testsuite/integration/src/main/resources/META-INF/persistence.xml
@@ -16,6 +16,7 @@
         <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
+        <class>org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity</class>
         <class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
         <class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
diff --git a/testsuite/performance/src/test/resources/META-INF/persistence.xml b/testsuite/performance/src/test/resources/META-INF/persistence.xml
index e5ae89a..13695fa 100755
--- a/testsuite/performance/src/test/resources/META-INF/persistence.xml
+++ b/testsuite/performance/src/test/resources/META-INF/persistence.xml
@@ -17,6 +17,7 @@
         <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
         <class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
+        <class>org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity</class>
         <class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>