keycloak-aplcache

Changes

Details

diff --git a/docbook/auth-server-docs/reference/en/en-US/master.xml b/docbook/auth-server-docs/reference/en/en-US/master.xml
index 16a8601..60fa1eb 100755
--- a/docbook/auth-server-docs/reference/en/en-US/master.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/master.xml
@@ -20,6 +20,7 @@
                 <!ENTITY SpringSecurityAdapter SYSTEM "modules/spring-security-adapter.xml">
                 <!ENTITY InstalledApplications SYSTEM "modules/installed-applications.xml">
                 <!ENTITY Logout SYSTEM "modules/logout.xml">
+                <!ENTITY ErrorHandling SYSTEM "modules/adapter_error_handling.xml">
                 <!ENTITY SAML SYSTEM "modules/saml.xml">
                 <!ENTITY JAAS SYSTEM "modules/jaas.xml">
                 <!ENTITY IdentityBroker SYSTEM "modules/identity-broker.xml">
@@ -115,6 +116,7 @@ This one is short
         &SpringSecurityAdapter;
         &InstalledApplications;
         &Logout;
+        &ErrorHandling;
         &MultiTenancy;
         &JAAS;
     </chapter>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/adapter_error_handling.xml b/docbook/auth-server-docs/reference/en/en-US/modules/adapter_error_handling.xml
new file mode 100755
index 0000000..38c0bb1
--- /dev/null
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/adapter_error_handling.xml
@@ -0,0 +1,58 @@
+<section id="adapter_error_handling">
+    <title>Error Handling</title>
+    <para>
+        Keycloak has some error handling facilities for servlet based client adapters.  When an error is encountered in
+        authentication, keycloak will call <literal>HttpServletResponse.sendError()</literal>.  You can set up an error-page
+        within your <literal>web.xml</literal> file to handle the error however you want.  Keycloak may throw
+        400, 401, 403, and 500 errors.
+    </para>
+    <para>
+<programlisting>
+<![CDATA[
+<error-page>
+    <error-code>404</error-code>
+    <location>/ErrorHandler</location>
+</error-page>]]>
+</programlisting>
+    </para>
+    <para>
+        Keycloak also sets an <literal>HttpServletRequest</literal> attribute that you can retrieve.  The attribute name
+        is <literal>org.keycloak.adapters.spi.AuthenticationError</literal>.  Typecast this object to:
+        <literal>org.keycloak.adapters.OIDCAuthenticationError</literal>.  This class can tell you exactly what happened.
+        If this attribute is not set, then the adapter was not responsible for the error code.
+    </para>
+    <para>
+<programlisting>
+public class OIDCAuthenticationError implements AuthenticationError {
+    public static enum Reason {
+        NO_BEARER_TOKEN,
+        NO_REDIRECT_URI,
+        INVALID_STATE_COOKIE,
+        OAUTH_ERROR,
+        SSL_REQUIRED,
+        CODE_TO_TOKEN_FAILURE,
+        INVALID_TOKEN,
+        STALE_TOKEN,
+        NO_AUTHORIZATION_HEADER
+    }
+
+    private Reason reason;
+    private String description;
+
+    public OIDCAuthenticationError(Reason reason, String description) {
+        this.reason = reason;
+        this.description = description;
+    }
+
+    public Reason getReason() {
+        return reason;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}
+
+</programlisting>
+    </para>
+</section>
\ No newline at end of file
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml
index b0a443d..571cd6c 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml
@@ -84,6 +84,10 @@
             <simplesect>
                 <title>Option 'Update Profile On First Login' moved from Identity provider to Review Profile authenticator</title>
                 <para>
+                    form-error-page in web.xml will no longer work for client adapter authentication errors.  You must define an error-page for
+                    the the various HTTP error codes.  See documentation for more details if you want to catch and handle adapter error conditions.
+                </para>
+                <para>
                     In this version, we added <literal>First Broker Login</literal>, which allows you to specify what exactly should be done
                     when new user is logged through Identity provider (or Social provider), but there is no existing Keycloak user
                     yet linked to the social account. As part of this work, we added option <literal>First Login Flow</literal> to identity providers where
diff --git a/docbook/saml-adapter-docs/reference/en/en-US/master.xml b/docbook/saml-adapter-docs/reference/en/en-US/master.xml
index 5b798a1..6e88ed6 100755
--- a/docbook/saml-adapter-docs/reference/en/en-US/master.xml
+++ b/docbook/saml-adapter-docs/reference/en/en-US/master.xml
@@ -9,6 +9,7 @@
                 <!ENTITY Jetty8Adapter SYSTEM "modules/jetty8-adapter.xml">
                 <!ENTITY FilterAdapter SYSTEM "modules/servlet-filter-adapter.xml">
                 <!ENTITY Logout SYSTEM "modules/logout.xml">
+                <!ENTITY ErrorHandling SYSTEM "modules/adapter_error_handling.xml">
                 ]>
 
 <book>
@@ -49,6 +50,7 @@ This one is short
     &Jetty8Adapter;
     &FilterAdapter;
     &Logout;
+    &ErrorHandling;
 
 
 
diff --git a/docbook/saml-adapter-docs/reference/en/en-US/modules/adapter_error_handling.xml b/docbook/saml-adapter-docs/reference/en/en-US/modules/adapter_error_handling.xml
new file mode 100755
index 0000000..1d6d11f
--- /dev/null
+++ b/docbook/saml-adapter-docs/reference/en/en-US/modules/adapter_error_handling.xml
@@ -0,0 +1,42 @@
+<chapter id="adapter_error_handling">
+    <title>Error Handling</title>
+    <para>
+        Keycloak has some error handling facilities for servlet based client adapters.  When an error is encountered in
+        authentication, keycloak will call <literal>HttpServletResponse.sendError()</literal>.  You can set up an error-page
+        within your <literal>web.xml</literal> file to handle the error however you want.  Keycloak may throw
+        400, 401, 403, and 500 errors.
+    </para>
+    <para>
+<programlisting>
+<![CDATA[
+<error-page>
+    <error-code>404</error-code>
+    <location>/ErrorHandler</location>
+</error-page>]]>
+</programlisting>
+    </para>
+    <para>
+        Keycloak also sets an <literal>HttpServletRequest</literal> attribute that you can retrieve.  The attribute name
+        is <literal>org.keycloak.adapters.spi.AuthenticationError</literal>.  Typecast this object to:
+        <literal>org.keycloak.adapters.saml.SamlAuthenticationError</literal>.  This class can tell you exactly what happened.
+        If this attribute is not set, then the adapter was not responsible for the error code.
+    </para>
+    <para>
+<programlisting>
+public class SamlAuthenticationError implements AuthenticationError {
+    public static enum Reason {
+        EXTRACTION_FAILURE,
+        INVALID_SIGNATURE,
+        ERROR_STATUS
+    }
+
+    public Reason getReason() {
+        return reason;
+    }
+    public StatusResponseType getStatus() {
+        return status;
+    }
+}
+</programlisting>
+    </para>
+</chapter>
\ No newline at end of file
diff --git a/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java b/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java
old mode 100644
new mode 100755
index 8290b3b..1143968
--- a/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java
+++ b/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java
@@ -17,10 +17,12 @@ import org.apache.http.client.methods.HttpGet;
 import org.apache.http.impl.client.DefaultHttpClient;
 import org.keycloak.KeycloakSecurityContext;
 import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.spi.AuthenticationError;
 import org.keycloak.adapters.spi.HttpFacade;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
 import org.keycloak.adapters.ServerRequest;
+import org.keycloak.adapters.spi.LogoutError;
 import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.representations.RefreshToken;
 import org.keycloak.util.JsonSerialization;
@@ -203,6 +205,18 @@ public class OfflineAccessPortalServlet extends HttpServlet {
                     public String getRemoteAddr() {
                         return servletRequest.getRemoteAddr();
                     }
+
+                    @Override
+                    public void setError(AuthenticationError error) {
+                        servletRequest.setAttribute(AuthenticationError.class.getName(), error);
+
+                    }
+
+                    @Override
+                    public void setError(LogoutError error) {
+                        servletRequest.setAttribute(LogoutError.class.getName(), error);
+                    }
+
                 };
             }
 
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java
old mode 100644
new mode 100755
index 6b90105..7f260ab
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java
@@ -33,7 +33,7 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
     public AuthOutcome authenticate(HttpFacade exchange)  {
         List<String> authHeaders = exchange.getRequest().getHeaders("Authorization");
         if (authHeaders == null || authHeaders.size() == 0) {
-            challenge = challengeResponse(exchange, null, null);
+            challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_AUTHORIZATION_HEADER, null, null);
             return AuthOutcome.NOT_ATTEMPTED;
         }
 
@@ -46,7 +46,7 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
         }
 
         if (tokenString == null) {
-            challenge = challengeResponse(exchange, null, null);
+            challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, null, null);
             return AuthOutcome.NOT_ATTEMPTED;
         }
 
@@ -59,7 +59,7 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
             tokenString = atr.getToken();
         } catch (Exception e) {
             log.debug("Failed to obtain token", e);
-            challenge = challengeResponse(exchange, "no_token", e.getMessage());
+            challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "no_token", e.getMessage());
             return AuthOutcome.FAILED;
         }
 
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java
index 92b03da..686b802 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java
@@ -45,7 +45,7 @@ public class BearerTokenRequestAuthenticator {
     public AuthOutcome authenticate(HttpFacade exchange)  {
         List<String> authHeaders = exchange.getRequest().getHeaders("Authorization");
         if (authHeaders == null || authHeaders.size() == 0) {
-            challenge = challengeResponse(exchange, null, null);
+            challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_BEARER_TOKEN, null, null);
             return AuthOutcome.NOT_ATTEMPTED;
         }
 
@@ -58,7 +58,7 @@ public class BearerTokenRequestAuthenticator {
         }
 
         if (tokenString == null) {
-            challenge = challengeResponse(exchange, null, null);
+            challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_BEARER_TOKEN, null, null);
             return AuthOutcome.NOT_ATTEMPTED;
         }
 
@@ -70,12 +70,12 @@ public class BearerTokenRequestAuthenticator {
             token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
         } catch (VerificationException e) {
             log.error("Failed to verify token", e);
-            challenge = challengeResponse(exchange, "invalid_token", e.getMessage());
+            challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", e.getMessage());
             return AuthOutcome.FAILED;
         }
         if (token.getIssuedAt() < deployment.getNotBefore()) {
             log.error("Stale token");
-            challenge = challengeResponse(exchange, "invalid_token", "Stale token");
+            challenge = challengeResponse(exchange,  OIDCAuthenticationError.Reason.STALE_TOKEN, "invalid_token", "Stale token");
             return AuthOutcome.FAILED;
         }
         boolean verifyCaller = false;
@@ -113,11 +113,6 @@ public class BearerTokenRequestAuthenticator {
     protected AuthChallenge clientCertChallenge() {
         return new AuthChallenge() {
             @Override
-            public boolean errorPage() {
-                return false;
-            }
-
-            @Override
             public int getResponseCode() {
                 return 0;
             }
@@ -131,7 +126,7 @@ public class BearerTokenRequestAuthenticator {
     }
 
 
-    protected AuthChallenge challengeResponse(HttpFacade facade, String error, String description) {
+    protected AuthChallenge challengeResponse(HttpFacade facade, final OIDCAuthenticationError.Reason reason, final String error, final String description) {
         StringBuilder header = new StringBuilder("Bearer realm=\"");
         header.append(deployment.getRealm()).append("\"");
         if (error != null) {
@@ -143,19 +138,16 @@ public class BearerTokenRequestAuthenticator {
         final String challenge = header.toString();
         return new AuthChallenge() {
             @Override
-            public boolean errorPage() {
-                return true;
-            }
-
-            @Override
             public int getResponseCode() {
                 return 401;
             }
 
             @Override
             public boolean challenge(HttpFacade facade) {
-                facade.getResponse().setStatus(401);
+                OIDCAuthenticationError error = new OIDCAuthenticationError(reason, description);
+                facade.getRequest().setError(error);
                 facade.getResponse().addHeader("WWW-Authenticate", challenge);
+                facade.getResponse().sendError(401);
                 return true;
             }
         };
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
index d341003..f41f80f 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
@@ -174,16 +174,11 @@ public class OAuthRequestAuthenticator {
         final String state = getStateCode();
         final String redirect = getRedirectUri(state);
         if (redirect == null) {
-            return challenge(403);
+            return challenge(403, OIDCAuthenticationError.Reason.NO_REDIRECT_URI, null);
         }
         return new AuthChallenge() {
 
             @Override
-            public boolean errorPage() {
-                return false;
-            }
-
-            @Override
             public int getResponseCode() {
                 return 0;
             }
@@ -205,7 +200,7 @@ public class OAuthRequestAuthenticator {
 
         if (stateCookie == null) {
             log.warn("No state cookie");
-            return challenge(400);
+            return challenge(400, OIDCAuthenticationError.Reason.INVALID_STATE_COOKIE, null);
         }
         // reset the cookie
         log.debug("** reseting application state cookie");
@@ -215,13 +210,13 @@ public class OAuthRequestAuthenticator {
         String state = getQueryParamValue(OAuth2Constants.STATE);
         if (state == null) {
             log.warn("state parameter was null");
-            return challenge(400);
+            return challenge(400, OIDCAuthenticationError.Reason.INVALID_STATE_COOKIE, null);
         }
         if (!state.equals(stateCookieValue)) {
             log.warn("state parameter invalid");
             log.warn("cookie: " + stateCookieValue);
             log.warn("queryParam: " + state);
-            return challenge(400);
+            return challenge(400, OIDCAuthenticationError.Reason.INVALID_STATE_COOKIE, null);
         }
         return null;
 
@@ -235,7 +230,7 @@ public class OAuthRequestAuthenticator {
             if (error != null) {
                 // todo how do we send a response?
                 log.warn("There was an error: " + error);
-                challenge = challenge(400);
+                challenge = challenge(400, OIDCAuthenticationError.Reason.OAUTH_ERROR, error);
                 return AuthOutcome.FAILED;
             } else {
                 log.debug("redirecting to auth server");
@@ -253,20 +248,17 @@ public class OAuthRequestAuthenticator {
 
     }
 
-    protected AuthChallenge challenge(final int code) {
+    protected AuthChallenge challenge(final int code, final OIDCAuthenticationError.Reason reason, final String description) {
         return new AuthChallenge() {
             @Override
-            public boolean errorPage() {
-                return true;
-            }
-
-            @Override
             public int getResponseCode() {
                 return code;
             }
 
             @Override
             public boolean challenge(HttpFacade exchange) {
+                OIDCAuthenticationError error = new OIDCAuthenticationError(reason, description);
+                exchange.getRequest().setError(error);
                 exchange.getResponse().sendError(code);
                 return true;
             }
@@ -289,7 +281,7 @@ public class OAuthRequestAuthenticator {
         // abort if not HTTPS
         if (!isRequestSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
             log.error("Adapter requires SSL. Request: " + facade.getRequest().getURI());
-            return challenge(403);
+            return challenge(403, OIDCAuthenticationError.Reason.SSL_REQUIRED, null);
         }
 
         log.debug("checking state cookie for after code");
@@ -308,11 +300,11 @@ public class OAuthRequestAuthenticator {
             if (failure.getStatus() == 400 && failure.getError() != null) {
                 log.error("   " + failure.getError());
             }
-            return challenge(403);
+            return challenge(403, OIDCAuthenticationError.Reason.CODE_TO_TOKEN_FAILURE, null);
 
         } catch (IOException e) {
             log.error("failed to turn code into token", e);
-            return challenge(403);
+            return challenge(403, OIDCAuthenticationError.Reason.CODE_TO_TOKEN_FAILURE, null);
         }
 
         tokenString = tokenResponse.getToken();
@@ -331,14 +323,14 @@ public class OAuthRequestAuthenticator {
             log.debug("Token Verification succeeded!");
         } catch (VerificationException e) {
             log.error("failed verification of token: " + e.getMessage());
-            return challenge(403);
+            return challenge(403, OIDCAuthenticationError.Reason.INVALID_TOKEN, null);
         }
         if (tokenResponse.getNotBeforePolicy() > deployment.getNotBefore()) {
             deployment.setNotBefore(tokenResponse.getNotBeforePolicy());
         }
         if (token.getIssuedAt() < deployment.getNotBefore()) {
             log.error("Stale token");
-            return challenge(403);
+            return challenge(403, OIDCAuthenticationError.Reason.STALE_TOKEN, null);
         }
         log.debug("successful authenticated");
         return null;
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/OIDCAuthenticationError.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/OIDCAuthenticationError.java
new file mode 100755
index 0000000..5b3f45d
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/OIDCAuthenticationError.java
@@ -0,0 +1,39 @@
+package org.keycloak.adapters;
+
+import org.keycloak.adapters.spi.AuthenticationError;
+
+/**
+ * Object that describes the OIDC error that happened.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class OIDCAuthenticationError implements AuthenticationError {
+    public static enum Reason {
+        NO_BEARER_TOKEN,
+        NO_REDIRECT_URI,
+        INVALID_STATE_COOKIE,
+        OAUTH_ERROR,
+        SSL_REQUIRED,
+        CODE_TO_TOKEN_FAILURE,
+        INVALID_TOKEN,
+        STALE_TOKEN,
+        NO_AUTHORIZATION_HEADER
+    }
+
+    private Reason reason;
+    private String description;
+
+    public OIDCAuthenticationError(Reason reason, String description) {
+        this.reason = reason;
+        this.description = description;
+    }
+
+    public Reason getReason() {
+        return reason;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}
diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/AuthChallenge.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/AuthChallenge.java
index ff0960f..8bb2f71 100755
--- a/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/AuthChallenge.java
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/AuthChallenge.java
@@ -13,15 +13,7 @@ public interface AuthChallenge {
     boolean challenge(HttpFacade exchange);
 
     /**
-     * Whether or not an error page should be displayed if possible along with the challenge
-     *
-     * @return
-     */
-    boolean errorPage();
-
-    /**
-     * If errorPage is true, this is the response code the challenge will send.  This is used by platforms
-     * that call HttpServletResponse.sendError() to forward to error page.
+     * Some platforms need the error code that will be sent (i.e. Undertow)
      *
      * @return
      */
diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/AuthenticationError.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/AuthenticationError.java
new file mode 100755
index 0000000..238a7d8
--- /dev/null
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/AuthenticationError.java
@@ -0,0 +1,13 @@
+package org.keycloak.adapters.spi;
+
+/**
+ * Common marker interface used by keycloak client adapters when there is an error.  For servlets, you'll be able
+ * to extract this error from the HttpServletRequest.getAttribute(AuthenticationError.class.getName()).  Each protocol
+ * will have their own subclass of this interface.
+ *
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface AuthenticationError {
+}
diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/HttpFacade.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/HttpFacade.java
index cf6e0d5..ead316a 100755
--- a/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/HttpFacade.java
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/HttpFacade.java
@@ -47,6 +47,8 @@ public interface HttpFacade {
         InputStream getInputStream();
 
         String getRemoteAddr();
+        void setError(AuthenticationError error);
+        void setError(LogoutError error);
     }
 
     interface Response {
diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/LogoutError.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/LogoutError.java
new file mode 100755
index 0000000..2d846ec
--- /dev/null
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/LogoutError.java
@@ -0,0 +1,12 @@
+package org.keycloak.adapters.spi;
+
+/**
+ * Common marker interface used by keycloak client adapters when there is an error.  For servlets, you'll be able
+ * to extract this error from the HttpServletRequest.getAttribute(LogoutError.class.getName()).  Each protocol
+ * will have their own subclass of this interface.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface LogoutError {
+}
diff --git a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java
index 29f483b..4af3b90 100755
--- a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java
+++ b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java
@@ -12,6 +12,8 @@ import javax.ws.rs.core.SecurityContext;
 
 import org.keycloak.KeycloakSecurityContext;
 import org.keycloak.adapters.OIDCHttpFacade;
+import org.keycloak.adapters.spi.AuthenticationError;
+import org.keycloak.adapters.spi.LogoutError;
 import org.keycloak.common.util.HostUtils;
 
 /**
@@ -93,6 +95,17 @@ public class JaxrsHttpFacade implements OIDCHttpFacade {
             // TODO: implement properly
             return HostUtils.getIpAddress();
         }
+
+        @Override
+        public void setError(AuthenticationError error) {
+            requestContext.setProperty(AuthenticationError.class.getName(), error);
+        }
+
+        @Override
+        public void setError(LogoutError error) {
+            requestContext.setProperty(LogoutError.class.getName(), error);
+
+        }
     }
 
     protected class ResponseFacade implements OIDCHttpFacade.Response {
diff --git a/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/spi/JettyHttpFacade.java b/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/spi/JettyHttpFacade.java
index c1008fd..42ff201 100755
--- a/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/spi/JettyHttpFacade.java
+++ b/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/spi/JettyHttpFacade.java
@@ -1,6 +1,8 @@
 package org.keycloak.adapters.jetty.spi;
 
+import org.keycloak.adapters.spi.AuthenticationError;
 import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.spi.LogoutError;
 import org.keycloak.common.util.MultivaluedHashMap;
 import org.keycloak.common.util.UriUtils;
 
@@ -125,6 +127,18 @@ public class JettyHttpFacade implements HttpFacade {
         public String getRemoteAddr() {
             return request.getRemoteAddr();
         }
+
+        @Override
+        public void setError(AuthenticationError error) {
+            request.setAttribute(AuthenticationError.class.getName(), error);
+
+        }
+
+        @Override
+        public void setError(LogoutError error) {
+            request.setAttribute(LogoutError.class.getName(), error);
+        }
+
     }
 
     protected class ResponseFacade implements Response {
diff --git a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java
index d3790af..dad7570 100755
--- a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java
+++ b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java
@@ -265,15 +265,6 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
         AuthChallenge challenge = authenticator.getChallenge();
         if (challenge != null) {
             challenge.challenge(facade);
-            if (challenge.errorPage() && errorPage != null) {
-                Response response = (Response)res;
-                try {
-                   response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), errorPage)));
-                } catch (IOException e) {
-                    throw new RuntimeException(e);
-                }
-
-            }
         }
         return Authentication.SEND_CONTINUE;
     }
diff --git a/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java b/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java
index eb487f5..6d32bda 100755
--- a/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java
+++ b/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java
@@ -1,6 +1,8 @@
 package org.keycloak.adapters.servlet;
 
+import org.keycloak.adapters.spi.AuthenticationError;
 import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.spi.LogoutError;
 import org.keycloak.common.util.MultivaluedHashMap;
 import org.keycloak.common.util.ServerCookie;
 import org.keycloak.common.util.UriUtils;
@@ -111,6 +113,18 @@ public class ServletHttpFacade implements HttpFacade {
         public String getRemoteAddr() {
             return request.getRemoteAddr();
         }
+
+
+        @Override
+        public void setError(AuthenticationError error) {
+            request.setAttribute(AuthenticationError.class.getName(), error);
+
+        }
+
+        @Override
+        public void setError(LogoutError error) {
+            request.setAttribute(LogoutError.class.getName(), error);
+        }
     }
     public boolean isEnded() {
         return responseFacade.isEnded();
diff --git a/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/KeycloakOIDCFilter.java b/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/KeycloakOIDCFilter.java
index 57bd93d..3cfc871 100755
--- a/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/KeycloakOIDCFilter.java
+++ b/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/KeycloakOIDCFilter.java
@@ -142,11 +142,6 @@ public class KeycloakOIDCFilter implements Filter {
         if (challenge != null) {
             log.fine("challenge");
             challenge.challenge(facade);
-            if (challenge.errorPage()) {
-                response.sendError(challenge.getResponseCode());
-                return;
-            }
-            log.fine("sending challenge");
             return;
         }
         response.sendError(403);
diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
index c3f1dfd..9c54ab1 100755
--- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
+++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
@@ -6,6 +6,8 @@ import org.keycloak.adapters.AdapterDeploymentContext;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.OIDCHttpFacade;
 import org.keycloak.adapters.ServerRequest;
+import org.keycloak.adapters.spi.AuthenticationError;
+import org.keycloak.adapters.spi.LogoutError;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.representations.IDToken;
@@ -237,6 +239,18 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
                 public String getRemoteAddr() {
                     return servletRequest.getRemoteAddr();
                 }
+
+                @Override
+                public void setError(AuthenticationError error) {
+                    servletRequest.setAttribute(AuthenticationError.class.getName(), error);
+
+                }
+
+                @Override
+                public void setError(LogoutError error) {
+                    servletRequest.setAttribute(LogoutError.class.getName(), error);
+                }
+
             };
         }
 
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java
index 79074da..ba9fa1a 100755
--- a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java
@@ -1,7 +1,9 @@
 package org.keycloak.adapters.springsecurity.facade;
 
+import org.keycloak.adapters.spi.AuthenticationError;
 import org.keycloak.adapters.spi.HttpFacade.Cookie;
 import org.keycloak.adapters.spi.HttpFacade.Request;
+import org.keycloak.adapters.spi.LogoutError;
 import org.springframework.util.Assert;
 
 import javax.servlet.http.HttpServletRequest;
@@ -109,4 +111,17 @@ class WrappedHttpServletRequest implements Request {
     public String getRemoteAddr() {
         return request.getRemoteAddr();
     }
+
+    @Override
+    public void setError(AuthenticationError error) {
+        request.setAttribute(AuthenticationError.class.getName(), error);
+
+    }
+
+    @Override
+    public void setError(LogoutError error) {
+        request.setAttribute(LogoutError.class.getName(), error);
+    }
+
+
 }
diff --git a/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java b/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java
index ba0b376..3638759 100755
--- a/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java
+++ b/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java
@@ -1,6 +1,8 @@
 package org.keycloak.adapters.tomcat;
 
+import org.keycloak.adapters.spi.AuthenticationError;
 import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.spi.LogoutError;
 import org.keycloak.common.util.MultivaluedHashMap;
 import org.keycloak.common.util.ServerCookie;
 import org.keycloak.common.util.UriUtils;
@@ -125,6 +127,18 @@ public class CatalinaHttpFacade implements HttpFacade {
         public String getRemoteAddr() {
             return request.getRemoteAddr();
         }
+
+        @Override
+        public void setError(AuthenticationError error) {
+            request.setAttribute(AuthenticationError.class.getName(), error);
+
+        }
+
+        @Override
+        public void setError(LogoutError error) {
+            request.setAttribute(LogoutError.class.getName(), error);
+        }
+
     }
 
     protected class ResponseFacade implements Response {
diff --git a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java
index 89bee43..20c87bd 100755
--- a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java
+++ b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java
@@ -195,12 +195,6 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
         }
         AuthChallenge challenge = authenticator.getChallenge();
         if (challenge != null) {
-            if (loginConfig == null) {
-                loginConfig = request.getContext().getLoginConfig();
-            }
-            if (challenge.errorPage()) {
-                if (forwardToErrorPageInternal(request, response, loginConfig))return false;
-            }
             challenge.challenge(facade);
         }
         return false;
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java
index aded0ef..36e8aa4 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java
@@ -56,11 +56,7 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
     public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) {
         AuthChallenge challenge = exchange.getAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY);
         if (challenge != null) {
-            if (challenge.errorPage() && errorPage != null) {
-                Integer code = servePage(exchange, errorPage);
-                return new ChallengeResult(true, code);
-            }
-            UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
+            UndertowHttpFacade facade = createFacade(exchange);
             if (challenge.challenge(facade)) {
                 return new ChallengeResult(true, exchange.getResponseCode());
             }
@@ -68,6 +64,10 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
         return new ChallengeResult(false);
     }
 
+    public UndertowHttpFacade createFacade(HttpServerExchange exchange) {
+        return new OIDCUndertowHttpFacade(exchange);
+    }
+
     protected Integer servePage(final HttpServerExchange exchange, final String location) {
         sendRedirect(exchange, location);
         return StatusCodes.TEMPORARY_REDIRECT;
@@ -89,7 +89,7 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
                 if (notification.getEventType() != SecurityNotification.EventType.LOGGED_OUT) return;
 
                 HttpServerExchange exchange = notification.getExchange();
-                UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange);
+                UndertowHttpFacade facade = createFacade(exchange);
                 KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
                 KeycloakSecurityContext ksc = exchange.getAttachment(OIDCUndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY);
                 if (ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) {
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/OIDCServletUndertowHttpFacade.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/OIDCServletUndertowHttpFacade.java
new file mode 100755
index 0000000..ddf0f41
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/OIDCServletUndertowHttpFacade.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.AttachmentKey;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.OIDCHttpFacade;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class OIDCServletUndertowHttpFacade extends ServletHttpFacade implements OIDCHttpFacade {
+    public static final AttachmentKey<KeycloakSecurityContext> KEYCLOAK_SECURITY_CONTEXT_KEY = AttachmentKey.create(KeycloakSecurityContext.class);
+
+    public OIDCServletUndertowHttpFacade(HttpServerExchange exchange) {
+        super(exchange);
+    }
+
+    @Override
+    public KeycloakSecurityContext getSecurityContext() {
+        return exchange.getAttachment(KEYCLOAK_SECURITY_CONTEXT_KEY);
+    }
+
+}
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 b5984a4..b546e76 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
@@ -79,7 +79,7 @@ public class ServletKeycloakAuthMech extends AbstractUndertowKeycloakAuthMech {
 
     @Override
     public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) {
-        UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange);
+        UndertowHttpFacade facade = createFacade(exchange);
         KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
         if (!deployment.isConfigured()) {
             return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
@@ -119,4 +119,8 @@ public class ServletKeycloakAuthMech extends AbstractUndertowKeycloakAuthMech {
         }
     }
 
+    @Override
+    public UndertowHttpFacade createFacade(HttpServerExchange exchange) {
+        return new OIDCServletUndertowHttpFacade(exchange);
+    }
 }
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
index 362cdb0..05672db 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java
@@ -61,11 +61,13 @@ public class ServletPreAuthActionsHandler implements HttpHandler {
 
     @Override
     public void handleRequest(HttpServerExchange exchange) throws Exception {
-        UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange);
+        UndertowHttpFacade facade = new OIDCServletUndertowHttpFacade(exchange);
         final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
         SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, servletRequestContext.getDeployment().getSessionManager());
         PreAuthActionsHandler handler = new PreAuthActionsHandler(bridge, deploymentContext, facade);
         if (handler.handleRequest()) return;
         next.handleRequest(exchange);
     }
+
+
 }
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java
index 85b7581..88ba705 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java
@@ -25,7 +25,7 @@ public class UndertowAuthenticationMechanism extends AbstractUndertowKeycloakAut
 
     @Override
     public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) {
-        UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange);
+        UndertowHttpFacade facade = createFacade(exchange);
         KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
         if (!deployment.isConfigured()) {
             return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java
index 77194e1..8ad97ef 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java
@@ -47,10 +47,14 @@ public class UndertowPreAuthActionsHandler implements HttpHandler {
 
     @Override
     public void handleRequest(HttpServerExchange exchange) throws Exception {
-        UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange);
+        UndertowHttpFacade facade = createFacade(exchange);
         SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, sessionManager);
         PreAuthActionsHandler handler = new PreAuthActionsHandler(bridge, deploymentContext, facade);
         if (handler.handleRequest()) return;
         next.handleRequest(exchange);
     }
+
+    public UndertowHttpFacade createFacade(HttpServerExchange exchange) {
+        return new OIDCUndertowHttpFacade(exchange);
+    }
 }
diff --git a/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java b/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java
index 0b10188..815b1ce 100755
--- a/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java
+++ b/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java
@@ -2,9 +2,13 @@ package org.keycloak.adapters.undertow;
 
 import io.undertow.server.HttpServerExchange;
 import io.undertow.servlet.handlers.ServletRequestContext;
+import org.keycloak.adapters.spi.AuthenticationError;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.spi.LogoutError;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -18,6 +22,7 @@ public class ServletHttpFacade extends UndertowHttpFacade {
         super(exchange);
         final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
         request = (HttpServletRequest)servletRequestContext.getServletRequest();
+        response = (HttpServletResponse)servletRequestContext.getServletResponse();
     }
 
     protected class RequestFacade extends UndertowHttpFacade.RequestFacade {
@@ -26,6 +31,47 @@ public class ServletHttpFacade extends UndertowHttpFacade {
             return request.getParameter(param);
         }
 
+        @Override
+        public void setError(AuthenticationError error) {
+            request.setAttribute(AuthenticationError.class.getName(), error);
+
+        }
+
+        @Override
+        public void setError(LogoutError error) {
+            request.setAttribute(LogoutError.class.getName(), error);
+        }
+
+
+    }
+
+    protected class ResponseFacade extends UndertowHttpFacade.ResponseFacade {
+        // can't call sendError from a challenge.  Undertow ends up calling send error.
+        /*
+        @Override
+        public void sendError(int code) {
+            try {
+                response.sendError(code);
+            } 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 Response getResponse() {
+        return new ResponseFacade();
     }
 
     @Override
diff --git a/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java b/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
index f420533..d38fe55 100755
--- a/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
+++ b/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
@@ -2,9 +2,12 @@ package org.keycloak.adapters.undertow;
 
 import io.undertow.server.HttpServerExchange;
 import io.undertow.server.handlers.CookieImpl;
+import io.undertow.util.AttachmentKey;
 import io.undertow.util.Headers;
 import io.undertow.util.HttpString;
+import org.keycloak.adapters.spi.AuthenticationError;
 import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.spi.LogoutError;
 import org.keycloak.common.util.KeycloakUriBuilder;
 
 import javax.security.cert.X509Certificate;
@@ -22,6 +25,9 @@ import java.util.Map;
  * @version $Revision: 1 $
  */
 public class UndertowHttpFacade implements HttpFacade {
+    public static final AttachmentKey<AuthenticationError> AUTH_ERROR_ATTACHMENT_KEY = AttachmentKey.create(AuthenticationError.class);
+    public static final AttachmentKey<LogoutError> LOGOUT_ERROR_ATTACHMENT_KEY = AttachmentKey.create(LogoutError.class);
+
     protected HttpServerExchange exchange;
     protected RequestFacade requestFacade = new RequestFacade();
     protected ResponseFacade responseFacade = new ResponseFacade();
@@ -127,6 +133,17 @@ public class UndertowHttpFacade implements HttpFacade {
             }
             return address.getHostAddress();
         }
+
+        @Override
+        public void setError(AuthenticationError error) {
+            exchange.putAttachment(AUTH_ERROR_ATTACHMENT_KEY, error);
+        }
+
+        @Override
+        public void setError(LogoutError error) {
+            exchange.putAttachment(LOGOUT_ERROR_ATTACHMENT_KEY, error);
+
+        }
     }
 
     protected class ResponseFacade implements Response {
@@ -173,7 +190,6 @@ public class UndertowHttpFacade implements HttpFacade {
         @Override
         public void sendError(int code) {
             exchange.setResponseCode(code);
-            exchange.endExchange();
         }
 
         @Override
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/InitiateLogin.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/InitiateLogin.java
index a34fb1b..158bae8 100755
--- a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/InitiateLogin.java
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/InitiateLogin.java
@@ -27,11 +27,6 @@ public class InitiateLogin implements AuthChallenge {
     }
 
     @Override
-    public boolean errorPage() {
-        return false;
-    }
-
-    @Override
     public int getResponseCode() {
         return 0;
     }
@@ -87,6 +82,7 @@ public class InitiateLogin implements AuthChallenge {
             Document document = authnRequestBuilder.toDocument();
             SamlDeployment.Binding samlBinding = deployment.getIDP().getSingleSignOnService().getRequestBinding();
             SamlUtil.sendSaml(true, httpFacade, actionUrl, binding, document, samlBinding);
+            sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.LOGGING_IN);
         } catch (Exception e) {
             throw new RuntimeException("Could not create authentication request.", e);
         }
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticationError.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticationError.java
new file mode 100755
index 0000000..8b63103
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticationError.java
@@ -0,0 +1,43 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.adapters.spi.AuthenticationError;
+import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
+import org.keycloak.dom.saml.v2.protocol.StatusType;
+
+/**
+ * Object that describes the SAML error that happened.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlAuthenticationError implements AuthenticationError {
+    public static enum Reason {
+        EXTRACTION_FAILURE,
+        INVALID_SIGNATURE,
+        ERROR_STATUS
+    }
+
+    private Reason reason;
+
+    private StatusResponseType status;
+
+    public SamlAuthenticationError(Reason reason) {
+        this.reason = reason;
+    }
+
+    public SamlAuthenticationError(Reason reason, StatusResponseType status) {
+        this.reason = reason;
+        this.status = status;
+    }
+
+    public SamlAuthenticationError(StatusResponseType statusType) {
+        this.status = statusType;
+    }
+
+    public Reason getReason() {
+        return reason;
+    }
+    public StatusResponseType getStatus() {
+        return status;
+    }
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java
index 39f026a..1d289d7 100755
--- a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java
@@ -1,10 +1,12 @@
 package org.keycloak.adapters.saml;
 
 import org.jboss.logging.Logger;
-import org.keycloak.common.VerificationException;
 import org.keycloak.adapters.spi.AuthChallenge;
 import org.keycloak.adapters.spi.AuthOutcome;
 import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.common.VerificationException;
+import org.keycloak.common.util.KeycloakUriBuilder;
+import org.keycloak.common.util.MultivaluedHashMap;
 import org.keycloak.dom.saml.v2.assertion.AssertionType;
 import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
 import org.keycloak.dom.saml.v2.assertion.AttributeType;
@@ -29,8 +31,6 @@ import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
 import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
 import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
 import org.keycloak.saml.processing.web.util.PostBindingUtil;
-import org.keycloak.common.util.KeycloakUriBuilder;
-import org.keycloak.common.util.MultivaluedHashMap;
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
 
@@ -74,7 +74,7 @@ public abstract class SamlAuthenticator {
             return handleSamlRequest(samlRequest, relayState);
         } else if (samlResponse != null) {
             return handleSamlResponse(samlResponse, relayState);
-        }  else if (sessionStore.isLoggedIn()) {
+        } else if (sessionStore.isLoggedIn()) {
             if (globalLogout) {
                 return globalLogout();
             }
@@ -106,6 +106,7 @@ public abstract class SamlAuthenticator {
 
         try {
             SamlUtil.sendSaml(true, facade, deployment.getIDP().getSingleLogoutService().getRequestBindingUrl(), binding, logoutBuilder.buildDocument(), deployment.getIDP().getSingleLogoutService().getRequestBinding());
+            sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.LOGGING_OUT);
         } catch (Exception e) {
             log.error("Could not send global logout SAML request", e);
             return AuthOutcome.FAILED;
@@ -155,7 +156,7 @@ public abstract class SamlAuthenticator {
     protected AuthOutcome logoutRequest(LogoutRequestType request, String relayState) {
         if (request.getSessionIndex() == null || request.getSessionIndex().isEmpty()) {
             sessionStore.logoutByPrincipal(request.getNameID().getValue());
-        }  else {
+        } else {
             sessionStore.logoutBySsoId(request.getSessionIndex());
         }
 
@@ -169,7 +170,8 @@ public abstract class SamlAuthenticator {
             binding.signatureAlgorithm(deployment.getSignatureAlgorithm())
                     .signWith(deployment.getSigningKeyPair())
                     .signDocument();
-            if (deployment.getSignatureCanonicalizationMethod() != null) binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
+            if (deployment.getSignatureCanonicalizationMethod() != null)
+                binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
         }
 
 
@@ -199,34 +201,80 @@ public abstract class SamlAuthenticator {
             postBinding = true;
             holder = extractPostBindingResponse(samlResponse);
         }
-        StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject();
+        final StatusResponseType statusResponse = (StatusResponseType) holder.getSamlObject();
         // validate destination
         if (!requestUri.equals(statusResponse.getDestination())) {
             log.error("Request URI does not match SAML request destination");
             return AuthOutcome.FAILED;
         }
         if (statusResponse instanceof ResponseType) {
-            if (deployment.getIDP().getSingleSignOnService().validateResponseSignature()) {
-                try {
-                    validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY);
-                } catch (VerificationException e) {
-                    log.error("Failed to verify saml response signature", e);
-                    return AuthOutcome.FAILED;
+            try {
+                if (deployment.getIDP().getSingleSignOnService().validateResponseSignature()) {
+                    try {
+                        validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY);
+                    } catch (VerificationException e) {
+                        log.error("Failed to verify saml response signature", e);
+
+                        challenge = new AuthChallenge() {
+                            @Override
+                            public boolean challenge(HttpFacade exchange) {
+                                SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE);
+                                exchange.getRequest().setError(error);
+                                exchange.getResponse().sendError(403);
+                                return true;
+                            }
+
+                            @Override
+                            public int getResponseCode() {
+                                return 403;
+                            }
+                        };
+                        return AuthOutcome.FAILED;
+                    }
                 }
+                return handleLoginResponse((ResponseType) statusResponse);
+            } finally {
+                sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE);
             }
-            return handleLoginResponse((ResponseType)statusResponse);
 
         } else {
-            if (deployment.getIDP().getSingleLogoutService().validateResponseSignature()) {
+            if (sessionStore.isLoggingOut()) {
                 try {
-                    validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY);
-                } catch (VerificationException e) {
-                    log.error("Failed to verify saml response signature", e);
+                    if (deployment.getIDP().getSingleLogoutService().validateResponseSignature()) {
+                        try {
+                            validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY);
+                        } catch (VerificationException e) {
+                            log.error("Failed to verify saml response signature", e);
+                            return AuthOutcome.FAILED;
+                        }
+                    }
+                    return handleLogoutResponse(holder, statusResponse, relayState);
+                } finally {
+                    sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE);
+                }
+
+            } else if (sessionStore.isLoggingIn()) {
+                try {
+                    challenge = new AuthChallenge() {
+                        @Override
+                        public boolean challenge(HttpFacade exchange) {
+                            SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.ERROR_STATUS, statusResponse);
+                            exchange.getRequest().setError(error);
+                            exchange.getResponse().sendError(403);
+                            return true;
+                        }
+
+                        @Override
+                        public int getResponseCode() {
+                            return 403;
+                        }
+                    };
                     return AuthOutcome.FAILED;
+                } finally {
+                    sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE);
                 }
             }
-            // todo need to check that it is actually a LogoutResponse
-            return handleLogoutResponse(holder, statusResponse, relayState);
+            return AuthOutcome.NOT_ATTEMPTED;
         }
 
     }
@@ -239,7 +287,7 @@ public abstract class SamlAuthenticator {
         }
     }
 
-    protected AuthOutcome handleLoginResponse(ResponseType responseType)  {
+    protected AuthOutcome handleLoginResponse(ResponseType responseType) {
         AssertionType assertion = null;
         try {
             assertion = AssertionUtil.getAssertion(responseType, deployment.getDecryptionKey());
@@ -248,7 +296,20 @@ public abstract class SamlAuthenticator {
             }
         } catch (Exception e) {
             log.error("Error extracting SAML assertion, e");
-            return AuthOutcome.FAILED;
+            challenge = new AuthChallenge() {
+                @Override
+                public boolean challenge(HttpFacade exchange) {
+                    SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.EXTRACTION_FAILURE);
+                    exchange.getRequest().setError(error);
+                    exchange.getResponse().sendError(403);
+                    return true;
+                }
+
+                @Override
+                public int getResponseCode() {
+                    return 403;
+                }
+            };
         }
 
         SubjectType subject = assertion.getSubject();
@@ -308,14 +369,14 @@ public abstract class SamlAuthenticator {
         AuthnStatementType authn = null;
         for (Object statement : assertion.getStatements()) {
             if (statement instanceof AuthnStatementType) {
-                authn = (AuthnStatementType)statement;
+                authn = (AuthnStatementType) statement;
                 break;
             }
         }
 
 
         URI nameFormat = subjectNameID.getFormat();
-        String nameFormatString = nameFormat == null ?  JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get() : nameFormat.toString();
+        String nameFormatString = nameFormat == null ? JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get() : nameFormat.toString();
         final SamlPrincipal principal = new SamlPrincipal(principalName, principalName, nameFormatString, attributes, friendlyAttributes);
         String index = authn == null ? null : authn.getSessionIndex();
         final String sessionIndex = index;
@@ -341,9 +402,9 @@ public abstract class SamlAuthenticator {
     protected abstract void completeAuthentication(SamlSession account);
 
     private String getAttributeValue(Object attrValue) {
-        String value =  null;
+        String value = null;
         if (attrValue instanceof String) {
-            value = (String)attrValue;
+            value = (String) attrValue;
         } else if (attrValue instanceof Node) {
             Node roleNode = (Node) attrValue;
             value = roleNode.getFirstChild().getNodeValue();
@@ -372,6 +433,7 @@ public abstract class SamlAuthenticator {
     protected SAMLDocumentHolder extractRedirectBindingResponse(String response) {
         return SAMLRequestParser.parseRequestRedirectBinding(response);
     }
+
     protected SAMLDocumentHolder extractPostBindingResponse(String response) {
         byte[] samlBytes = PostBindingUtil.base64Decode(response);
         String xml = new String(samlBytes);
@@ -379,7 +441,6 @@ public abstract class SamlAuthenticator {
     }
 
 
-
     protected AuthOutcome initiateLogin() {
         challenge = new InitiateLogin(deployment, sessionStore);
         return AuthOutcome.NOT_ATTEMPTED;
@@ -445,5 +506,4 @@ public abstract class SamlAuthenticator {
     }
 
 
-
 }
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSessionStore.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSessionStore.java
index da20026..1a19464 100755
--- a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSessionStore.java
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSessionStore.java
@@ -1,6 +1,8 @@
 package org.keycloak.adapters.saml;
 
 import org.keycloak.adapters.spi.AdapterSessionStore;
+import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
+import org.keycloak.dom.saml.v2.protocol.StatusType;
 
 import java.util.List;
 
@@ -9,6 +11,19 @@ import java.util.List;
  * @version $Revision: 1 $
  */
 public interface SamlSessionStore extends AdapterSessionStore {
+    public static final String CURRENT_ACTION = "SAML_CURRENT_ACTION";
+    public static final String SAML_LOGIN_ERROR_STATUS = "SAML_LOGIN_ERROR_STATUS";
+    public static final String SAML_LOGOUT_ERROR_STATUS = "SAML_LOGOUT_ERROR_STATUS";
+
+    enum CurrentAction {
+        NONE,
+        LOGGING_IN,
+        LOGGING_OUT
+    }
+    void setCurrentAction(CurrentAction action);
+    boolean isLoggingIn();
+    boolean isLoggingOut();
+
     boolean isLoggedIn();
     SamlSession getAccount();
     void saveAccount(SamlSession account);
diff --git a/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java b/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java
index 60cea89..2df0fad 100755
--- a/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java
+++ b/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java
@@ -260,15 +260,6 @@ public abstract class AbstractSamlAuthenticator extends LoginAuthenticator {
         AuthChallenge challenge = authenticator.getChallenge();
         if (challenge != null) {
             challenge.challenge(facade);
-            if (challenge.errorPage() && errorPage != null) {
-                Response response = (Response)res;
-                try {
-                    response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), errorPage)));
-                } catch (IOException e) {
-                    throw new RuntimeException(e);
-                }
-
-            }
         }
         return Authentication.SEND_CONTINUE;
     }
diff --git a/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/JettySamlSessionStore.java b/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/JettySamlSessionStore.java
index d86184b..6e51f11 100755
--- a/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/JettySamlSessionStore.java
+++ b/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/JettySamlSessionStore.java
@@ -8,6 +8,7 @@ import org.keycloak.adapters.spi.SessionIdMapper;
 import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
 import org.keycloak.adapters.saml.SamlSession;
 import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.dom.saml.v2.protocol.StatusType;
 
 import javax.servlet.http.HttpSession;
 
@@ -38,6 +39,28 @@ public class JettySamlSessionStore implements SamlSessionStore {
     }
 
     @Override
+    public void setCurrentAction(CurrentAction action) {
+        if (action == CurrentAction.NONE && request.getSession(false) == null) return;
+        request.getSession().setAttribute(CURRENT_ACTION, action);
+    }
+
+    @Override
+    public boolean isLoggingIn() {
+        HttpSession session = request.getSession(false);
+        if (session == null) return false;
+        CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION);
+        return action == CurrentAction.LOGGING_IN;
+    }
+
+    @Override
+    public boolean isLoggingOut() {
+        HttpSession session = request.getSession(false);
+        if (session == null) return false;
+        CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION);
+        return action == CurrentAction.LOGGING_OUT;
+    }
+
+    @Override
     public void logoutAccount() {
         HttpSession session = request.getSession(false);
         if (session != null) {
diff --git a/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/FilterSamlSessionStore.java b/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/FilterSamlSessionStore.java
index cbd036e..e690db5 100755
--- a/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/FilterSamlSessionStore.java
+++ b/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/FilterSamlSessionStore.java
@@ -7,6 +7,7 @@ import org.keycloak.adapters.spi.SessionIdMapper;
 import org.keycloak.adapters.saml.SamlSession;
 import org.keycloak.adapters.saml.SamlSessionStore;
 import org.keycloak.adapters.servlet.FilterSessionStore;
+import org.keycloak.dom.saml.v2.protocol.StatusType;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequestWrapper;
@@ -30,6 +31,28 @@ public class FilterSamlSessionStore extends FilterSessionStore implements SamlSe
     }
 
     @Override
+    public void setCurrentAction(CurrentAction action) {
+        if (action == CurrentAction.NONE && request.getSession(false) == null) return;
+        request.getSession().setAttribute(CURRENT_ACTION, action);
+    }
+
+    @Override
+    public boolean isLoggingIn() {
+        HttpSession session = request.getSession(false);
+        if (session == null) return false;
+        CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION);
+        return action == CurrentAction.LOGGING_IN;
+    }
+
+    @Override
+    public boolean isLoggingOut() {
+        HttpSession session = request.getSession(false);
+        if (session == null) return false;
+        CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION);
+        return action == CurrentAction.LOGGING_OUT;
+    }
+
+    @Override
     public void logoutAccount() {
         HttpSession session = request.getSession(false);
         if (session == null) return;
diff --git a/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java b/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java
index bce4dbc..b2a0ded 100755
--- a/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java
+++ b/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java
@@ -137,11 +137,6 @@ public class SamlFilter implements Filter {
         if (challenge != null) {
             log.fine("challenge");
             challenge.challenge(facade);
-            if (challenge.errorPage()) {
-                response.sendError(challenge.getResponseCode());
-                return;
-            }
-            log.fine("sending challenge");
             return;
         }
         if (!facade.isEnded()) {
diff --git a/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java b/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java
index d231265..3119ba7 100755
--- a/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java
+++ b/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java
@@ -213,10 +213,6 @@ public abstract class AbstractSamlAuthenticatorValve extends FormAuthenticator i
                 loginConfig = request.getContext().getLoginConfig();
             }
             challenge.challenge(facade);
-            if (challenge.errorPage()) {
-                log.fine("error page");
-                if (forwardToErrorPageInternal(request, response, loginConfig))return false;
-            }
         }
         return false;
     }
diff --git a/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/CatalinaSamlSessionStore.java b/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/CatalinaSamlSessionStore.java
index adbc441..804acf3 100755
--- a/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/CatalinaSamlSessionStore.java
+++ b/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/CatalinaSamlSessionStore.java
@@ -9,6 +9,8 @@ import org.keycloak.adapters.spi.HttpFacade;
 import org.keycloak.adapters.spi.SessionIdMapper;
 import org.keycloak.adapters.tomcat.CatalinaUserSessionManagement;
 import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
+import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
+import org.keycloak.dom.saml.v2.protocol.StatusType;
 
 import javax.servlet.http.HttpSession;
 import java.io.IOException;
@@ -42,6 +44,28 @@ public class CatalinaSamlSessionStore implements SamlSessionStore {
     }
 
     @Override
+    public void setCurrentAction(CurrentAction action) {
+        if (action == CurrentAction.NONE && request.getSession(false) == null) return;
+        request.getSession().setAttribute(CURRENT_ACTION, action);
+    }
+
+    @Override
+    public boolean isLoggingIn() {
+        HttpSession session = request.getSession(false);
+        if (session == null) return false;
+        CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION);
+        return action == CurrentAction.LOGGING_IN;
+    }
+
+    @Override
+    public boolean isLoggingOut() {
+        HttpSession session = request.getSession(false);
+        if (session == null) return false;
+        CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION);
+        return action == CurrentAction.LOGGING_OUT;
+    }
+
+    @Override
     public void logoutAccount() {
         Session sessionInternal = request.getSessionInternal(false);
         if (sessionInternal == null) return;
diff --git a/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java
index 721f0c0..8f1929a 100755
--- a/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java
+++ b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java
@@ -55,10 +55,6 @@ public abstract class AbstractSamlAuthMech implements AuthenticationMechanism {
     public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) {
         AuthChallenge challenge = exchange.getAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY);
         if (challenge != null) {
-            if (challenge.errorPage() && errorPage != null) {
-                Integer code = servePage(exchange, errorPage);
-                return new ChallengeResult(true, code);
-            }
             UndertowHttpFacade facade = createFacade(exchange);
             if (challenge.challenge(facade)) {
                 return new ChallengeResult(true, exchange.getResponseCode());
diff --git a/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java
index 8afcc1f..34d718b 100755
--- a/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java
+++ b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java
@@ -13,9 +13,12 @@ import org.keycloak.adapters.saml.SamlSession;
 import org.keycloak.adapters.saml.SamlSessionStore;
 import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
 import org.keycloak.common.util.KeycloakUriBuilder;
+import org.keycloak.dom.saml.v2.protocol.StatusType;
 
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
+import java.io.IOException;
 import java.security.Principal;
 import java.util.LinkedList;
 import java.util.List;
@@ -34,6 +37,7 @@ public class ServletSamlSessionStore implements SamlSessionStore {
     private final SecurityContext securityContext;
     private final SessionIdMapper idMapper;
 
+
     public ServletSamlSessionStore(HttpServerExchange exchange, UndertowUserSessionManagement sessionManagement,
                                    SecurityContext securityContext,
                                    SessionIdMapper idMapper) {
@@ -44,6 +48,28 @@ public class ServletSamlSessionStore implements SamlSessionStore {
     }
 
     @Override
+    public void setCurrentAction(CurrentAction action) {
+        if (action == CurrentAction.NONE && getRequest().getSession(false) == null) return;
+        getRequest().getSession().setAttribute(CURRENT_ACTION, action);
+    }
+
+    @Override
+    public boolean isLoggingIn() {
+        HttpSession session = getRequest().getSession(false);
+        if (session == null) return false;
+        CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION);
+        return action == CurrentAction.LOGGING_IN;
+    }
+
+    @Override
+    public boolean isLoggingOut() {
+        HttpSession session = getRequest().getSession(false);
+        if (session == null) return false;
+        CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION);
+        return action == CurrentAction.LOGGING_OUT;
+    }
+
+    @Override
     public void logoutAccount() {
         HttpSession session = getSession(false);
         if (session != null) {
@@ -170,8 +196,18 @@ public class ServletSamlSessionStore implements SamlSessionStore {
     }
 
     protected HttpSession getSession(boolean create) {
-        final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
-        HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
+        HttpServletRequest req = getRequest();
         return req.getSession(create);
     }
+
+    private HttpServletResponse getResponse() {
+        final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+        return (HttpServletResponse)servletRequestContext.getServletResponse();
+
+    }
+
+    private HttpServletRequest getRequest() {
+        final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+        return (HttpServletRequest) servletRequestContext.getServletRequest();
+    }
 }
diff --git a/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusCodeType.java b/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusCodeType.java
index 2ce85eb..482834b 100755
--- a/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusCodeType.java
+++ b/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusCodeType.java
@@ -17,6 +17,7 @@
  */
 package org.keycloak.dom.saml.v2.protocol;
 
+import java.io.Serializable;
 import java.net.URI;
 
 /**
@@ -39,7 +40,7 @@ import java.net.URI;
  * &lt;/complexType>
  * </pre>
  */
-public class StatusCodeType {
+public class StatusCodeType implements Serializable {
 
     protected StatusCodeType statusCode;
     protected URI value;
diff --git a/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusType.java b/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusType.java
index 9918879..2e2eab9 100755
--- a/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusType.java
+++ b/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusType.java
@@ -17,6 +17,8 @@
  */
 package org.keycloak.dom.saml.v2.protocol;
 
+import java.io.Serializable;
+
 /**
  * <p>
  * Java class for StatusType complex type.
@@ -38,7 +40,7 @@ package org.keycloak.dom.saml.v2.protocol;
  * &lt;/complexType>
  * </pre>
  */
-public class StatusType {
+public class StatusType implements Serializable {
 
     protected String statusMessage;
     protected StatusCodeType statusCode;
diff --git a/saml/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java b/saml/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
index db7ac7e..2373656 100755
--- a/saml/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
+++ b/saml/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
@@ -1,6 +1,12 @@
 package org.keycloak.saml;
 
+import org.keycloak.dom.saml.v2.assertion.NameIDType;
+import org.keycloak.dom.saml.v2.protocol.StatusCodeType;
+import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
+import org.keycloak.dom.saml.v2.protocol.StatusType;
 import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.common.exceptions.ConfigurationException;
+import org.keycloak.saml.common.exceptions.ParsingException;
 import org.keycloak.saml.common.exceptions.ProcessingException;
 import org.keycloak.saml.processing.api.saml.v2.response.SAML2Response;
 import org.keycloak.saml.processing.core.saml.v2.common.IDGenerator;
@@ -9,8 +15,11 @@ import org.keycloak.saml.processing.core.saml.v2.holders.IDPInfoHolder;
 import org.keycloak.saml.processing.core.saml.v2.holders.IssuerInfoHolder;
 import org.keycloak.saml.processing.core.saml.v2.holders.SPInfoHolder;
 import org.keycloak.dom.saml.v2.protocol.ResponseType;
+import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
 import org.w3c.dom.Document;
 
+import java.net.URI;
+
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
@@ -38,29 +47,25 @@ public class SAML2ErrorResponseBuilder {
 
 
     public Document buildDocument() throws ProcessingException {
-        Document samlResponse = null;
-        ResponseType responseType = null;
-
-        SAML2Response saml2Response = new SAML2Response();
-
-        // Create a response type
-        String id = IDGenerator.create("ID_");
 
-        IssuerInfoHolder issuerHolder = new IssuerInfoHolder(issuer);
-        issuerHolder.setStatusCode(status);
+        try {
+            StatusResponseType statusResponse = new StatusResponseType(IDGenerator.create("ID_"), XMLTimeUtil.getIssueInstant());
 
-        IDPInfoHolder idp = new IDPInfoHolder();
-        idp.setNameIDFormatValue(null);
-        idp.setNameIDFormat(JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get());
+            statusResponse.setStatus(JBossSAMLAuthnResponseFactory.createStatusTypeForResponder(status));
+            NameIDType issuer = new NameIDType();
+            issuer.setValue(this.issuer);
 
-        SPInfoHolder sp = new SPInfoHolder();
-        sp.setResponseDestinationURI(destination);
+            statusResponse.setIssuer(issuer);
+            statusResponse.setDestination(destination);
 
-        responseType = saml2Response.createResponseType(id);
-        responseType.setStatus(JBossSAMLAuthnResponseFactory.createStatusTypeForResponder(status));
-        responseType.setDestination(destination);
+            SAML2Response saml2Response = new SAML2Response();
+            return saml2Response.convert(statusResponse);
+        } catch (ConfigurationException e) {
+            throw new ProcessingException(e);
+        } catch (ParsingException e) {
+            throw new ProcessingException(e);
+        }
 
-        return samlResponse;
     }
 
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
index 816be92..31b0550 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
@@ -24,6 +24,7 @@ package org.keycloak.testsuite.adapter;
 import org.junit.Assert;
 import org.junit.rules.ExternalResource;
 import org.keycloak.OAuth2Constants;
+import org.keycloak.adapters.OIDCAuthenticationError;
 import org.keycloak.common.Version;
 import org.keycloak.representations.VersionRepresentation;
 import org.keycloak.admin.client.Keycloak;
@@ -42,6 +43,7 @@ import org.keycloak.testsuite.admin.ApiUtil;
 import org.keycloak.testsuite.pages.AccountSessionsPage;
 import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+import org.keycloak.testsuite.rule.ErrorServlet;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.WebResource;
 import org.keycloak.testsuite.rule.WebRule;
@@ -385,6 +387,7 @@ public class AdapterTestStrategy extends ExternalResource {
      * @throws Exception
      */
     public void testNullBearerTokenCustomErrorPage() throws Exception {
+        ErrorServlet.authError = null;
         Client client = ClientBuilder.newClient();
         WebTarget target = client.target(APP_SERVER_BASE_URL + "/customer-db-error-page/");
 
@@ -396,11 +399,15 @@ public class AdapterTestStrategy extends ExternalResource {
             response.close();
             response = client.target(location).request().get();
         }
-        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(401, response.getStatus());
         String errorPageResponse = response.readEntity(String.class);
         Assert.assertTrue(errorPageResponse.contains("Error Page"));
         response.close();
+        Assert.assertNotNull(ErrorServlet.authError);
+        OIDCAuthenticationError error = (OIDCAuthenticationError)ErrorServlet.authError;
+        Assert.assertEquals(OIDCAuthenticationError.Reason.NO_BEARER_TOKEN, error.getReason());
 
+        ErrorServlet.authError = null;
         response = target.request().header(HttpHeaders.AUTHORIZATION, "Bearer null").get();
         // TODO: follow redirects automatically if possible
         if (response.getStatus() == 302) {
@@ -408,10 +415,13 @@ public class AdapterTestStrategy extends ExternalResource {
             response.close();
             response = client.target(location).request().get();
         }
-        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(401, response.getStatus());
         errorPageResponse = response.readEntity(String.class);
         Assert.assertTrue(errorPageResponse.contains("Error Page"));
         response.close();
+        Assert.assertNotNull(ErrorServlet.authError);
+        error = (OIDCAuthenticationError)ErrorServlet.authError;
+        Assert.assertEquals(OIDCAuthenticationError.Reason.INVALID_TOKEN, error.getReason());
 
         client.close();
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTest.java
index f97a05e..0d20f7a 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTest.java
@@ -48,12 +48,7 @@ public class SamlAdapterTest {
 
     @Test
     public void testPostBadRealmSignature() {
-        testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() {
-            @Override
-            public void check(WebDriver driver) {
-                Assert.assertTrue(driver.getPageSource().contains("Forbidden"));
-            }
-        });
+        testStrategy.testPostBadRealmSignature();
     }
 
     @Test
@@ -61,12 +56,17 @@ public class SamlAdapterTest {
         testStrategy.testPostSimpleUnauthorized( new SamlAdapterTestStrategy.CheckAuthError() {
             @Override
             public void check(WebDriver driver) {
-                Assert.assertTrue(driver.getPageSource().contains("Forbidden"));
+                String pageSource = driver.getPageSource();
+                Assert.assertTrue(pageSource.contains("Error Page"));
             }
         });
     }
 
     @Test
+    public void testErrorHandling() throws Exception {
+        testStrategy.testErrorHandling();
+    }
+    @Test
     public void testMetadataPostSignedLoginLogout() throws Exception {
         testStrategy.testMetadataPostSignedLoginLogout();
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java
index 4791c2a..b63c960 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java
@@ -1,26 +1,17 @@
 package org.keycloak.testsuite.keycloaksaml;
 
 import org.apache.commons.io.IOUtils;
-import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
 import org.junit.Assert;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
 import org.junit.rules.ExternalResource;
-import org.keycloak.Config;
+import org.keycloak.adapters.saml.SamlAuthenticationError;
 import org.keycloak.adapters.saml.SamlPrincipal;
 import org.keycloak.admin.client.Keycloak;
 import org.keycloak.admin.client.resource.RealmResource;
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserSessionModel;
-import org.keycloak.protocol.oidc.OIDCLoginProtocol;
-import org.keycloak.protocol.oidc.TokenManager;
 import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
 import org.keycloak.protocol.saml.mappers.GroupMembershipMapper;
 import org.keycloak.protocol.saml.mappers.HardcodedAttributeMapper;
@@ -28,33 +19,28 @@ import org.keycloak.protocol.saml.mappers.HardcodedRole;
 import org.keycloak.protocol.saml.mappers.RoleListMapper;
 import org.keycloak.protocol.saml.mappers.RoleNameMapper;
 import org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper;
-import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.saml.BaseSAML2BindingBuilder;
+import org.keycloak.saml.SAML2ErrorResponseBuilder;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
 import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
 import org.keycloak.services.managers.RealmManager;
-import org.keycloak.services.resources.admin.AdminRoot;
 import org.keycloak.testsuite.KeycloakServer;
 import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+import org.keycloak.testsuite.rule.ErrorServlet;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.WebResource;
 import org.keycloak.testsuite.rule.WebRule;
-import org.keycloak.util.JsonSerialization;
 import org.openqa.selenium.WebDriver;
+import org.w3c.dom.Document;
 
 import javax.ws.rs.client.Client;
 import javax.ws.rs.client.ClientBuilder;
-import javax.ws.rs.client.ClientRequestContext;
-import javax.ws.rs.client.ClientRequestFilter;
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.client.WebTarget;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriBuilder;
 import java.io.IOException;
-import java.io.InputStream;
+import java.net.URI;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
@@ -115,6 +101,33 @@ public class SamlAdapterTestStrategy  extends ExternalResource {
         Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml"));
     }
 
+    public void testErrorHandling() throws Exception {
+        ErrorServlet.authError = null;
+        Client client = ClientBuilder.newClient();
+        // make sure
+        Response response = client.target(APP_SERVER_BASE_URL + "/employee-sig/").request().get();
+        response.close();
+        SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder()
+                .destination(APP_SERVER_BASE_URL + "/employee-sig/")
+                        .issuer(AUTH_SERVER_URL + "/realms/demo")
+                        .status(JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
+        BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder()
+                .relayState(null);
+        Document document = builder.buildDocument();
+        URI uri = binding.redirectBinding(document).generateURI(APP_SERVER_BASE_URL + "/employee-sig/", false);
+        response = client.target(uri).request().get();
+        String errorPage = response.readEntity(String.class);
+        response.close();
+        Assert.assertTrue(errorPage.contains("Error Page"));
+        client.close();
+        Assert.assertNotNull(ErrorServlet.authError);
+        SamlAuthenticationError error = (SamlAuthenticationError)ErrorServlet.authError;
+        Assert.assertEquals(SamlAuthenticationError.Reason.ERROR_STATUS, error.getReason());
+        Assert.assertNotNull(error.getStatus());
+        ErrorServlet.authError = null;
+
+    }
+
     public void testPostSimpleLoginLogout() {
         driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post/");
         assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
@@ -383,13 +396,17 @@ public class SamlAdapterTestStrategy  extends ExternalResource {
         void check(WebDriver driver);
     }
 
-    public void testPostBadRealmSignature(CheckAuthError error) {
+    public void testPostBadRealmSignature() {
+        ErrorServlet.authError = null;
         driver.navigate().to(APP_SERVER_BASE_URL + "/bad-realm-sales-post-sig/");
         assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
         loginPage.login("bburke", "password");
         assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/bad-realm-sales-post-sig/");
         System.out.println(driver.getPageSource());
-        error.check(driver);
+        Assert.assertNotNull(ErrorServlet.authError);
+        SamlAuthenticationError error = (SamlAuthenticationError)ErrorServlet.authError;
+        Assert.assertEquals(SamlAuthenticationError.Reason.INVALID_SIGNATURE, error.getReason());
+        ErrorServlet.authError = null;
     }
 
     public void testMetadataPostSignedLoginLogout() throws Exception {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlKeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlKeycloakRule.java
index 34d17f0..5a1d01f 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlKeycloakRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlKeycloakRule.java
@@ -115,6 +115,7 @@ public abstract class SamlKeycloakRule extends AbstractKeycloakRule {
                 .addServlets(regularServletInfo)
                 .addSecurityConstraint(constraint)
                 .addServletExtension(new SamlServletExtension());
+        addErrorPage("/error.html", deploymentInfo);
         server.getServer().deploy(deploymentInfo);
     }
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java
index fc2dc41..155bdf1 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java
@@ -1,6 +1,7 @@
 package org.keycloak.testsuite.rule;
 
 import io.undertow.servlet.api.DeploymentInfo;
+import io.undertow.servlet.api.ErrorPage;
 import io.undertow.servlet.api.FilterInfo;
 import io.undertow.servlet.api.LoginConfig;
 import io.undertow.servlet.api.SecurityConstraint;
@@ -156,7 +157,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
         return new DeploymentBuilder();
     }
 
-    public void addErrorPage(DeploymentInfo di) {
+    public void addErrorPage(String errorPage, DeploymentInfo di) {
         ServletInfo servlet = new ServletInfo("Error Page", ErrorServlet.class);
         servlet.addMapping("/error.html");
         SecurityConstraint constraint = new SecurityConstraint();
@@ -166,6 +167,11 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
         constraint.setEmptyRoleSemantic(SecurityInfo.EmptyRoleSemantic.PERMIT);
         di.addSecurityConstraint(constraint);
         di.addServlet(servlet);
+        di
+                .addErrorPage(new ErrorPage(errorPage, 400))
+                .addErrorPage(new ErrorPage(errorPage, 401))
+                .addErrorPage(new ErrorPage(errorPage, 403))
+                .addErrorPage(new ErrorPage(errorPage, 500));
     }
 
     public void deployJaxrsApplication(String name, String contextPath, Class<? extends Application> applicationClass, Map<String,String> initParams) {
@@ -346,9 +352,9 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
                 constraint.addRoleAllowed(role);
                 di.addSecurityConstraint(constraint);
             }
-            LoginConfig loginConfig = new LoginConfig("KEYCLOAK", "demo", null, errorPage);
+            LoginConfig loginConfig = new LoginConfig("KEYCLOAK", "demo", null, null);
             di.setLoginConfig(loginConfig);
-            addErrorPage(di);
+            addErrorPage(errorPage, di);
 
             server.getServer().deploy(di);
         }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/ErrorServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/ErrorServlet.java
index 47f7135..68410d4 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/ErrorServlet.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/ErrorServlet.java
@@ -1,5 +1,7 @@
 package org.keycloak.testsuite.rule;
 
+import org.keycloak.adapters.spi.AuthenticationError;
+
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
@@ -12,10 +14,11 @@ import java.io.PrintWriter;
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
 public class ErrorServlet extends HttpServlet {
+    public static AuthenticationError authError;
 
     @Override
     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-
+        authError = (AuthenticationError)req.getAttribute(AuthenticationError.class.getName());
 
         resp.setContentType("text/html");
         PrintWriter pw = resp.getWriter();
@@ -25,4 +28,9 @@ public class ErrorServlet extends HttpServlet {
 
 
     }
+
+    @Override
+    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        doGet(req, resp);
+    }
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlAdapterTest.java
index 88f97a6..9a47b44 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlAdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlAdapterTest.java
@@ -53,12 +53,7 @@ public class SamlAdapterTest {
 
     @Test
     public void testPostBadRealmSignature() {
-        testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() {
-            @Override
-            public void check(WebDriver driver) {
-                Assert.assertTrue(driver.getPageSource().contains("Forbidden"));
-            }
-        });
+        testStrategy.testPostBadRealmSignature();
     }
 
     @Test
@@ -72,7 +67,7 @@ public class SamlAdapterTest {
             testStrategy.testPostSimpleUnauthorized(new SamlAdapterTestStrategy.CheckAuthError() {
                 @Override
                 public void check(WebDriver driver) {
-                    Assert.assertTrue(driver.getPageSource().contains("Forbidden"));
+                    Assert.assertTrue(driver.getPageSource().contains("Error Page"));
                 }
             });
         } finally {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlKeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlKeycloakRule.java
index e1fd3c8..a348408 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlKeycloakRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlKeycloakRule.java
@@ -114,6 +114,7 @@ public abstract class SamlKeycloakRule extends AbstractKeycloakRule {
                 .addFilter(samlFilter)
                 .addFilterUrlMapping("saml-filter", "/*", DispatcherType.REQUEST)
                 .addServletExtension(new SamlServletExtension());
+        addErrorPage("/error.html", deploymentInfo);
         server.getServer().deploy(deploymentInfo);
     }
 
diff --git a/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/JettySamlTest.java b/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/JettySamlTest.java
index 1506b0c..6228a68 100755
--- a/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/JettySamlTest.java
+++ b/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/JettySamlTest.java
@@ -105,6 +105,11 @@ public class JettySamlTest {
     }
 
     @Test
+    public void testErrorHandling() throws Exception {
+        testStrategy.testErrorHandling();
+    }
+
+    @Test
     public void testPostSimpleLoginLogout() {
         testStrategy.testPostSimpleLoginLogout();
     }
@@ -166,12 +171,7 @@ public class JettySamlTest {
 
     @Test
     public void testPostBadRealmSignature() {
-        testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() {
-            @Override
-            public void check(WebDriver driver) {
-                Assert.assertEquals(driver.getPageSource(), "");
-            }
-        });
+        testStrategy.testPostBadRealmSignature();
     }
 
     @Test
diff --git a/testsuite/jetty/jetty81/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml b/testsuite/jetty/jetty81/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml
index f44a60b..e41448a 100755
--- a/testsuite/jetty/jetty81/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml
+++ b/testsuite/jetty/jetty81/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml
@@ -25,6 +25,26 @@
         <url-pattern>/error.html</url-pattern>
     </servlet-mapping>
 
+    <error-page>
+        <error-code>400</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>403</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>500</error-code>
+        <location>/error.html</location>
+    </error-page>
+
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
diff --git a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml
index 86db4a4..71eff52 100755
--- a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml
+++ b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml
@@ -10,11 +10,40 @@
         <servlet-name>SendUsernameServlet</servlet-name>
         <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
     </servlet>
+    <servlet>
+        <servlet-name>Error Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
+    </servlet>
     <servlet-mapping>
         <servlet-name>SendUsernameServlet</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 
+    <servlet-mapping>
+        <servlet-name>Error Servlet</servlet-name>
+        <url-pattern>/error.html</url-pattern>
+    </servlet-mapping>
+
+    <error-page>
+        <error-code>400</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>403</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>500</error-code>
+        <location>/error.html</location>
+    </error-page>
+
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
diff --git a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml
index 86db4a4..ed4f018 100755
--- a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml
+++ b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml
@@ -10,11 +10,39 @@
         <servlet-name>SendUsernameServlet</servlet-name>
         <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
     </servlet>
+    <servlet>
+        <servlet-name>Error Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
+    </servlet>
     <servlet-mapping>
         <servlet-name>SendUsernameServlet</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 
+    <servlet-mapping>
+        <servlet-name>Error Servlet</servlet-name>
+        <url-pattern>/error.html</url-pattern>
+    </servlet-mapping>
+
+    <error-page>
+        <error-code>400</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>403</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>500</error-code>
+        <location>/error.html</location>
+    </error-page>
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
diff --git a/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/JettySamlTest.java b/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/JettySamlTest.java
index e71887e..40edb45 100755
--- a/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/JettySamlTest.java
+++ b/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/JettySamlTest.java
@@ -104,6 +104,11 @@ public class JettySamlTest {
     }
 
     @Test
+    public void testErrorHandling() throws Exception {
+        testStrategy.testErrorHandling();
+    }
+
+    @Test
     public void testPostSimpleLoginLogout() {
         testStrategy.testPostSimpleLoginLogout();
     }
@@ -165,12 +170,7 @@ public class JettySamlTest {
 
     @Test
     public void testPostBadRealmSignature() {
-        testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() {
-            @Override
-            public void check(WebDriver driver) {
-                Assert.assertEquals(driver.getPageSource(), "");
-            }
-        });
+        testStrategy.testPostBadRealmSignature();
     }
 
     @Test
diff --git a/testsuite/jetty/jetty91/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml b/testsuite/jetty/jetty91/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml
index f44a60b..e41448a 100755
--- a/testsuite/jetty/jetty91/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml
+++ b/testsuite/jetty/jetty91/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml
@@ -25,6 +25,26 @@
         <url-pattern>/error.html</url-pattern>
     </servlet-mapping>
 
+    <error-page>
+        <error-code>400</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>403</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>500</error-code>
+        <location>/error.html</location>
+    </error-page>
+
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
diff --git a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml
index 86db4a4..2f7ef22 100755
--- a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml
+++ b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml
@@ -10,12 +10,39 @@
         <servlet-name>SendUsernameServlet</servlet-name>
         <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
     </servlet>
+    <servlet>
+        <servlet-name>Error Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
+    </servlet>
     <servlet-mapping>
         <servlet-name>SendUsernameServlet</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 
-    <security-constraint>
+    <servlet-mapping>
+        <servlet-name>Error Servlet</servlet-name>
+        <url-pattern>/error.html</url-pattern>
+    </servlet-mapping>
+
+    <error-page>
+        <error-code>400</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>403</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>500</error-code>
+        <location>/error.html</location>
+    </error-page>    <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
             <url-pattern>/*</url-pattern>
diff --git a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml
index 86db4a4..ed4f018 100755
--- a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml
+++ b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml
@@ -10,11 +10,39 @@
         <servlet-name>SendUsernameServlet</servlet-name>
         <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
     </servlet>
+    <servlet>
+        <servlet-name>Error Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
+    </servlet>
     <servlet-mapping>
         <servlet-name>SendUsernameServlet</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 
+    <servlet-mapping>
+        <servlet-name>Error Servlet</servlet-name>
+        <url-pattern>/error.html</url-pattern>
+    </servlet-mapping>
+
+    <error-page>
+        <error-code>400</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>403</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>500</error-code>
+        <location>/error.html</location>
+    </error-page>
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
diff --git a/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/JettySamlTest.java b/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/JettySamlTest.java
index e71887e..cd3c11a 100755
--- a/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/JettySamlTest.java
+++ b/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/JettySamlTest.java
@@ -104,6 +104,11 @@ public class JettySamlTest {
     }
 
     @Test
+    public void testErrorHandling() throws Exception {
+        testStrategy.testErrorHandling();
+    }
+
+    @Test
     public void testPostSimpleLoginLogout() {
         testStrategy.testPostSimpleLoginLogout();
     }
@@ -165,12 +170,7 @@ public class JettySamlTest {
 
     @Test
     public void testPostBadRealmSignature() {
-        testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() {
-            @Override
-            public void check(WebDriver driver) {
-                Assert.assertEquals(driver.getPageSource(), "");
-            }
-        });
+        testStrategy.testPostBadRealmSignature( );
     }
 
     @Test
diff --git a/testsuite/jetty/jetty92/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml b/testsuite/jetty/jetty92/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml
index f44a60b..e41448a 100755
--- a/testsuite/jetty/jetty92/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml
+++ b/testsuite/jetty/jetty92/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml
@@ -25,6 +25,26 @@
         <url-pattern>/error.html</url-pattern>
     </servlet-mapping>
 
+    <error-page>
+        <error-code>400</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>403</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>500</error-code>
+        <location>/error.html</location>
+    </error-page>
+
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
diff --git a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml
index 86db4a4..ed4f018 100755
--- a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml
+++ b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml
@@ -10,11 +10,39 @@
         <servlet-name>SendUsernameServlet</servlet-name>
         <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
     </servlet>
+    <servlet>
+        <servlet-name>Error Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
+    </servlet>
     <servlet-mapping>
         <servlet-name>SendUsernameServlet</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 
+    <servlet-mapping>
+        <servlet-name>Error Servlet</servlet-name>
+        <url-pattern>/error.html</url-pattern>
+    </servlet-mapping>
+
+    <error-page>
+        <error-code>400</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>403</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>500</error-code>
+        <location>/error.html</location>
+    </error-page>
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
diff --git a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml
index 86db4a4..ed4f018 100755
--- a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml
+++ b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml
@@ -10,11 +10,39 @@
         <servlet-name>SendUsernameServlet</servlet-name>
         <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
     </servlet>
+    <servlet>
+        <servlet-name>Error Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
+    </servlet>
     <servlet-mapping>
         <servlet-name>SendUsernameServlet</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 
+    <servlet-mapping>
+        <servlet-name>Error Servlet</servlet-name>
+        <url-pattern>/error.html</url-pattern>
+    </servlet-mapping>
+
+    <error-page>
+        <error-code>400</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>403</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>500</error-code>
+        <location>/error.html</location>
+    </error-page>
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
diff --git a/testsuite/tomcat6/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java b/testsuite/tomcat6/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java
index c72ab01..0e6973a 100755
--- a/testsuite/tomcat6/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java
+++ b/testsuite/tomcat6/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java
@@ -113,6 +113,11 @@ public class TomcatSamlTest {
     }
 
     @Test
+    public void testErrorHandling() throws Exception {
+        testStrategy.testErrorHandling();
+    }
+
+    @Test
     public void testPostSignedLoginLogoutEmailNameID() {
         testStrategy.testPostSignedLoginLogoutEmailNameID();
     }
@@ -149,12 +154,7 @@ public class TomcatSamlTest {
 
     @Test
     public void testPostBadRealmSignature() {
-        testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() {
-            @Override
-            public void check(WebDriver driver) {
-                Assert.assertEquals(driver.getPageSource(), "");
-            }
-        });
+        testStrategy.testPostBadRealmSignature();
     }
 
     @Test
diff --git a/testsuite/tomcat6/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml b/testsuite/tomcat6/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml
index c2cef86..fa6a47b 100755
--- a/testsuite/tomcat6/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml
+++ b/testsuite/tomcat6/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml
@@ -25,6 +25,26 @@
         <url-pattern>/error.html</url-pattern>
     </servlet-mapping>
 
+    <error-page>
+        <error-code>400</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>403</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>500</error-code>
+        <location>/error.html</location>
+    </error-page>
+
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
@@ -44,11 +64,7 @@
     <login-config>
         <auth-method>BASIC</auth-method>
         <realm-name>demo</realm-name>
-        <form-login-config>
-            <form-login-page>/error.html</form-login-page>
-            <form-error-page>/error.html</form-error-page>
-        </form-login-config>
-    </login-config>
+     </login-config>
 
     <security-role>
         <role-name>admin</role-name>
diff --git a/testsuite/tomcat6/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml b/testsuite/tomcat6/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml
index 86db4a4..2f7ef22 100755
--- a/testsuite/tomcat6/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml
+++ b/testsuite/tomcat6/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml
@@ -10,12 +10,39 @@
         <servlet-name>SendUsernameServlet</servlet-name>
         <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
     </servlet>
+    <servlet>
+        <servlet-name>Error Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
+    </servlet>
     <servlet-mapping>
         <servlet-name>SendUsernameServlet</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 
-    <security-constraint>
+    <servlet-mapping>
+        <servlet-name>Error Servlet</servlet-name>
+        <url-pattern>/error.html</url-pattern>
+    </servlet-mapping>
+
+    <error-page>
+        <error-code>400</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>403</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>500</error-code>
+        <location>/error.html</location>
+    </error-page>    <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
             <url-pattern>/*</url-pattern>
diff --git a/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml b/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml
index 86db4a4..ed4f018 100755
--- a/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml
+++ b/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml
@@ -10,11 +10,39 @@
         <servlet-name>SendUsernameServlet</servlet-name>
         <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
     </servlet>
+    <servlet>
+        <servlet-name>Error Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
+    </servlet>
     <servlet-mapping>
         <servlet-name>SendUsernameServlet</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 
+    <servlet-mapping>
+        <servlet-name>Error Servlet</servlet-name>
+        <url-pattern>/error.html</url-pattern>
+    </servlet-mapping>
+
+    <error-page>
+        <error-code>400</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>403</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>500</error-code>
+        <location>/error.html</location>
+    </error-page>
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
diff --git a/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java b/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java
index f9cb853..2483333 100755
--- a/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java
+++ b/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java
@@ -93,6 +93,11 @@ public class TomcatSamlTest {
 
 
     @Test
+    public void testErrorHandling() throws Exception {
+        testStrategy.testErrorHandling();
+    }
+
+    @Test
     public void testPostSimpleLoginLogout() {
         testStrategy.testPostSimpleLoginLogout();
     }
@@ -154,12 +159,7 @@ public class TomcatSamlTest {
 
     @Test
     public void testPostBadRealmSignature() {
-        testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() {
-            @Override
-            public void check(WebDriver driver) {
-                Assert.assertEquals(driver.getPageSource(), "");
-            }
-        });
+        testStrategy.testPostBadRealmSignature();
     }
 
     @Test
diff --git a/testsuite/tomcat7/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml b/testsuite/tomcat7/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml
index c2cef86..fdd69a3 100755
--- a/testsuite/tomcat7/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml
+++ b/testsuite/tomcat7/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml
@@ -25,6 +25,26 @@
         <url-pattern>/error.html</url-pattern>
     </servlet-mapping>
 
+    <error-page>
+        <error-code>400</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>403</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>500</error-code>
+        <location>/error.html</location>
+    </error-page>
+
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
diff --git a/testsuite/tomcat7/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml b/testsuite/tomcat7/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml
index 86db4a4..ed4f018 100755
--- a/testsuite/tomcat7/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml
+++ b/testsuite/tomcat7/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml
@@ -10,11 +10,39 @@
         <servlet-name>SendUsernameServlet</servlet-name>
         <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
     </servlet>
+    <servlet>
+        <servlet-name>Error Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
+    </servlet>
     <servlet-mapping>
         <servlet-name>SendUsernameServlet</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 
+    <servlet-mapping>
+        <servlet-name>Error Servlet</servlet-name>
+        <url-pattern>/error.html</url-pattern>
+    </servlet-mapping>
+
+    <error-page>
+        <error-code>400</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>403</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>500</error-code>
+        <location>/error.html</location>
+    </error-page>
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
diff --git a/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml b/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml
index 86db4a4..ed4f018 100755
--- a/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml
+++ b/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml
@@ -10,11 +10,39 @@
         <servlet-name>SendUsernameServlet</servlet-name>
         <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
     </servlet>
+    <servlet>
+        <servlet-name>Error Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
+    </servlet>
     <servlet-mapping>
         <servlet-name>SendUsernameServlet</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 
+    <servlet-mapping>
+        <servlet-name>Error Servlet</servlet-name>
+        <url-pattern>/error.html</url-pattern>
+    </servlet-mapping>
+
+    <error-page>
+        <error-code>400</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>403</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>500</error-code>
+        <location>/error.html</location>
+    </error-page>
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
diff --git a/testsuite/tomcat8/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java b/testsuite/tomcat8/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java
index 405c6ee..a4a3829 100755
--- a/testsuite/tomcat8/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java
+++ b/testsuite/tomcat8/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java
@@ -93,6 +93,11 @@ public class TomcatSamlTest {
     public SamlAdapterTestStrategy testStrategy = new SamlAdapterTestStrategy("http://localhost:8081/auth", "http://localhost:8082", keycloakRule);
 
     @Test
+    public void testErrorHandling() throws Exception {
+        testStrategy.testErrorHandling();
+    }
+
+    @Test
     public void testPostSimpleLoginLogout() {
         testStrategy.testPostSimpleLoginLogout();
     }
@@ -154,12 +159,7 @@ public class TomcatSamlTest {
 
     @Test
     public void testPostBadRealmSignature() {
-        testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() {
-            @Override
-            public void check(WebDriver driver) {
-                Assert.assertEquals(driver.getPageSource(), "");
-            }
-        });
+        testStrategy.testPostBadRealmSignature();
     }
 
     @Test
diff --git a/testsuite/tomcat8/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml b/testsuite/tomcat8/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml
index c2cef86..fdd69a3 100755
--- a/testsuite/tomcat8/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml
+++ b/testsuite/tomcat8/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml
@@ -25,6 +25,26 @@
         <url-pattern>/error.html</url-pattern>
     </servlet-mapping>
 
+    <error-page>
+        <error-code>400</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>403</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>500</error-code>
+        <location>/error.html</location>
+    </error-page>
+
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
diff --git a/testsuite/tomcat8/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml b/testsuite/tomcat8/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml
index 86db4a4..ed4f018 100755
--- a/testsuite/tomcat8/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml
+++ b/testsuite/tomcat8/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml
@@ -10,11 +10,39 @@
         <servlet-name>SendUsernameServlet</servlet-name>
         <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
     </servlet>
+    <servlet>
+        <servlet-name>Error Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
+    </servlet>
     <servlet-mapping>
         <servlet-name>SendUsernameServlet</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 
+    <servlet-mapping>
+        <servlet-name>Error Servlet</servlet-name>
+        <url-pattern>/error.html</url-pattern>
+    </servlet-mapping>
+
+    <error-page>
+        <error-code>400</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>403</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>500</error-code>
+        <location>/error.html</location>
+    </error-page>
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
diff --git a/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml b/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml
index 86db4a4..ed4f018 100755
--- a/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml
+++ b/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml
@@ -10,11 +10,39 @@
         <servlet-name>SendUsernameServlet</servlet-name>
         <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
     </servlet>
+    <servlet>
+        <servlet-name>Error Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
+    </servlet>
     <servlet-mapping>
         <servlet-name>SendUsernameServlet</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 
+    <servlet-mapping>
+        <servlet-name>Error Servlet</servlet-name>
+        <url-pattern>/error.html</url-pattern>
+    </servlet-mapping>
+
+    <error-page>
+        <error-code>400</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>403</error-code>
+        <location>/error.html</location>
+    </error-page>
+
+    <error-page>
+        <error-code>500</error-code>
+        <location>/error.html</location>
+    </error-page>
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>