keycloak-uncached

error page adapter support

12/23/2014 7:33:08 PM

Changes

Details

diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthChallenge.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthChallenge.java
index 4682808..10f960e 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthChallenge.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthChallenge.java
@@ -11,4 +11,11 @@ public interface AuthChallenge {
      * @return challenge sent
      */
     boolean challenge(HttpFacade exchange);
+
+    /**
+     * Whether or not an error page should be displayed if possible
+     *
+     * @return
+     */
+    boolean errorPage();
 }
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 ccbe596..81b1d42 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
@@ -110,6 +110,11 @@ public class BearerTokenRequestAuthenticator {
     protected AuthChallenge clientCertChallenge() {
         return new AuthChallenge() {
             @Override
+            public boolean errorPage() {
+                return false;
+            }
+
+            @Override
             public boolean challenge(HttpFacade exchange) {
                 // do the same thing as client cert auth
                 return false;
@@ -130,6 +135,11 @@ public class BearerTokenRequestAuthenticator {
         final String challenge = header.toString();
         return new AuthChallenge() {
             @Override
+            public boolean errorPage() {
+                return false;
+            }
+
+            @Override
             public boolean challenge(HttpFacade facade) {
                 facade.getResponse().setStatus(401);
                 facade.getResponse().addHeader("WWW-Authenticate", challenge);
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 f5451c8..d780f5d 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
@@ -146,13 +146,29 @@ public class OAuthRequestAuthenticator {
     protected AuthChallenge loginRedirect() {
         final String state = getStateCode();
         final String redirect = getRedirectUri(state);
-        return new AuthChallenge() {
-            @Override
-            public boolean challenge(HttpFacade exchange) {
-                if (redirect == null) {
+        if (redirect == null) {
+            return new AuthChallenge() {
+                @Override
+                public boolean challenge(HttpFacade exchange) {
                     exchange.getResponse().setStatus(403);
                     return true;
                 }
+
+                @Override
+                public boolean errorPage() {
+                    return true;
+                }
+            };
+        }
+        return new AuthChallenge() {
+
+            @Override
+            public boolean errorPage() {
+                return false;
+            }
+
+            @Override
+            public boolean challenge(HttpFacade exchange) {
                 tokenStore.saveRequest();
                 log.debug("Sending redirect to login page: " + redirect);
                 exchange.getResponse().setStatus(302);
@@ -219,6 +235,11 @@ public class OAuthRequestAuthenticator {
     protected AuthChallenge challenge(final int code) {
         return new AuthChallenge() {
             @Override
+            public boolean errorPage() {
+                return true;
+            }
+
+            @Override
             public boolean challenge(HttpFacade exchange) {
                 exchange.getResponse().setStatus(code);
                 return true;
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/jbossweb/KeycloakAuthenticatorValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/jbossweb/KeycloakAuthenticatorValve.java
index 71e0f19..91cbb87 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/jbossweb/KeycloakAuthenticatorValve.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/jbossweb/KeycloakAuthenticatorValve.java
@@ -11,6 +11,7 @@ import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
 import java.security.Principal;
 import java.util.List;
 
@@ -22,10 +23,20 @@ import java.util.List;
  */
 public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorValve {
     public boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws java.io.IOException {
-        return authenticateInternal(request, response);
+        return authenticateInternal(request, response, config);
     }
 
     @Override
+    protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
+        if (loginConfig == null) return false;
+        LoginConfig config = (LoginConfig)loginConfig;
+        if (config.getErrorPage() == null) return false;
+        forwardToErrorPage(request, (Response)response, config);
+        return true;
+    }
+
+
+    @Override
     public void start() throws LifecycleException {
         StandardContext standardContext = (StandardContext) context;
         standardContext.addLifecycleListener(this);
diff --git a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/AbstractKeycloakJettyAuthenticator.java b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/AbstractKeycloakJettyAuthenticator.java
index 2e78eed..fc01385 100755
--- a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/AbstractKeycloakJettyAuthenticator.java
+++ b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/AbstractKeycloakJettyAuthenticator.java
@@ -1,14 +1,18 @@
 package org.keycloak.adapters.jetty;
 
+import org.apache.http.HttpVersion;
 import org.eclipse.jetty.security.DefaultUserIdentity;
 import org.eclipse.jetty.security.ServerAuthException;
 import org.eclipse.jetty.security.UserAuthentication;
 import org.eclipse.jetty.security.authentication.DeferredAuthentication;
+import org.eclipse.jetty.security.authentication.FormAuthenticator;
 import org.eclipse.jetty.security.authentication.LoginAuthenticator;
 import org.eclipse.jetty.server.Authentication;
 import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
 import org.eclipse.jetty.server.UserIdentity;
 import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.URIUtil;
 import org.jboss.logging.Logger;
 import org.keycloak.KeycloakPrincipal;
 import org.keycloak.KeycloakSecurityContext;
@@ -37,6 +41,7 @@ import javax.servlet.http.HttpServletResponse;
 import java.io.ByteArrayInputStream;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.InputStream;
 import java.util.HashSet;
 import java.util.Set;
@@ -52,6 +57,7 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
     protected NodesRegistrationManagement nodesRegistrationManagement;
     protected AdapterConfig adapterConfig;
     protected KeycloakConfigResolver configResolver;
+    protected String errorPage;
 
     public AbstractKeycloakJettyAuthenticator() {
         super();
@@ -66,7 +72,7 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
     }
 
     public AdapterTokenStore getTokenStore(Request request, HttpFacade facade, KeycloakDeployment resolvedDeployment) {
-        AdapterTokenStore store = (AdapterTokenStore)request.getAttribute(TOKEN_STORE_NOTE);
+        AdapterTokenStore store = (AdapterTokenStore) request.getAttribute(TOKEN_STORE_NOTE);
         if (store != null) {
             return store;
         }
@@ -84,8 +90,8 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
     public abstract AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment);
 
     public void logoutCurrent(Request request) {
-        AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext)request.getAttribute(AdapterDeploymentContext.class.getName());
-        KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
+        AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext) request.getAttribute(AdapterDeploymentContext.class.getName());
+        KeycloakSecurityContext ksc = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
         if (ksc != null) {
             JettyHttpFacade facade = new JettyHttpFacade(request, null);
             KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
@@ -115,11 +121,25 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
     public void setConfiguration(AuthConfiguration configuration) {
         //super.setConfiguration(configuration);
         initializeKeycloak();
+        String error = configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
+        setErrorPage(error);
+    }
+
+    private void setErrorPage(String path) {
+        if (path == null || path.trim().length() == 0) {
+        } else {
+            if (!path.startsWith("/")) {
+                path = "/" + path;
+            }
+            errorPage = path;
+
+            if (errorPage.indexOf('?') > 0)
+                errorPage = errorPage.substring(0, errorPage.indexOf('?'));
+        }
     }
 
     @Override
-    public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, Authentication.User validatedUser) throws ServerAuthException
-    {
+    public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, Authentication.User validatedUser) throws ServerAuthException {
         return true;
     }
 
@@ -167,12 +187,13 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
             InputStream configInputStream = getConfigInputStream(theServletContext);
             if (configInputStream != null) {
                 deploymentContext = new AdapterDeploymentContext(KeycloakDeploymentBuilder.build(configInputStream));
-             }
+            }
         }
         if (deploymentContext == null) {
             deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
         }
-        if (theServletContext != null) theServletContext.setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
+        if (theServletContext != null)
+            theServletContext.setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
     }
 
     private InputStream getConfigInputStream(ServletContext servletContext) {
@@ -198,7 +219,7 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
             log.trace("*** authenticate");
         }
         Request request = resolveRequest(req);
-        JettyHttpFacade facade = new JettyHttpFacade(request, (HttpServletResponse)res);
+        JettyHttpFacade facade = new JettyHttpFacade(request, (HttpServletResponse) res);
         KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
         if (deployment == null || !deployment.isConfigured()) {
             log.debug("*** deployment isn't configured return false");
@@ -231,17 +252,25 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
         }
         AuthChallenge challenge = authenticator.getChallenge();
         if (challenge != null) {
+            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);
+                }
+
+            }
             challenge.challenge(facade);
         }
         return Authentication.SEND_CONTINUE;
     }
 
 
-
     protected abstract Request resolveRequest(ServletRequest req);
 
     protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade,
-                                                                           KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
+                                                                   KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
         return new JettyRequestAuthenticator(facade, deployment, tokenStore, -1, request);
     }
 
@@ -263,8 +292,7 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
 
     protected abstract Authentication createAuthentication(UserIdentity userIdentity);
 
-    public static abstract class KeycloakAuthentication extends UserAuthentication
-    {
+    public static abstract class KeycloakAuthentication extends UserAuthentication {
         public KeycloakAuthentication(String method, UserIdentity userIdentity) {
             super(method, userIdentity);
         }
diff --git a/integration/tomcat/tomcat6/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java b/integration/tomcat/tomcat6/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java
index fa6ac80..5dc7701 100755
--- a/integration/tomcat/tomcat6/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java
+++ b/integration/tomcat/tomcat6/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java
@@ -8,6 +8,8 @@ import org.apache.catalina.deploy.LoginConfig;
 import org.apache.catalina.realm.GenericPrincipal;
 
 import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
 import java.security.Principal;
 import java.util.List;
 
@@ -20,10 +22,20 @@ import java.util.List;
 public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorValve {
     @Override
     public boolean authenticate(Request request, Response response, LoginConfig config) throws java.io.IOException {
-        return authenticateInternal(request, response);
+        return authenticateInternal(request, response, config);
     }
 
     @Override
+    protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
+        if (loginConfig == null) return false;
+        LoginConfig config = (LoginConfig)loginConfig;
+        if (config.getErrorPage() == null) return false;
+        forwardToErrorPage(request, (Response)response, config);
+        return true;
+    }
+
+
+    @Override
     public void start() throws LifecycleException {
         StandardContext standardContext = (StandardContext) context;
         standardContext.addLifecycleListener(this);
diff --git a/integration/tomcat/tomcat7/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java b/integration/tomcat/tomcat7/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java
index 700378e..45402e6 100755
--- a/integration/tomcat/tomcat7/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java
+++ b/integration/tomcat/tomcat7/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java
@@ -1,6 +1,7 @@
 package org.keycloak.adapters.tomcat;
 
 import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
 import org.apache.catalina.core.StandardContext;
 import org.apache.catalina.deploy.LoginConfig;
 import org.apache.catalina.realm.GenericPrincipal;
@@ -19,9 +20,19 @@ import java.util.List;
  */
 public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorValve {
     public boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws IOException {
-        return authenticateInternal(request, response);
+        return authenticateInternal(request, response, config);
     }
 
+    @Override
+    protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
+        if (loginConfig == null) return false;
+        LoginConfig config = (LoginConfig)loginConfig;
+        if (config.getErrorPage() == null) return false;
+        forwardToErrorPage(request, (Response)response, config);
+        return true;
+    }
+
+
     protected void initInternal() {
         StandardContext standardContext = (StandardContext) context;
         standardContext.addLifecycleListener(this);
diff --git a/integration/tomcat/tomcat8/pom.xml b/integration/tomcat/tomcat8/pom.xml
index 8e9eb1b..f503a66 100755
--- a/integration/tomcat/tomcat8/pom.xml
+++ b/integration/tomcat/tomcat8/pom.xml
@@ -33,6 +33,19 @@
             <version>${project.version}</version>
         </dependency>
         <dependency>
+            <groupId>org.apache.tomcat</groupId>
+            <artifactId>tomcat-servlet-api</artifactId>
+            <version>${tomcat.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.tomcat</groupId>
+            <artifactId>tomcat-catalina</artifactId>
+            <version>${tomcat.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-tomcat-core-adapter</artifactId>
             <version>${project.version}</version>
diff --git a/integration/tomcat/tomcat8/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java b/integration/tomcat/tomcat8/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java
index 9812c3d..f390fe7 100755
--- a/integration/tomcat/tomcat8/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java
+++ b/integration/tomcat/tomcat8/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java
@@ -1,12 +1,19 @@
 package org.keycloak.adapters.tomcat;
 
+import org.apache.catalina.authenticator.FormAuthenticator;
 import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
 import org.apache.catalina.core.StandardContext;
 import org.apache.catalina.realm.GenericPrincipal;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.descriptor.web.LoginConfig;
 
+import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.security.Principal;
 import java.util.List;
 
@@ -18,7 +25,32 @@ import java.util.List;
  */
 public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorValve {
     public boolean authenticate(Request request, HttpServletResponse response) throws IOException {
-        return authenticateInternal(request, response);
+       return authenticateInternal(request, response, request.getContext().getLoginConfig());
+    }
+
+    @Override
+    protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
+        if (loginConfig == null) return false;
+        LoginConfig config = (LoginConfig)loginConfig;
+        if (config.getErrorPage() == null) return false;
+        // had to do this to get around compiler/IDE issues :(
+        try {
+            Method method = null;
+            /*
+            for (Method m : getClass().getDeclaredMethods()) {
+                if (m.getName().equals("forwardToErrorPage")) {
+                    method = m;
+                    break;
+                }
+            }
+            */
+            method = FormAuthenticator.class.getDeclaredMethod("forwardToErrorPage", Request.class, HttpServletResponse.class, LoginConfig.class);
+            method.setAccessible(true);
+            method.invoke(this, request, response, config);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return true;
     }
 
     protected void initInternal() {
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 acf33a4..ffc5941 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
@@ -10,6 +10,7 @@ import org.apache.catalina.authenticator.FormAuthenticator;
 import org.apache.catalina.authenticator.SavedRequest;
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
+import org.apache.catalina.deploy.LoginConfig;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.keycloak.KeycloakSecurityContext;
 import org.keycloak.constants.AdapterConstants;
@@ -175,8 +176,9 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
     }
 
     protected abstract GenericPrincipalFactory createPrincipalFactory();
+    protected abstract boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException;
 
-    protected boolean authenticateInternal(Request request, HttpServletResponse response) {
+    protected boolean authenticateInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
         CatalinaHttpFacade facade = new CatalinaHttpFacade(request, response);
         KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
         if (deployment == null || !deployment.isConfigured()) {
@@ -196,6 +198,12 @@ 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 9a35896..892343b 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
@@ -23,7 +23,9 @@ import io.undertow.security.api.SecurityNotification;
 import io.undertow.server.HttpServerExchange;
 import io.undertow.server.session.Session;
 import io.undertow.util.AttachmentKey;
+import io.undertow.util.Headers;
 import io.undertow.util.Sessions;
+import io.undertow.util.StatusCodes;
 import org.keycloak.KeycloakPrincipal;
 import org.keycloak.KeycloakSecurityContext;
 import org.keycloak.adapters.AdapterDeploymentContext;
@@ -46,16 +48,22 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
     public static final AttachmentKey<AuthChallenge> KEYCLOAK_CHALLENGE_ATTACHMENT_KEY = AttachmentKey.create(AuthChallenge.class);
     protected AdapterDeploymentContext deploymentContext;
     protected UndertowUserSessionManagement sessionManagement;
+    protected String errorPage;
 
-    public AbstractUndertowKeycloakAuthMech(AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement) {
+    public AbstractUndertowKeycloakAuthMech(AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement, String errorPage) {
         this.deploymentContext = deploymentContext;
         this.sessionManagement = sessionManagement;
+        this.errorPage = errorPage;
     }
 
     @Override
     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);
             if (challenge.challenge(facade)) {
                 return new ChallengeResult(true, exchange.getResponseCode());
@@ -64,6 +72,19 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
         return new ChallengeResult(false);
     }
 
+    protected Integer servePage(final HttpServerExchange exchange, final String location) {
+        sendRedirect(exchange, location);
+        return StatusCodes.TEMPORARY_REDIRECT;
+    }
+
+    static void sendRedirect(final HttpServerExchange exchange, final String location) {
+        // TODO - String concatenation to construct URLS is extremely error prone - switch to a URI which will better handle this.
+        String loc = exchange.getRequestScheme() + "://" + exchange.getHostAndPort() + location;
+        exchange.getResponseHeaders().put(Headers.LOCATION, loc);
+    }
+
+
+
     protected void registerNotifications(final SecurityContext securityContext) {
 
         final NotificationReceiver logoutReceiver = new NotificationReceiver() {
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
index 4014a92..0258621 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
@@ -32,11 +32,12 @@ import io.undertow.servlet.api.LoginConfig;
 import io.undertow.servlet.api.ServletSessionConfig;
 import io.undertow.servlet.util.ImmediateInstanceHandle;
 import org.jboss.logging.Logger;
-import org.keycloak.constants.AdapterConstants;
 import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.KeycloakConfigResolver;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
 import org.keycloak.adapters.NodesRegistrationManagement;
+import org.keycloak.constants.AdapterConstants;
 
 import javax.servlet.ServletContext;
 import java.io.ByteArrayInputStream;
@@ -44,7 +45,6 @@ import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.util.Map;
-import org.keycloak.adapters.KeycloakConfigResolver;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -57,9 +57,9 @@ public class KeycloakServletExtension implements ServletExtension {
     // todo when this DeploymentInfo method of the same name is fixed.
     public boolean isAuthenticationMechanismPresent(DeploymentInfo deploymentInfo, final String mechanismName) {
         LoginConfig loginConfig = deploymentInfo.getLoginConfig();
-        if(loginConfig != null) {
-            for(AuthMethodConfig method : loginConfig.getAuthMethods()) {
-                if(method.getName().equalsIgnoreCase(mechanismName)) {
+        if (loginConfig != null) {
+            for (AuthMethodConfig method : loginConfig.getAuthMethods()) {
+                if (method.getName().equalsIgnoreCase(mechanismName)) {
                     return true;
                 }
             }
@@ -191,7 +191,17 @@ public class KeycloakServletExtension implements ServletExtension {
 
     protected ServletKeycloakAuthMech createAuthenticationMechanism(DeploymentInfo deploymentInfo, AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement userSessionManagement,
                                                                     NodesRegistrationManagement nodesRegistrationManagement) {
-       log.debug("creating ServletKeycloakAuthMech");
-       return new ServletKeycloakAuthMech(deploymentContext, userSessionManagement, nodesRegistrationManagement, deploymentInfo.getConfidentialPortManager());
+        log.debug("creating ServletKeycloakAuthMech");
+        String errorPage = getErrorPage(deploymentInfo);
+        return new ServletKeycloakAuthMech(deploymentContext, userSessionManagement, nodesRegistrationManagement, deploymentInfo.getConfidentialPortManager(), errorPage);
+    }
+
+    protected String getErrorPage(DeploymentInfo deploymentInfo) {
+        LoginConfig loginConfig = deploymentInfo.getLoginConfig();
+        String errorPage = null;
+        if (loginConfig != null) {
+            errorPage = loginConfig.getErrorPage();
+        }
+        return errorPage;
     }
 }
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 0ae5b6e..144210f 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
@@ -16,18 +16,28 @@
  */
 package org.keycloak.adapters.undertow;
 
+import io.undertow.security.api.AuthenticationMechanism;
 import io.undertow.security.api.SecurityContext;
 import io.undertow.server.HttpServerExchange;
 import io.undertow.servlet.api.ConfidentialPortManager;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import io.undertow.util.Headers;
 import org.jboss.logging.Logger;
 import org.keycloak.adapters.AdapterDeploymentContext;
 import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.AuthChallenge;
 import org.keycloak.adapters.HttpFacade;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.NodesRegistrationManagement;
 import org.keycloak.adapters.RequestAuthenticator;
 import org.keycloak.enums.TokenStore;
 
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.io.IOException;
+
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
@@ -39,13 +49,37 @@ public class ServletKeycloakAuthMech extends AbstractUndertowKeycloakAuthMech {
     protected NodesRegistrationManagement nodesRegistrationManagement;
     protected ConfidentialPortManager portManager;
 
-    public ServletKeycloakAuthMech(AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement userSessionManagement, NodesRegistrationManagement nodesRegistrationManagement, ConfidentialPortManager portManager) {
-        super(deploymentContext, userSessionManagement);
+    public ServletKeycloakAuthMech(AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement userSessionManagement,
+                                   NodesRegistrationManagement nodesRegistrationManagement, ConfidentialPortManager portManager,
+                                   String errorPage) {
+        super(deploymentContext, userSessionManagement, errorPage);
         this.nodesRegistrationManagement = nodesRegistrationManagement;
         this.portManager = portManager;
     }
 
     @Override
+    protected Integer servePage(HttpServerExchange exchange, String location) {
+        final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+        ServletRequest req = servletRequestContext.getServletRequest();
+        ServletResponse resp = servletRequestContext.getServletResponse();
+        RequestDispatcher disp = req.getRequestDispatcher(location);
+        //make sure the login page is never cached
+        exchange.getResponseHeaders().add(Headers.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
+        exchange.getResponseHeaders().add(Headers.PRAGMA, "no-cache");
+        exchange.getResponseHeaders().add(Headers.EXPIRES, "0");
+
+
+        try {
+            disp.forward(req, resp);
+        } catch (ServletException e) {
+            throw new RuntimeException(e);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return null;
+    }
+
+    @Override
     public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) {
         UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
         KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
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 057fc50..b408255 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
@@ -17,8 +17,8 @@ public class UndertowAuthenticationMechanism extends AbstractUndertowKeycloakAut
     protected int confidentialPort;
 
     public UndertowAuthenticationMechanism(AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement,
-                                           NodesRegistrationManagement nodesRegistrationManagement, int confidentialPort) {
-        super(deploymentContext, sessionManagement);
+                                           NodesRegistrationManagement nodesRegistrationManagement, int confidentialPort, String errorPage) {
+        super(deploymentContext, sessionManagement, errorPage);
         this.nodesRegistrationManagement = nodesRegistrationManagement;
         this.confidentialPort = confidentialPort;
     }
diff --git a/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyAuthenticationMechanism.java b/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyAuthenticationMechanism.java
index 6880d66..85f8bd3 100755
--- a/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyAuthenticationMechanism.java
+++ b/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyAuthenticationMechanism.java
@@ -21,8 +21,8 @@ public class WildflyAuthenticationMechanism extends ServletKeycloakAuthMech {
     public WildflyAuthenticationMechanism(AdapterDeploymentContext deploymentContext,
                                           UndertowUserSessionManagement userSessionManagement,
                                           NodesRegistrationManagement nodesRegistrationManagement,
-                                          ConfidentialPortManager portManager) {
-        super(deploymentContext, userSessionManagement, nodesRegistrationManagement, portManager);
+                                          ConfidentialPortManager portManager, String errorPage) {
+        super(deploymentContext, userSessionManagement, nodesRegistrationManagement, portManager, errorPage);
     }
 
     @Override
diff --git a/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyKeycloakServletExtension.java b/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyKeycloakServletExtension.java
index 0cdf2f9..2e43285 100755
--- a/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyKeycloakServletExtension.java
+++ b/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyKeycloakServletExtension.java
@@ -19,7 +19,7 @@ public class WildflyKeycloakServletExtension extends KeycloakServletExtension {
     protected ServletKeycloakAuthMech createAuthenticationMechanism(DeploymentInfo deploymentInfo, AdapterDeploymentContext deploymentContext,
                                                                     UndertowUserSessionManagement userSessionManagement, NodesRegistrationManagement nodesRegistrationManagement) {
         log.debug("creating WildflyAuthenticationMechanism");
-        return new WildflyAuthenticationMechanism(deploymentContext, userSessionManagement, nodesRegistrationManagement, deploymentInfo.getConfidentialPortManager());
+        return new WildflyAuthenticationMechanism(deploymentContext, userSessionManagement, nodesRegistrationManagement, deploymentInfo.getConfidentialPortManager(), getErrorPage(deploymentInfo));
 
     }
 }
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java
index 8835856..59977e6 100755
--- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java
@@ -229,7 +229,7 @@ public class ProxyServerBuilder {
             handler = new ConstraintMatcherHandler(matches, handler, toWrap, errorPage);
             final List<AuthenticationMechanism> mechanisms = new LinkedList<AuthenticationMechanism>();
             mechanisms.add(new CachedAuthenticatedSessionMechanism());
-            mechanisms.add(new UndertowAuthenticationMechanism(deploymentContext, userSessionManagement, nodesRegistrationManagement, -1));
+            mechanisms.add(new UndertowAuthenticationMechanism(deploymentContext, userSessionManagement, nodesRegistrationManagement, -1, null));
             handler = new AuthenticationMechanismsHandler(handler, mechanisms);
             IdentityManager identityManager = new IdentityManager() {
                 @Override
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 49b1a6d..6ff9df2 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
@@ -224,6 +224,9 @@ public class AdapterTestStrategy extends ExternalResource {
         Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
         driver.navigate().to(APP_SERVER_BASE_URL + "/customer-portal");
         Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        loginPage.cancel();
+        System.out.println(driver.getPageSource());
+        Assert.assertTrue(driver.getPageSource().contains("Error Page"));
 
 
     }
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 d618567..0603751 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
@@ -5,6 +5,7 @@ import io.undertow.servlet.api.DeploymentInfo;
 import io.undertow.servlet.api.FilterInfo;
 import io.undertow.servlet.api.LoginConfig;
 import io.undertow.servlet.api.SecurityConstraint;
+import io.undertow.servlet.api.SecurityInfo;
 import io.undertow.servlet.api.ServletInfo;
 import io.undertow.servlet.api.WebResourceCollection;
 import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
@@ -126,6 +127,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
         server.getServer().deploy(deploymentInfo);
     }
 
+
     private DeploymentInfo createDeploymentInfo(String name, String contextPath, Class<? extends Servlet> servletClass) {
         DeploymentInfo deploymentInfo = new DeploymentInfo();
         deploymentInfo.setClassLoader(getClass().getClassLoader());
@@ -168,11 +170,25 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
             constraint.addRoleAllowed(role);
             di.addSecurityConstraint(constraint);
         }
-        LoginConfig loginConfig = new LoginConfig("KEYCLOAK", "demo");
+        LoginConfig loginConfig = new LoginConfig("KEYCLOAK", "demo", null, "/error.html");
         di.setLoginConfig(loginConfig);
+        addErrorPage(di);
+
         server.getServer().deploy(di);
     }
 
+    public void addErrorPage(DeploymentInfo di) {
+        ServletInfo servlet = new ServletInfo("Error Page", ErrorServlet.class);
+        servlet.addMapping("/error.html");
+        SecurityConstraint constraint = new SecurityConstraint();
+        WebResourceCollection collection = new WebResourceCollection();
+        collection.addUrlPattern("/error.html");
+        constraint.addWebResourceCollection(collection);
+        constraint.setEmptyRoleSemantic(SecurityInfo.EmptyRoleSemantic.PERMIT);
+        di.addSecurityConstraint(constraint);
+        di.addServlet(servlet);
+    }
+
     public void deployJaxrsApplication(String name, String contextPath, Class<? extends Application> applicationClass, Map<String,String> initParams) {
         ResteasyDeployment deployment = new ResteasyDeployment();
         deployment.setApplicationClass(applicationClass.getName());
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
new file mode 100755
index 0000000..1f8534d
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/ErrorServlet.java
@@ -0,0 +1,28 @@
+package org.keycloak.testsuite.rule;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ErrorServlet extends HttpServlet {
+
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+
+
+        resp.setContentType("text/html");
+        PrintWriter pw = resp.getWriter();
+        pw.printf("<html><head><title>%s</title></head><body>", "Error Page");
+        pw.print("<h1>There was an error</h1></body></html>");
+        pw.flush();
+
+
+    }
+}
diff --git a/testsuite/jetty/jetty81/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml b/testsuite/jetty/jetty81/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml
index 19acca0..f96add7 100755
--- a/testsuite/jetty/jetty81/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml
+++ b/testsuite/jetty/jetty81/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml
@@ -10,12 +10,21 @@
         <servlet-name>Servlet</servlet-name>
         <servlet-class>org.keycloak.testsuite.adapter.CustomerServlet</servlet-class>
     </servlet>
+    <servlet>
+        <servlet-name>Error Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
+    </servlet>
 
     <servlet-mapping>
         <servlet-name>Servlet</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 
+    <servlet-mapping>
+        <servlet-name>Error Servlet</servlet-name>
+        <url-pattern>/error.html</url-pattern>
+    </servlet-mapping>
+
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
@@ -25,12 +34,23 @@
             <role-name>user</role-name>
         </auth-constraint>
     </security-constraint>
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Errors</web-resource-name>
+            <url-pattern>/error.html</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
 
     <login-config>
-        <auth-method>BASIC</auth-method>
+        <auth-method>FORM</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>
 
+♦
     <security-role>
         <role-name>admin</role-name>
     </security-role>
diff --git a/testsuite/jetty/jetty91/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml b/testsuite/jetty/jetty91/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml
index 19acca0..cce0616 100755
--- a/testsuite/jetty/jetty91/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml
+++ b/testsuite/jetty/jetty91/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml
@@ -10,12 +10,21 @@
         <servlet-name>Servlet</servlet-name>
         <servlet-class>org.keycloak.testsuite.adapter.CustomerServlet</servlet-class>
     </servlet>
+    <servlet>
+        <servlet-name>Error Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
+    </servlet>
 
     <servlet-mapping>
         <servlet-name>Servlet</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 
+    <servlet-mapping>
+        <servlet-name>Error Servlet</servlet-name>
+        <url-pattern>/error.html</url-pattern>
+    </servlet-mapping>
+
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
@@ -25,10 +34,20 @@
             <role-name>user</role-name>
         </auth-constraint>
     </security-constraint>
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Errors</web-resource-name>
+            <url-pattern>/error.html</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
 
     <login-config>
-        <auth-method>BASIC</auth-method>
+        <auth-method>FORM</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>
 
     <security-role>
diff --git a/testsuite/jetty/jetty92/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml b/testsuite/jetty/jetty92/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml
index 19acca0..cce0616 100755
--- a/testsuite/jetty/jetty92/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml
+++ b/testsuite/jetty/jetty92/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml
@@ -10,12 +10,21 @@
         <servlet-name>Servlet</servlet-name>
         <servlet-class>org.keycloak.testsuite.adapter.CustomerServlet</servlet-class>
     </servlet>
+    <servlet>
+        <servlet-name>Error Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
+    </servlet>
 
     <servlet-mapping>
         <servlet-name>Servlet</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 
+    <servlet-mapping>
+        <servlet-name>Error Servlet</servlet-name>
+        <url-pattern>/error.html</url-pattern>
+    </servlet-mapping>
+
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
@@ -25,10 +34,20 @@
             <role-name>user</role-name>
         </auth-constraint>
     </security-constraint>
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Errors</web-resource-name>
+            <url-pattern>/error.html</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
 
     <login-config>
-        <auth-method>BASIC</auth-method>
+        <auth-method>FORM</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>
 
     <security-role>
diff --git a/testsuite/tomcat6/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml b/testsuite/tomcat6/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml
index 19acca0..9c211cb 100755
--- a/testsuite/tomcat6/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml
+++ b/testsuite/tomcat6/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml
@@ -10,12 +10,21 @@
         <servlet-name>Servlet</servlet-name>
         <servlet-class>org.keycloak.testsuite.adapter.CustomerServlet</servlet-class>
     </servlet>
+    <servlet>
+        <servlet-name>Error Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
+    </servlet>
 
     <servlet-mapping>
         <servlet-name>Servlet</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 
+    <servlet-mapping>
+        <servlet-name>Error Servlet</servlet-name>
+        <url-pattern>/error.html</url-pattern>
+    </servlet-mapping>
+
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
@@ -25,10 +34,20 @@
             <role-name>user</role-name>
         </auth-constraint>
     </security-constraint>
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Errors</web-resource-name>
+            <url-pattern>/error.html</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
 
     <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>
 
     <security-role>
diff --git a/testsuite/tomcat7/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml b/testsuite/tomcat7/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml
index 19acca0..9c211cb 100755
--- a/testsuite/tomcat7/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml
+++ b/testsuite/tomcat7/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml
@@ -10,12 +10,21 @@
         <servlet-name>Servlet</servlet-name>
         <servlet-class>org.keycloak.testsuite.adapter.CustomerServlet</servlet-class>
     </servlet>
+    <servlet>
+        <servlet-name>Error Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
+    </servlet>
 
     <servlet-mapping>
         <servlet-name>Servlet</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 
+    <servlet-mapping>
+        <servlet-name>Error Servlet</servlet-name>
+        <url-pattern>/error.html</url-pattern>
+    </servlet-mapping>
+
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
@@ -25,10 +34,20 @@
             <role-name>user</role-name>
         </auth-constraint>
     </security-constraint>
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Errors</web-resource-name>
+            <url-pattern>/error.html</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
 
     <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>
 
     <security-role>
diff --git a/testsuite/tomcat8/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml b/testsuite/tomcat8/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml
index 19acca0..9c211cb 100755
--- a/testsuite/tomcat8/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml
+++ b/testsuite/tomcat8/src/test/resources/adapter-test/customer-portal/WEB-INF/web.xml
@@ -10,12 +10,21 @@
         <servlet-name>Servlet</servlet-name>
         <servlet-class>org.keycloak.testsuite.adapter.CustomerServlet</servlet-class>
     </servlet>
+    <servlet>
+        <servlet-name>Error Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
+    </servlet>
 
     <servlet-mapping>
         <servlet-name>Servlet</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 
+    <servlet-mapping>
+        <servlet-name>Error Servlet</servlet-name>
+        <url-pattern>/error.html</url-pattern>
+    </servlet-mapping>
+
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>Users</web-resource-name>
@@ -25,10 +34,20 @@
             <role-name>user</role-name>
         </auth-constraint>
     </security-constraint>
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Errors</web-resource-name>
+            <url-pattern>/error.html</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
 
     <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>
 
     <security-role>