keycloak-aplcache

Merge pull request #279 from patriot1burke/master refactor

3/10/2014 9:08:06 PM

Changes

integration/undertow/src/main/java/org/keycloak/adapters/undertow/AuthenticatedActionsHandler.java 132(+0 -132)

integration/undertow/src/main/java/org/keycloak/adapters/undertow/PreflightCorsHandler.java 82(+0 -82)

integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletAdminActionsHandler.java 198(+0 -198)

Details

diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java
new file mode 100755
index 0000000..f04f144
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java
@@ -0,0 +1,108 @@
+package org.keycloak.adapters;
+
+import org.jboss.logging.Logger;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.representations.AccessToken;
+
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * Pre-installed actions that must be authenticated
+ *
+ * Actions include:
+ *
+ * CORS Origin Check and Response headers
+ * k_query_bearer_token: Get bearer token from server for Javascripts CORS requests
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class AuthenticatedActionsHandler {
+    private static final Logger log = Logger.getLogger(AuthenticatedActionsHandler.class);
+    protected KeycloakDeployment deployment;
+    protected HttpFacade facade;
+
+    public AuthenticatedActionsHandler(KeycloakDeployment deployment, HttpFacade facade) {
+        this.deployment = deployment;
+        this.facade = facade;
+    }
+
+    public boolean handledRequest() {
+        log.debugv("AuthenticatedActionsValve.invoke {0}", facade.getRequest().getURI());
+        if (corsRequest()) return true;
+        String requestUri = facade.getRequest().getURI();
+        if (requestUri.endsWith(AdapterConstants.K_QUERY_BEARER_TOKEN)) {
+            queryBearerToken();
+            return true;
+        }
+        return false;
+    }
+
+    protected void queryBearerToken()  {
+        log.debugv("queryBearerToken {0}",facade.getRequest().getURI());
+        if (abortTokenResponse()) return;
+        facade.getResponse().setStatus(200);
+        facade.getResponse().setHeader("Content-Type", "text/plain");
+        try {
+            facade.getResponse().getOutputStream().write(facade.getSecurityContext().getTokenString().getBytes());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        facade.getResponse().end();
+    }
+
+    protected boolean abortTokenResponse() {
+        if (facade.getSecurityContext() == null) {
+            log.debugv("Not logged in, sending back 401: {0}",facade.getRequest().getURI());
+            facade.getResponse().setStatus(401);
+            facade.getResponse().end();
+            return true;
+        }
+        if (!deployment.isExposeToken()) {
+            facade.getResponse().setStatus(200);
+            facade.getResponse().end();
+             return true;
+        }
+        // Don't allow a CORS request if we're not validating CORS requests.
+        if (!deployment.isCors() && facade.getRequest().getHeader(CorsHeaders.ORIGIN) != null) {
+            facade.getResponse().setStatus(200);
+            facade.getResponse().end();
+            return true;
+        }
+        return false;
+    }
+
+    protected boolean corsRequest()  {
+        if (!deployment.isCors()) return false;
+        KeycloakSecurityContext securityContext = facade.getSecurityContext();
+        String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN);
+        log.debugv("Origin: {0} uri: {1}", origin, facade.getRequest().getURI());
+        if (securityContext != null && origin != null) {
+            AccessToken token = securityContext.getToken();
+            Set<String> allowedOrigins = token.getAllowedOrigins();
+            if (log.isDebugEnabled()) {
+                for (String a : allowedOrigins) log.debug("   " + a);
+            }
+            if (allowedOrigins == null || (!allowedOrigins.contains("*") && !allowedOrigins.contains(origin))) {
+                if (allowedOrigins == null) {
+                    log.debugv("allowedOrigins was null in token");
+                }
+                if (!allowedOrigins.contains("*") && !allowedOrigins.contains(origin)) {
+                    log.debugv("allowedOrigins did not contain origin");
+
+                }
+                facade.getResponse().setStatus(403);
+                facade.getResponse().end();
+                return true;
+            }
+            log.debugv("returning origin: {0}", origin);
+            facade.getResponse().setStatus(200);
+            facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
+            facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
+        } else {
+            log.debugv("cors validation not needed as we're not a secure session or origin header was null: {0}", facade.getRequest().getURI());
+        }
+        return false;
+    }
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/CorsHeaders.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/CorsHeaders.java
new file mode 100755
index 0000000..21f2b94
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/CorsHeaders.java
@@ -0,0 +1,16 @@
+package org.keycloak.adapters;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface CorsHeaders {
+    String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
+    String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
+    String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
+    String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
+    String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
+    String ORIGIN = "Origin";
+    String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
+    String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/HttpFacade.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/HttpFacade.java
index 25b9a7f..a6eaff2 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/HttpFacade.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/HttpFacade.java
@@ -1,6 +1,10 @@
 package org.keycloak.adapters;
 
+import org.keycloak.KeycloakSecurityContext;
+
 import javax.security.cert.X509Certificate;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.List;
 
 /**
@@ -48,6 +52,8 @@ public interface HttpFacade {
     }
 
     interface Request {
+
+        String getMethod();
         /**
          * Full request URI with query params
          *
@@ -64,7 +70,9 @@ public interface HttpFacade {
 
         String getQueryParamValue(String param);
         Cookie getCookie(String cookieName);
+        String getHeader(String name);
         List<String> getHeaders(String name);
+        InputStream getInputStream();
     }
 
     interface Response {
@@ -73,6 +81,8 @@ public interface HttpFacade {
         void setHeader(String name, String value);
         void resetCookie(String name, String path);
         void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly);
+        OutputStream getOutputStream();
+        void sendError(int code, String message);
 
         /**
          * If the response is finished, end it.
@@ -81,6 +91,7 @@ public interface HttpFacade {
         void end();
     }
 
+    KeycloakSecurityContext getSecurityContext();
     Request getRequest();
     Response getResponse();
     X509Certificate[] getCertificateChain();
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
new file mode 100755
index 0000000..6cf53a6
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
@@ -0,0 +1,227 @@
+package org.keycloak.adapters;
+
+import org.jboss.logging.Logger;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.representations.adapters.action.AdminAction;
+import org.keycloak.representations.adapters.action.LogoutAction;
+import org.keycloak.representations.adapters.action.PushNotBeforeAction;
+import org.keycloak.representations.adapters.action.SessionStats;
+import org.keycloak.representations.adapters.action.SessionStatsAction;
+import org.keycloak.representations.adapters.action.UserStats;
+import org.keycloak.representations.adapters.action.UserStatsAction;
+import org.keycloak.util.JsonSerialization;
+import org.keycloak.util.StreamUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class PreAuthActionsHandler {
+
+    private static final Logger log = Logger.getLogger(PreAuthActionsHandler.class);
+
+    protected UserSessionManagement userSessionManagement;
+    protected KeycloakDeployment deployment;
+    protected HttpFacade facade;
+
+    public PreAuthActionsHandler(UserSessionManagement userSessionManagement, KeycloakDeployment deployment, HttpFacade facade) {
+        this.userSessionManagement = userSessionManagement;
+        this.deployment = deployment;
+        this.facade = facade;
+    }
+
+    public boolean handleRequest() {
+        String requestUri = facade.getRequest().getURI();
+        log.debugv("adminRequest {0}", requestUri);
+        if (preflightCors()) {
+            return true;
+        }
+        if (requestUri.endsWith(AdapterConstants.K_LOGOUT)) {
+            handleLogout();
+            return true;
+        } else if (requestUri.endsWith(AdapterConstants.K_PUSH_NOT_BEFORE)) {
+            handlePushNotBefore();
+            return true;
+        } else if (requestUri.endsWith(AdapterConstants.K_GET_SESSION_STATS)) {
+            handleGetSessionStats();
+            return true;
+        }else if (requestUri.endsWith(AdapterConstants.K_GET_USER_STATS)) {
+            handleGetUserStats();
+            return true;
+        }
+        return false;
+    }
+
+    public boolean preflightCors() {
+        log.debugv("checkCorsPreflight {0}", facade.getRequest().getURI());
+        if (!facade.getRequest().getMethod().equalsIgnoreCase("OPTIONS")) {
+            return false;
+        }
+        if (facade.getRequest().getHeader(CorsHeaders.ORIGIN) == null) {
+            log.debug("checkCorsPreflight: no origin header");
+            return false;
+        }
+        log.debug("Preflight request returning");
+        facade.getResponse().setStatus(200);
+        String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN);
+        facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
+        facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
+        String requestMethods = facade.getRequest().getHeader(CorsHeaders.ACCESS_CONTROL_REQUEST_METHOD);
+        if (requestMethods != null) {
+            if (deployment.getCorsAllowedMethods() != null) {
+                requestMethods = deployment.getCorsAllowedMethods();
+            }
+            facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethods);
+        }
+        String allowHeaders = facade.getRequest().getHeader(CorsHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
+        if (allowHeaders != null) {
+            if (deployment.getCorsAllowedHeaders() != null) {
+                allowHeaders = deployment.getCorsAllowedHeaders();
+            }
+            facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_HEADERS, allowHeaders);
+        }
+        if (deployment.getCorsMaxAge() > -1) {
+            facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_MAX_AGE, Integer.toString(deployment.getCorsMaxAge()));
+        }
+        return true;
+    }
+
+    protected void handleLogout()  {
+        log.info("K_LOGOUT sent");
+        try {
+            JWSInput token = verifyAdminRequest();
+            if (token == null) {
+                return;
+            }
+            LogoutAction action = JsonSerialization.readValue(token.getContent(), LogoutAction.class);
+            if (!validateAction(action)) return;
+            String user = action.getUser();
+            if (user != null) {
+                log.info("logout of session for: " + user);
+                userSessionManagement.logout(user);
+            } else {
+                log.info("logout of all sessions");
+                userSessionManagement.logoutAll();
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+
+    protected void handlePushNotBefore()  {
+        log.info("K_PUSH_NOT_BEFORE sent");
+        try {
+            JWSInput token = verifyAdminRequest();
+            if (token == null) {
+                return;
+            }
+            PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class);
+            if (!validateAction(action)) return;
+            deployment.setNotBefore(action.getNotBefore());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected JWSInput verifyAdminRequest() throws Exception {
+        String token = StreamUtil.readString(facade.getRequest().getInputStream());
+        if (token == null) {
+            log.warn("admin request failed, no token");
+            facade.getResponse().sendError(403, "no token");
+            return null;
+        }
+
+        JWSInput input = new JWSInput(token);
+        boolean verified = false;
+        try {
+            verified = RSAProvider.verify(input, deployment.getRealmKey());
+        } catch (Exception ignore) {
+        }
+        if (!verified) {
+            log.warn("admin request failed, unable to verify token");
+            facade.getResponse().sendError(403, "no token");
+            return null;
+        }
+        return input;
+    }
+
+
+    protected boolean validateAction(AdminAction action)  {
+        if (!action.validate()) {
+            log.warn("admin request failed, not validated" + action.getAction());
+            facade.getResponse().sendError(400, "Not validated");
+            return false;
+        }
+        if (action.isExpired()) {
+            log.warn("admin request failed, expired token");
+            facade.getResponse().sendError(400, "Expired token");
+            return false;
+        }
+        if (!deployment.getResourceName().equals(action.getResource())) {
+            log.warn("Resource name does not match");
+            facade.getResponse().sendError(400, "Resource name does not match");
+            return false;
+
+        }
+        return true;
+    }
+
+    protected void handleGetSessionStats()  {
+        log.info("K_GET_SESSION_STATS sent");
+        try {
+            JWSInput token = verifyAdminRequest();
+            if (token == null) return;
+            SessionStatsAction action = JsonSerialization.readValue(token.getContent(), SessionStatsAction.class);
+            if (!validateAction(action)) return;
+            SessionStats stats = new SessionStats();
+            stats.setActiveSessions(userSessionManagement.getActiveSessions());
+            stats.setActiveUsers(userSessionManagement.getActiveUsers().size());
+            if (action.isListUsers() && userSessionManagement.getActiveSessions() > 0) {
+                Map<String, UserStats> list = new HashMap<String, UserStats>();
+                for (String user : userSessionManagement.getActiveUsers()) {
+                    list.put(user, getUserStats(user));
+                }
+                stats.setUsers(list);
+            }
+            facade.getResponse().setStatus(200);
+            facade.getResponse().setHeader("Content-Type", "application/json");
+            JsonSerialization.writeValueToStream(facade.getResponse().getOutputStream(), stats);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+    protected void handleGetUserStats()  {
+        log.info("K_GET_USER_STATS sent");
+        try {
+            JWSInput token = verifyAdminRequest();
+            if (token == null) return;
+            UserStatsAction action = JsonSerialization.readValue(token.getContent(), UserStatsAction.class);
+            if (!validateAction(action)) return;
+            String user = action.getUser();
+            UserStats stats = getUserStats(user);
+            facade.getResponse().setStatus(200);
+            facade.getResponse().setHeader("Content-Type", "application/json");
+            JsonSerialization.writeValueToStream(facade.getResponse().getOutputStream(), stats);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected UserStats getUserStats(String user) {
+        UserStats stats = new UserStats();
+        Long loginTime = userSessionManagement.getUserLoginTime(user);
+        if (loginTime != null) {
+            stats.setLoggedIn(true);
+            stats.setWhenLoggedIn(loginTime);
+        } else {
+            stats.setLoggedIn(false);
+        }
+        return stats;
+    }
+}
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
new file mode 100755
index 0000000..94da30f
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/UserSessionManagement.java
@@ -0,0 +1,19 @@
+package org.keycloak.adapters;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserSessionManagement {
+    int getActiveSessions();
+
+    Long getUserLoginTime(String username);
+
+    Set<String> getActiveUsers();
+
+    void logoutAll();
+
+    void logout(String user);
+}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/AuthenticatedActionsValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/AuthenticatedActionsValve.java
index 229858c..84f5ab6 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/AuthenticatedActionsValve.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/AuthenticatedActionsValve.java
@@ -9,6 +9,7 @@ import org.apache.catalina.valves.ValveBase;
 import org.jboss.logging.Logger;
 import org.keycloak.KeycloakSecurityContext;
 import org.keycloak.adapters.AdapterConstants;
+import org.keycloak.adapters.AuthenticatedActionsHandler;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.adapters.config.AdapterConfig;
@@ -46,81 +47,10 @@ public class AuthenticatedActionsValve extends ValveBase {
     @Override
     public void invoke(Request request, Response response) throws IOException, ServletException {
         log.debugv("AuthenticatedActionsValve.invoke {0}", request.getRequestURI());
-        KeycloakSecurityContext session = getSkeletonKeySession(request);
-        if (corsRequest(request, response, session)) return;
-        String requestUri = request.getRequestURI();
-        if (requestUri.endsWith(AdapterConstants.K_QUERY_BEARER_TOKEN)) {
-            queryBearerToken(request, response, session);
+        AuthenticatedActionsHandler handler = new AuthenticatedActionsHandler(deployment, new CatalinaHttpFacade(request, response));
+        if (handler.handledRequest()) {
             return;
         }
         getNext().invoke(request, response);
     }
-
-    public KeycloakSecurityContext getSkeletonKeySession(Request request) {
-        KeycloakSecurityContext skSession = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
-        if (skSession != null) return skSession;
-        Session session = request.getSessionInternal();
-        if (session != null) {
-            return (KeycloakSecurityContext) session.getNote(KeycloakSecurityContext.class.getName());
-        }
-        return null;
-    }
-
-    protected void queryBearerToken(Request request, Response response, KeycloakSecurityContext session) throws IOException, ServletException {
-        log.debugv("queryBearerToken {0}", request.getRequestURI());
-        if (abortTokenResponse(request, response, session)) return;
-        response.setStatus(HttpServletResponse.SC_OK);
-        response.setContentType("text/plain");
-        response.getOutputStream().write(session.getTokenString().getBytes());
-        response.getOutputStream().flush();
-
-    }
-
-    protected boolean abortTokenResponse(Request request, Response response, KeycloakSecurityContext session) throws IOException {
-        if (session == null) {
-            log.debugv("session was null, sending back 401: {0}", request.getRequestURI());
-            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
-            return true;
-        }
-        if (!deployment.isExposeToken()) {
-            response.setStatus(HttpServletResponse.SC_OK);
-            return true;
-        }
-        if (!deployment.isCors() && request.getHeader("Origin") != null) {
-            response.setStatus(HttpServletResponse.SC_OK);
-            return true;
-        }
-        return false;
-    }
-
-    protected boolean corsRequest(Request request, Response response, KeycloakSecurityContext session) throws IOException {
-        if (!deployment.isCors()) return false;
-        log.debugv("CORS enabled + request.getRequestURI()");
-        String origin = request.getHeader("Origin");
-        log.debugv("Origin: {0} uri: {1}", origin, request.getRequestURI());
-        if (session != null && origin != null) {
-            AccessToken token = session.getToken();
-            Set<String> allowedOrigins = token.getAllowedOrigins();
-            if (log.isDebugEnabled()) {
-                for (String a : allowedOrigins) log.debug("   " + a);
-            }
-            if (allowedOrigins == null || (!allowedOrigins.contains("*") && !allowedOrigins.contains(origin))) {
-                if (allowedOrigins == null) {
-                    log.debugv("allowedOrigins was null in token");
-                }
-                if (!allowedOrigins.contains("*") && !allowedOrigins.contains(origin)) {
-                    log.debugv("allowedOrigins did not contain origin");
-
-                }
-                response.sendError(HttpServletResponse.SC_FORBIDDEN);
-                return true;
-            }
-            log.debugv("returning origin: {0}", origin);
-            response.setHeader("Access-Control-Allow-Origin", origin);
-            response.setHeader("Access-Control-Allow-Credentials", "true");
-        } else {
-            log.debugv("letting through.  This is an unathenticated session or origin header was null: {0}", request.getRequestURI());
-        }
-        return false;
-    }
 }
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaHttpFacade.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaHttpFacade.java
index 62adcb6..6c47500 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaHttpFacade.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaHttpFacade.java
@@ -2,10 +2,14 @@ package org.keycloak.adapters.as7;
 
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
+import org.keycloak.KeycloakSecurityContext;
 import org.keycloak.adapters.HttpFacade;
 
 import javax.security.cert.X509Certificate;
 import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.List;
@@ -64,6 +68,25 @@ public class CatalinaHttpFacade implements HttpFacade {
             }
             return list;
         }
+
+        @Override
+        public InputStream getInputStream() {
+            try {
+                return request.getInputStream();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public String getMethod() {
+            return request.getMethod();
+        }
+
+        @Override
+        public String getHeader(String name) {
+            return request.getHeader(name);
+        }
     }
 
     protected class ResponseFacade implements Response {
@@ -101,6 +124,24 @@ public class CatalinaHttpFacade implements HttpFacade {
         }
 
         @Override
+        public OutputStream getOutputStream() {
+            try {
+                return response.getOutputStream();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public void sendError(int code, String message) {
+            try {
+                response.sendError(code, message);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
         public void end() {
             ended = true;
         }
@@ -126,6 +167,11 @@ public class CatalinaHttpFacade implements HttpFacade {
     }
 
     @Override
+    public KeycloakSecurityContext getSecurityContext() {
+        return (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
+    }
+
+    @Override
     public X509Certificate[] getCertificateChain() {
         throw new IllegalStateException("Not supported yet");
     }
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 143df34..1188e6d 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
@@ -23,11 +23,11 @@ import java.util.Set;
  */
 public class CatalinaRequestAuthenticator extends RequestAuthenticator {
     protected KeycloakAuthenticatorValve valve;
-    protected UserSessionManagement userSessionManagement;
+    protected CatalinaUserSessionManagement userSessionManagement;
     protected Request request;
 
     public CatalinaRequestAuthenticator(KeycloakDeployment deployment,
-                                        KeycloakAuthenticatorValve valve, UserSessionManagement userSessionManagement,
+                                        KeycloakAuthenticatorValve valve, CatalinaUserSessionManagement userSessionManagement,
                                         CatalinaHttpFacade facade,
                                         Request request) {
         super(facade, deployment, request.getConnector().getRedirectPort());
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
index 2012c66..74050be 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
@@ -5,24 +5,20 @@ import org.apache.catalina.Lifecycle;
 import org.apache.catalina.LifecycleEvent;
 import org.apache.catalina.LifecycleException;
 import org.apache.catalina.LifecycleListener;
-import org.apache.catalina.Session;
-import org.apache.catalina.authenticator.Constants;
 import org.apache.catalina.authenticator.FormAuthenticator;
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
 import org.apache.catalina.core.StandardContext;
 import org.apache.catalina.deploy.LoginConfig;
-import org.apache.catalina.realm.GenericPrincipal;
 import org.jboss.logging.Logger;
 import org.keycloak.KeycloakSecurityContext;
-import org.keycloak.KeycloakPrincipal;
 import org.keycloak.adapters.AdapterConstants;
 import org.keycloak.adapters.AuthChallenge;
 import org.keycloak.adapters.AuthOutcome;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.adapters.PreAuthActionsHandler;
 import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
-import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.adapters.action.AdminAction;
 import org.keycloak.representations.adapters.action.PushNotBeforeAction;
 import org.keycloak.representations.adapters.action.SessionStats;
@@ -35,7 +31,6 @@ import org.keycloak.representations.adapters.action.LogoutAction;
 import org.keycloak.util.JsonSerialization;
 import org.keycloak.util.StreamUtil;
 
-import javax.security.auth.login.LoginException;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
@@ -46,9 +41,7 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * Web deployment whose security is managed by a remote OAuth Skeleton Key authentication server
@@ -61,7 +54,7 @@ import java.util.Set;
  */
 public class KeycloakAuthenticatorValve extends FormAuthenticator implements LifecycleListener {
     private static final Logger log = Logger.getLogger(KeycloakAuthenticatorValve.class);
-    protected UserSessionManagement userSessionManagement = new UserSessionManagement();
+    protected CatalinaUserSessionManagement userSessionManagement = new CatalinaUserSessionManagement();
     protected KeycloakDeployment deployment;
 
 
@@ -115,37 +108,8 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
     @Override
     public void invoke(Request request, Response response) throws IOException, ServletException {
         try {
-            if (deployment.isCors() && new CorsPreflightChecker(deployment).checkCorsPreflight(request, response)) {
-                return;
-            }
-            String requestURI = request.getDecodedRequestURI();
-            if (requestURI.endsWith(AdapterConstants.K_LOGOUT)) {
-                JWSInput input = verifyAdminRequest(request, response);
-                if (input == null) {
-                    return; // we failed to verify the request
-                }
-                remoteLogout(input, response);
-                return;
-            } else if (requestURI.endsWith(AdapterConstants.K_PUSH_NOT_BEFORE)) {
-                JWSInput input = verifyAdminRequest(request, response);
-                if (input == null) {
-                    return; // we failed to verify the request
-                }
-                pushNotBefore(input, response);
-                return;
-            } else if (requestURI.endsWith(AdapterConstants.K_GET_SESSION_STATS)) {
-                JWSInput input = verifyAdminRequest(request, response);
-                if (input == null) {
-                    return; // we failed to verify the request
-                }
-                getSessionStats(input, response);
-                return;
-            } else if (requestURI.endsWith(AdapterConstants.K_GET_USER_STATS)) {
-                JWSInput input = verifyAdminRequest(request, response);
-                if (input == null) {
-                    return; // we failed to verify the request
-                }
-                getUserStats(input, response);
+            PreAuthActionsHandler handler = new PreAuthActionsHandler(userSessionManagement, deployment, new CatalinaHttpFacade(request, response));
+            if (handler.handleRequest()) {
                 return;
             }
             checkKeycloakSession(request);
@@ -172,131 +136,6 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
         return false;
      }
 
-    protected JWSInput verifyAdminRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
-        String token = StreamUtil.readString(request.getInputStream());
-        if (token == null) {
-            log.warn("admin request failed, no token");
-            response.sendError(403, "no token");
-            return null;
-        }
-
-        JWSInput input = new JWSInput(token);
-        boolean verified = false;
-        try {
-            verified = RSAProvider.verify(input, deployment.getRealmKey());
-        } catch (Exception ignore) {
-        }
-        if (!verified) {
-            log.warn("admin request failed, unable to verify token");
-            response.sendError(403, "verification failed");
-            return null;
-        }
-        return input;
-    }
-
-
-    protected boolean validateAction(HttpServletResponse response, AdminAction action) throws IOException {
-        if (!action.validate()) {
-            log.warn("admin request failed, not validated" + action.getAction());
-            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Not validated");
-            return false;
-        }
-        if (action.isExpired()) {
-            log.warn("admin request failed, expired token");
-            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Expired token");
-            return false;
-        }
-        if (!deployment.getResourceName().equals(action.getResource())) {
-            log.warn("Resource name does not match");
-            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Resource name does not match");
-            return false;
-
-        }
-        return true;
-    }
-
-    protected void pushNotBefore(JWSInput token, HttpServletResponse response) throws IOException {
-        log.info("->> pushNotBefore: ");
-        PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class);
-        if (!validateAction(response, action)) {
-            return;
-        }
-        deployment.setNotBefore(action.getNotBefore());
-        response.setStatus(HttpServletResponse.SC_NO_CONTENT);
-
-    }
-
-    protected UserStats getUserStats(String user) {
-        UserStats stats = new UserStats();
-        Long loginTime = userSessionManagement.getUserLoginTime(user);
-        if (loginTime != null) {
-            stats.setLoggedIn(true);
-            stats.setWhenLoggedIn(loginTime);
-        } else {
-            stats.setLoggedIn(false);
-        }
-        return stats;
-    }
-
-
-    protected void getSessionStats(JWSInput token, HttpServletResponse response) throws IOException {
-        log.info("->> getSessionStats: ");
-        SessionStatsAction action = JsonSerialization.readValue(token.getContent(), SessionStatsAction.class);
-        if (!validateAction(response, action)) {
-            return;
-        }
-        SessionStats stats = new SessionStats();
-        stats.setActiveSessions(userSessionManagement.getActiveSessions());
-        stats.setActiveUsers(userSessionManagement.getActiveUsers().size());
-        if (action.isListUsers() && userSessionManagement.getActiveSessions() > 0) {
-            Map<String, UserStats> list = new HashMap<String, UserStats>();
-            for (String user : userSessionManagement.getActiveUsers()) {
-                list.put(user, getUserStats(user));
-            }
-            stats.setUsers(list);
-        }
-        response.setStatus(200);
-        response.setContentType("application/json");
-        JsonSerialization.writeValueToStream(response.getOutputStream(), stats);
-
-    }
-
-    protected void getUserStats(JWSInput token, HttpServletResponse response) throws IOException {
-        log.info("->> getUserStats: ");
-        UserStatsAction action = JsonSerialization.readValue(token.getContent(), UserStatsAction.class);
-        if (!validateAction(response, action)) {
-            return;
-        }
-        String user = action.getUser();
-        UserStats stats = getUserStats(user);
-        response.setStatus(200);
-        response.setContentType("application/json");
-        JsonSerialization.writeValueToStream(response.getOutputStream(), stats);
-    }
-
-
-    protected void remoteLogout(JWSInput token, HttpServletResponse response) throws IOException {
-        try {
-            log.debug("->> remoteLogout: ");
-            LogoutAction action = JsonSerialization.readValue(token.getContent(), LogoutAction.class);
-            if (!validateAction(response, action)) {
-                return;
-            }
-            String user = action.getUser();
-            if (user != null) {
-                log.debug("logout of session for: " + user);
-                userSessionManagement.logout(user);
-            } else {
-                log.debug("logout of all sessions");
-                userSessionManagement.logoutAll();
-            }
-        } catch (Exception e) {
-            log.warn("failed to logout", e);
-            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Failed to logout");
-        }
-        response.setStatus(HttpServletResponse.SC_NO_CONTENT);
-    }
-
     /**
      * Checks that access token is still valid.  Will attempt refresh of token if it is not.
      *
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
index 9b0a911..50895c6 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
@@ -14,6 +14,7 @@ import io.undertow.servlet.api.ServletSessionConfig;
 import java.io.ByteArrayInputStream;
 import org.jboss.logging.Logger;
 import org.keycloak.adapters.AdapterConstants;
+import org.keycloak.adapters.AuthenticatedActionsHandler;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
 
@@ -63,16 +64,14 @@ public class KeycloakServletExtension implements ServletExtension {
         }
         if (is == null) throw new RuntimeException("Unable to find realm config in /WEB-INF/keycloak.json or in keycloak subsystem.");
         KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(is);
-        PreflightCorsHandler.Wrapper preflight = new PreflightCorsHandler.Wrapper(deployment);
-        UserSessionManagement userSessionManagement = new UserSessionManagement(deployment);
+        UndertowUserSessionManagement userSessionManagement = new UndertowUserSessionManagement(deployment);
         final ServletKeycloakAuthMech mech = new ServletKeycloakAuthMech(deployment, userSessionManagement, deploymentInfo.getConfidentialPortManager());
 
-        AuthenticatedActionsHandler.Wrapper actions = new AuthenticatedActionsHandler.Wrapper(deployment);
+        UndertowAuthenticatedActionsHandler.Wrapper actions = new UndertowAuthenticatedActionsHandler.Wrapper(deployment);
 
         // setup handlers
 
-        deploymentInfo.addInitialHandlerChainWrapper(preflight); // cors preflight
-        deploymentInfo.addOuterHandlerChainWrapper(new ServletAdminActionsHandler.Wrapper(deployment, userSessionManagement));
+        deploymentInfo.addOuterHandlerChainWrapper(new ServletPreAuthActionsHandler.Wrapper(deployment, userSessionManagement));
         deploymentInfo.addAuthenticationMechanism("KEYCLOAK", new AuthenticationMechanismFactory() {
             @Override
             public AuthenticationMechanism create(String s, FormParserFactory formParserFactory, Map<String, String> stringStringMap) {
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java
index f66cd01..310958a 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java
@@ -59,7 +59,7 @@ public class KeycloakUndertowAccount implements Account, Serializable {
         return session.getTokenString();
     }
 
-    public RefreshableKeycloakSecurityContext getSession() {
+    public RefreshableKeycloakSecurityContext getKeycloakSecurityContext() {
         return session;
     }
 
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
index 8160592..2ce8f06 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
@@ -17,10 +17,10 @@ public class ServletKeycloakAuthMech implements AuthenticationMechanism {
     public static final AttachmentKey<AuthChallenge> KEYCLOAK_CHALLENGE_ATTACHMENT_KEY = AttachmentKey.create(AuthChallenge.class);
 
     protected KeycloakDeployment deployment;
-    protected UserSessionManagement userSessionManagement;
+    protected UndertowUserSessionManagement userSessionManagement;
     protected ConfidentialPortManager portManager;
 
-    public ServletKeycloakAuthMech(KeycloakDeployment deployment, UserSessionManagement userSessionManagement, ConfidentialPortManager portManager) {
+    public ServletKeycloakAuthMech(KeycloakDeployment deployment, UndertowUserSessionManagement userSessionManagement, ConfidentialPortManager portManager) {
         this.deployment = deployment;
         this.userSessionManagement = userSessionManagement;
         this.portManager = portManager;
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java
new file mode 100755
index 0000000..30aa6c1
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java
@@ -0,0 +1,54 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HandlerWrapper;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.PreAuthActionsHandler;
+import org.keycloak.adapters.KeycloakDeployment;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletPreAuthActionsHandler implements HttpHandler {
+
+    private static final Logger log = Logger.getLogger(ServletPreAuthActionsHandler.class);
+    protected HttpHandler next;
+    protected UndertowUserSessionManagement userSessionManagement;
+    protected KeycloakDeployment deployment;
+
+    public static class Wrapper implements HandlerWrapper {
+        protected KeycloakDeployment deployment;
+        protected UndertowUserSessionManagement userSessionManagement;
+
+
+        public Wrapper(KeycloakDeployment deployment, UndertowUserSessionManagement userSessionManagement) {
+            this.deployment = deployment;
+            this.userSessionManagement = userSessionManagement;
+        }
+
+        @Override
+        public HttpHandler wrap(HttpHandler handler) {
+            return new ServletPreAuthActionsHandler(deployment, userSessionManagement, handler);
+        }
+    }
+
+    protected ServletPreAuthActionsHandler(KeycloakDeployment deployment,
+                                           UndertowUserSessionManagement userSessionManagement,
+                                           HttpHandler next) {
+        this.next = next;
+        this.deployment = deployment;
+        this.userSessionManagement = userSessionManagement;
+    }
+
+    @Override
+    public void handleRequest(HttpServerExchange exchange) throws Exception {
+        final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+        SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, servletRequestContext.getDeployment().getSessionManager());
+        PreAuthActionsHandler handler = new PreAuthActionsHandler(bridge, deployment, new UndertowHttpFacade(exchange));
+        if (handler.handleRequest()) return;
+        next.handleRequest(exchange);
+    }
+}
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 d0111bf..f294eb7 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
@@ -16,11 +16,11 @@ import javax.servlet.http.HttpSession;
  */
 public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
 
-    protected UserSessionManagement userSessionManagement;
+    protected UndertowUserSessionManagement userSessionManagement;
 
     public ServletRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort,
                                        SecurityContext securityContext, HttpServerExchange exchange,
-                                       UserSessionManagement userSessionManagement) {
+                                       UndertowUserSessionManagement userSessionManagement) {
         super(facade, deployment, sslRedirectPort, securityContext, exchange);
         this.userSessionManagement = userSessionManagement;
     }
@@ -52,16 +52,16 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
 
     @Override
     protected void propagateKeycloakContext(KeycloakUndertowAccount account) {
+        super.propagateKeycloakContext(account);
         final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
         HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
-        req.setAttribute(KeycloakSecurityContext.class.getName(), account.getSession());
+        req.setAttribute(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
     }
 
     @Override
     protected void login(KeycloakUndertowAccount account) {
         final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
         HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
-        req.setAttribute(KeycloakSecurityContext.class.getName(), account.getSession());
         HttpSession session = req.getSession(true);
         session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
         userSessionManagement.login(servletRequestContext.getDeployment().getSessionManager(), session, account.getPrincipal().getName());
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
new file mode 100755
index 0000000..4a5a4c2
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java
@@ -0,0 +1,47 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.session.SessionManager;
+import org.keycloak.adapters.UserSessionManagement;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SessionManagementBridge implements UserSessionManagement {
+
+    protected UndertowUserSessionManagement userSessionManagement;
+    protected SessionManager sessionManager;
+
+    public SessionManagementBridge(UndertowUserSessionManagement userSessionManagement, SessionManager sessionManager) {
+        this.userSessionManagement = userSessionManagement;
+        this.sessionManager = sessionManager;
+    }
+
+    @Override
+    public int getActiveSessions() {
+        return userSessionManagement.getActiveSessions();
+    }
+
+    @Override
+    public Long getUserLoginTime(String username) {
+        return userSessionManagement.getUserLoginTime(username);
+    }
+
+    @Override
+    public Set<String> getActiveUsers() {
+        return userSessionManagement.getActiveUsers();
+    }
+
+    @Override
+    public void logoutAll() {
+        userSessionManagement.logoutAll(sessionManager);
+    }
+
+    @Override
+    public void logout(String user) {
+        userSessionManagement.logout(sessionManager, user);
+    }
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticatedActionsHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticatedActionsHandler.java
new file mode 100755
index 0000000..8d641c0
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticatedActionsHandler.java
@@ -0,0 +1,46 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HandlerWrapper;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.AuthenticatedActionsHandler;
+import org.keycloak.adapters.KeycloakDeployment;
+
+/**
+ * Bridge for authenticated Keycloak adapter actions
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UndertowAuthenticatedActionsHandler implements HttpHandler {
+    private static final Logger log = Logger.getLogger(UndertowAuthenticatedActionsHandler.class);
+    protected KeycloakDeployment deployment;
+    protected HttpHandler next;
+
+    public static class Wrapper implements HandlerWrapper {
+        protected KeycloakDeployment deployment;
+
+        public Wrapper(KeycloakDeployment deployment) {
+            this.deployment = deployment;
+        }
+
+        @Override
+        public HttpHandler wrap(HttpHandler handler) {
+            return new UndertowAuthenticatedActionsHandler(deployment, handler);
+        }
+    }
+
+
+    protected UndertowAuthenticatedActionsHandler(KeycloakDeployment deployment, HttpHandler next) {
+        this.deployment = deployment;
+        this.next = next;
+    }
+
+    @Override
+    public void handleRequest(HttpServerExchange exchange) throws Exception {
+        AuthenticatedActionsHandler handler = new AuthenticatedActionsHandler(deployment, new UndertowHttpFacade(exchange));
+        if (handler.handledRequest()) return;
+        next.handleRequest(exchange);
+    }
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
index d08864d..f610443 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
@@ -3,11 +3,18 @@ package org.keycloak.adapters.undertow;
 import io.undertow.server.HttpServerExchange;
 import io.undertow.server.handlers.Cookie;
 import io.undertow.server.handlers.CookieImpl;
+import io.undertow.util.AttachmentKey;
+import io.undertow.util.Headers;
 import io.undertow.util.HttpString;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AuthChallenge;
 import org.keycloak.adapters.HttpFacade;
 import org.keycloak.util.KeycloakUriBuilder;
 
 import javax.security.cert.X509Certificate;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.Deque;
 import java.util.List;
 import java.util.Map;
@@ -17,6 +24,8 @@ import java.util.Map;
  * @version $Revision: 1 $
  */
 public class UndertowHttpFacade implements HttpFacade {
+    public static final AttachmentKey<KeycloakSecurityContext> KEYCLOAK_SECURITY_CONTEXT_KEY = AttachmentKey.create(KeycloakSecurityContext.class);
+
     protected HttpServerExchange exchange;
     protected RequestFacade requestFacade = new RequestFacade();
     protected ResponseFacade responseFacade = new ResponseFacade();
@@ -57,6 +66,23 @@ public class UndertowHttpFacade implements HttpFacade {
         public List<String> getHeaders(String name) {
             return exchange.getRequestHeaders().get(name);
         }
+
+        @Override
+        public String getMethod() {
+            return exchange.getRequestMethod().toString();
+        }
+
+
+
+        @Override
+        public String getHeader(String name) {
+            return exchange.getRequestHeaders().getFirst(name);
+        }
+
+        @Override
+        public InputStream getInputStream() {
+            return exchange.getInputStream();
+        }
     }
 
     protected class ResponseFacade implements Response {
@@ -95,6 +121,24 @@ public class UndertowHttpFacade implements HttpFacade {
         }
 
         @Override
+        public OutputStream getOutputStream() {
+            return exchange.getOutputStream();
+        }
+
+        @Override
+        public void sendError(int code, String message) {
+            exchange.setResponseCode(code);
+            exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/html");
+            try {
+                exchange.getOutputStream().write(message.getBytes());
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            exchange.endExchange();
+        }
+
+
+        @Override
         public void end() {
             exchange.endExchange();
         }
@@ -115,6 +159,11 @@ public class UndertowHttpFacade implements HttpFacade {
     }
 
     @Override
+    public KeycloakSecurityContext getSecurityContext() {
+        return exchange.getAttachment(KEYCLOAK_SECURITY_CONTEXT_KEY);
+    }
+
+    @Override
     public X509Certificate[] getCertificateChain() {
         X509Certificate[] chain = new X509Certificate[0];
         try {
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
index 14b4f72..89b2e11 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
@@ -25,6 +25,7 @@ public class UndertowRequestAuthenticator extends RequestAuthenticator {
     }
 
     protected void propagateKeycloakContext(KeycloakUndertowAccount account) {
+        exchange.putAttachment(UndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY, account.getKeycloakSecurityContext());
     }
 
     @Override
@@ -41,6 +42,7 @@ public class UndertowRequestAuthenticator extends RequestAuthenticator {
     protected void completeOAuthAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session) {
         KeycloakUndertowAccount account = new KeycloakUndertowAccount(principal, session, deployment);
         securityContext.authenticationComplete(account, "KEYCLOAK", false);
+        propagateKeycloakContext(account);
         login(account);
     }