keycloak-uncached

Changes

.travis.yml 10(+2 -8)

docbook/auth-server-docs/reference/en/en-US/modules/admin-recovery.xml 15(+0 -15)

pom.xml 12(+9 -3)

services/src/main/java/org/keycloak/offlineconfig/AdminRecovery.java 90(+0 -90)

services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientResponseRepresentation.java 77(+0 -77)

testsuite/integration/src/test/java/org/keycloak/testsuite/offlineconfig/AdminRecoveryTest.java 133(+0 -133)

wildfly/pom.xml 22(+22 -0)

Details

.travis.yml 10(+2 -8)

diff --git a/.travis.yml b/.travis.yml
index 8146d6e..5dee839 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,14 +3,8 @@ language: java
 jdk:
   - oraclejdk8
 
-cache:
-    directories:
-        - $HOME/.m2
-
-before_cache:
-  - rm -rf $HOME/.m2/repository/org/keycloak
-
-install: mvn install -Pdistribution -DskipTests=true -B -V
+install: 
+  - travis_wait mvn install -Pdistribution -DskipTests=true -B -V -q
 
 script:
   - mvn test -B
diff --git a/client-registration/api/src/main/java/org/keycloak/client/registration/Auth.java b/client-registration/api/src/main/java/org/keycloak/client/registration/Auth.java
index 31ad4e8..c3dd313 100644
--- a/client-registration/api/src/main/java/org/keycloak/client/registration/Auth.java
+++ b/client-registration/api/src/main/java/org/keycloak/client/registration/Auth.java
@@ -5,6 +5,7 @@ import org.apache.http.HttpRequest;
 import org.keycloak.common.util.Base64;
 import org.keycloak.representations.idm.ClientInitialAccessPresentation;
 import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.oidc.OIDCClientRepresentation;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -21,11 +22,14 @@ public abstract class Auth {
         return new BearerTokenAuth(initialAccess.getToken());
     }
 
-
     public static Auth token(ClientRepresentation client) {
         return new BearerTokenAuth(client.getRegistrationAccessToken());
     }
 
+    public static Auth token(OIDCClientRepresentation client) {
+        return new BearerTokenAuth(client.getRegistrationAccessToken());
+    }
+
     public static Auth client(String clientId, String clientSecret) {
         return new BasicAuth(clientId, clientSecret);
     }
diff --git a/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRegistration.java b/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRegistration.java
index 2b1a991..f2215f1 100644
--- a/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRegistration.java
+++ b/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRegistration.java
@@ -3,8 +3,10 @@ package org.keycloak.client.registration;
 import org.apache.http.client.HttpClient;
 import org.apache.http.impl.client.HttpClients;
 import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
 import org.keycloak.representations.adapters.config.AdapterConfig;
 import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.oidc.OIDCClientRepresentation;
 import org.keycloak.util.JsonSerialization;
 
 import java.io.IOException;
@@ -18,10 +20,17 @@ public class ClientRegistration {
     public static final ObjectMapper outputMapper = new ObjectMapper();
     static {
         outputMapper.getSerializationConfig().addMixInAnnotations(ClientRepresentation.class, ClientRepresentationMixIn.class);
+        outputMapper.getSerializationConfig().addMixInAnnotations(OIDCClientRepresentation.class, OIDCClientRepresentationMixIn.class);
+        outputMapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
     }
 
+    private final String JSON = "application/json";
+    private final String XML = "application/xml";
+
     private final String DEFAULT = "default";
     private final String INSTALLATION = "install";
+    private final String OIDC = "openid-connect";
+    private final String SAML = "saml2-entity-descriptor";
 
     private HttpUtil httpUtil;
 
@@ -47,23 +56,23 @@ public class ClientRegistration {
 
     public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException {
         String content = serialize(client);
-        InputStream resultStream = httpUtil.doPost(content, DEFAULT);
+        InputStream resultStream = httpUtil.doPost(content, JSON, JSON, DEFAULT);
         return deserialize(resultStream, ClientRepresentation.class);
     }
 
     public ClientRepresentation get(String clientId) throws ClientRegistrationException {
-        InputStream resultStream = httpUtil.doGet(DEFAULT, clientId);
+        InputStream resultStream = httpUtil.doGet(JSON, DEFAULT, clientId);
         return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
     }
 
     public AdapterConfig getAdapterConfig(String clientId) throws ClientRegistrationException {
-        InputStream resultStream = httpUtil.doGet(INSTALLATION, clientId);
+        InputStream resultStream = httpUtil.doGet(JSON, INSTALLATION, clientId);
         return resultStream != null ? deserialize(resultStream, AdapterConfig.class) : null;
     }
 
     public ClientRepresentation update(ClientRepresentation client) throws ClientRegistrationException {
         String content = serialize(client);
-        InputStream resultStream = httpUtil.doPut(content, DEFAULT, client.getClientId());
+        InputStream resultStream = httpUtil.doPut(content, JSON, JSON, DEFAULT, client.getClientId());
         return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
     }
 
@@ -75,10 +84,17 @@ public class ClientRegistration {
         httpUtil.doDelete(DEFAULT, clientId);
     }
 
-    public static String serialize(ClientRepresentation client) throws ClientRegistrationException {
-        try {
+    public OIDCClientRegistration oidc() {
+        return new OIDCClientRegistration();
+    }
 
-            return outputMapper.writeValueAsString(client);
+    public SAMLClientRegistration saml() {
+        return new SAMLClientRegistration();
+    }
+
+    public static String serialize(Object obj) throws ClientRegistrationException {
+        try {
+            return outputMapper.writeValueAsString(obj);
         } catch (IOException e) {
             throw new ClientRegistrationException("Failed to write json object", e);
         }
@@ -92,6 +108,44 @@ public class ClientRegistration {
         }
     }
 
+    public class OIDCClientRegistration {
+
+        public OIDCClientRepresentation create(OIDCClientRepresentation client) throws ClientRegistrationException {
+            String content = serialize(client);
+            InputStream resultStream = httpUtil.doPost(content, JSON, JSON, OIDC);
+            return deserialize(resultStream, OIDCClientRepresentation.class);
+        }
+
+        public OIDCClientRepresentation get(String clientId) throws ClientRegistrationException {
+            InputStream resultStream = httpUtil.doGet(JSON, OIDC, clientId);
+            return resultStream != null ? deserialize(resultStream, OIDCClientRepresentation.class) : null;
+        }
+
+        public OIDCClientRepresentation update(OIDCClientRepresentation client) throws ClientRegistrationException {
+            String content = serialize(client);
+            InputStream resultStream = httpUtil.doPut(content, JSON, JSON, OIDC, client.getClientId());
+            return resultStream != null ? deserialize(resultStream, OIDCClientRepresentation.class) : null;
+        }
+
+        public void delete(OIDCClientRepresentation client) throws ClientRegistrationException {
+            delete(client.getClientId());
+        }
+
+        public void delete(String clientId) throws ClientRegistrationException {
+            httpUtil.doDelete(OIDC, clientId);
+        }
+
+    }
+
+    public class SAMLClientRegistration {
+
+        public ClientRepresentation create(String entityDescriptor) throws ClientRegistrationException {
+            InputStream resultStream = httpUtil.doPost(entityDescriptor, XML, JSON, SAML);
+            return deserialize(resultStream, ClientRepresentation.class);
+        }
+
+    }
+
     public static class ClientRegistrationBuilder {
 
         private String url;
diff --git a/client-registration/api/src/main/java/org/keycloak/client/registration/HttpUtil.java b/client-registration/api/src/main/java/org/keycloak/client/registration/HttpUtil.java
index 6444749..4d44710 100644
--- a/client-registration/api/src/main/java/org/keycloak/client/registration/HttpUtil.java
+++ b/client-registration/api/src/main/java/org/keycloak/client/registration/HttpUtil.java
@@ -33,12 +33,12 @@ class HttpUtil {
         this.auth = auth;
     }
 
-    InputStream doPost(String content, String... path) throws ClientRegistrationException {
+    InputStream doPost(String content, String contentType, String acceptType, String... path) throws ClientRegistrationException {
         try {
             HttpPost request = new HttpPost(getUrl(baseUri, path));
 
-            request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
-            request.setHeader(HttpHeaders.ACCEPT, "application/json");
+            request.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
+            request.setHeader(HttpHeaders.ACCEPT, acceptType);
             request.setEntity(new StringEntity(content));
 
             addAuth(request);
@@ -60,11 +60,11 @@ class HttpUtil {
         }
     }
 
-    InputStream doGet(String... path) throws ClientRegistrationException {
+    InputStream doGet(String acceptType, String... path) throws ClientRegistrationException {
         try {
             HttpGet request = new HttpGet(getUrl(baseUri, path));
 
-            request.setHeader(HttpHeaders.ACCEPT, "application/json");
+            request.setHeader(HttpHeaders.ACCEPT, acceptType);
 
             addAuth(request);
 
@@ -90,12 +90,12 @@ class HttpUtil {
         }
     }
 
-    InputStream doPut(String content, String... path) throws ClientRegistrationException {
+    InputStream doPut(String content, String contentType, String acceptType, String... path) throws ClientRegistrationException {
         try {
             HttpPut request = new HttpPut(getUrl(baseUri, path));
 
-            request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
-            request.setHeader(HttpHeaders.ACCEPT, "application/json");
+            request.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
+            request.setHeader(HttpHeaders.ACCEPT, acceptType);
             request.setEntity(new StringEntity(content));
 
             addAuth(request);
@@ -134,7 +134,7 @@ class HttpUtil {
                 response.getEntity().getContent().close();
             }
 
-            if (response.getStatusLine().getStatusCode() != 200) {
+            if (response.getStatusLine().getStatusCode() != 204) {
                 throw new HttpErrorException(response.getStatusLine());
             }
         } catch (IOException e) {
diff --git a/client-registration/api/src/main/java/org/keycloak/client/registration/OIDCClientRepresentationMixIn.java b/client-registration/api/src/main/java/org/keycloak/client/registration/OIDCClientRepresentationMixIn.java
new file mode 100644
index 0000000..b0dfed6
--- /dev/null
+++ b/client-registration/api/src/main/java/org/keycloak/client/registration/OIDCClientRepresentationMixIn.java
@@ -0,0 +1,22 @@
+package org.keycloak.client.registration;
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+abstract class OIDCClientRepresentationMixIn {
+
+    @JsonIgnore
+    private Integer client_id_issued_at;
+
+    @JsonIgnore
+    private Integer client_secret_expires_at;
+
+    @JsonIgnore
+    private String registration_client_uri;
+
+    @JsonIgnore
+    private String registration_access_token;
+
+}
diff --git a/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java
index f0b0857..e76648b 100644
--- a/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java
+++ b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java
@@ -57,11 +57,15 @@ public class ClientRegistrationCLI {
                 .create();
 
         aeshConsole.start();
+
+
 /*
         if (args.length > 0) {
             CommandContainer command = registry.getCommand(args[0], null);
             ParserGenerator.parseAndPopulate(command, args[0], Arrays.copyOfRange(args, 1, args.length));
         }*/
+
+        //commandInvocation.getCommandRegistry().getAllCommandNames()
     }
 
 }
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
index db8b8d0..c81b062 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
@@ -73,5 +73,7 @@
             <column name="REGISTRATION_TOKEN" type="VARCHAR(255)"/>
         </addColumn>
 
+        <modifyDataType tableName="REALM" columnName="PASSWORD_POLICY" newDataType="VARCHAR(2550)"/>
+
     </changeSet>
 </databaseChangeLog>
\ No newline at end of file
diff --git a/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java
index 1e57dbc..6ad9715 100755
--- a/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java
@@ -29,7 +29,7 @@ public class CredentialRepresentation {
     private Integer period;
 
     // only used when updating a credential.  Might set required action
-    protected boolean temporary;
+    protected Boolean temporary;
 
     public String getType() {
         return type;
@@ -79,11 +79,11 @@ public class CredentialRepresentation {
         this.hashIterations = hashIterations;
     }
 
-    public boolean isTemporary() {
+    public Boolean isTemporary() {
         return temporary;
     }
 
-    public void setTemporary(boolean temporary) {
+    public void setTemporary(Boolean temporary) {
         this.temporary = temporary;
     }
 
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index 00785cf..9b76490 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -84,7 +84,6 @@ public class RealmRepresentation {
     private List<IdentityProviderRepresentation> identityProviders;
     private List<IdentityProviderMapperRepresentation> identityProviderMappers;
     private List<ProtocolMapperRepresentation> protocolMappers;
-    private Boolean identityFederationEnabled;
     protected Boolean internationalizationEnabled;
     protected Set<String> supportedLocales;
     protected String defaultLocale;
@@ -613,10 +612,6 @@ public class RealmRepresentation {
         identityProviders.add(identityProviderRepresentation);
     }
 
-    public boolean isIdentityFederationEnabled() {
-        return identityProviders != null && !identityProviders.isEmpty();
-    }
-
     public List<ProtocolMapperRepresentation> getProtocolMappers() {
         return protocolMappers;
     }
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
index 8635014..0990a4e 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
@@ -17,9 +17,9 @@ public class UserRepresentation {
     protected String id;
     protected Long createdTimestamp;
     protected String username;
-    protected boolean enabled;
-    protected boolean totp;
-    protected boolean emailVerified;
+    protected Boolean enabled;
+    protected Boolean totp;
+    protected Boolean emailVerified;
     protected String firstName;
     protected String lastName;
     protected String email;
@@ -98,27 +98,27 @@ public class UserRepresentation {
         this.username = username;
     }
 
-    public boolean isEnabled() {
+    public Boolean isEnabled() {
         return enabled;
     }
 
-    public void setEnabled(boolean enabled) {
+    public void setEnabled(Boolean enabled) {
         this.enabled = enabled;
     }
 
-    public boolean isTotp() {
+    public Boolean isTotp() {
         return totp;
     }
 
-    public void setTotp(boolean totp) {
+    public void setTotp(Boolean totp) {
         this.totp = totp;
     }
 
-    public boolean isEmailVerified() {
+    public Boolean isEmailVerified() {
         return emailVerified;
     }
 
-    public void setEmailVerified(boolean emailVerified) {
+    public void setEmailVerified(Boolean emailVerified) {
         this.emailVerified = emailVerified;
     }
 
diff --git a/core/src/main/java/org/keycloak/util/JsonSerialization.java b/core/src/main/java/org/keycloak/util/JsonSerialization.java
index a1a93ba..19df33f 100755
--- a/core/src/main/java/org/keycloak/util/JsonSerialization.java
+++ b/core/src/main/java/org/keycloak/util/JsonSerialization.java
@@ -3,6 +3,7 @@ package org.keycloak.util;
 import org.codehaus.jackson.map.ObjectMapper;
 import org.codehaus.jackson.map.SerializationConfig;
 import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.codehaus.jackson.type.TypeReference;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -27,7 +28,10 @@ public class JsonSerialization {
 
     public static void writeValueToStream(OutputStream os, Object obj) throws IOException {
         mapper.writeValue(os, obj);
+    }
 
+    public static void writeValuePrettyToStream(OutputStream os, Object obj) throws IOException {
+        prettyMapper.writeValue(os, obj);
     }
 
     public static String writeValueAsPrettyString(Object obj) throws IOException {
@@ -53,6 +57,10 @@ public class JsonSerialization {
         return readValue(bytes, type, false);
     }
 
+    public static <T> T readValue(InputStream bytes, TypeReference<T> type) throws IOException {
+        return mapper.readValue(bytes, type);
+    }
+
     public static <T> T readValue(InputStream bytes, Class<T> type, boolean replaceSystemProperties) throws IOException {
         if (replaceSystemProperties) {
             return sysPropertiesAwareMapper.readValue(bytes, type);
diff --git a/distribution/feature-packs/server-feature-pack/pom.xml b/distribution/feature-packs/server-feature-pack/pom.xml
index 0f5dd37..6f05056 100644
--- a/distribution/feature-packs/server-feature-pack/pom.xml
+++ b/distribution/feature-packs/server-feature-pack/pom.xml
@@ -38,17 +38,25 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-wildfly-adduser</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-wildfly-extensions</artifactId>
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-wf9-server-subsystem</artifactId>
+            <artifactId>keycloak-wildfly-server-subsystem</artifactId>
         </dependency>
         <dependency>
             <groupId>org.wildfly</groupId>
             <artifactId>wildfly-feature-pack</artifactId>
             <type>zip</type>
         </dependency>
+        <dependency>
+            <groupId>org.jboss.aesh</groupId>
+            <artifactId>aesh</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/add-user.bat b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/add-user.bat
new file mode 100644
index 0000000..dca1136
--- /dev/null
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/add-user.bat
@@ -0,0 +1,73 @@
+@echo off
+rem -------------------------------------------------------------------------
+rem Add User script for Windows
+rem -------------------------------------------------------------------------
+rem
+rem A simple utility for adding new users to the properties file used
+rem for domain management authentication out of the box.
+
+rem $Id$
+
+@if not "%ECHO%" == ""  echo %ECHO%
+@if "%OS%" == "Windows_NT" setlocal
+
+if "%OS%" == "Windows_NT" (
+  set "DIRNAME=%~dp0%"
+) else (
+  set DIRNAME=.\
+)
+
+pushd "%DIRNAME%.."
+set "RESOLVED_JBOSS_HOME=%CD%"
+popd
+
+if "x%JBOSS_HOME%" == "x" (
+  set "JBOSS_HOME=%RESOLVED_JBOSS_HOME%"
+)
+
+pushd "%JBOSS_HOME%"
+set "SANITIZED_JBOSS_HOME=%CD%"
+popd
+
+if /i "%RESOLVED_JBOSS_HOME%" NEQ "%SANITIZED_JBOSS_HOME%" (
+   echo.
+   echo   WARNING: The JBOSS_HOME ^("%SANITIZED_JBOSS_HOME%"^) that this script uses points to a different installation than the one that this script resides in ^("%RESOLVED_JBOSS_HOME%"^). Unpredictable results may occur.
+   echo.
+   echo       JBOSS_HOME: "%JBOSS_HOME%"
+   echo.
+)
+
+rem Setup JBoss specific properties
+if "x%JAVA_HOME%" == "x" (
+  set  JAVA=java
+  echo JAVA_HOME is not set. Unexpected results may occur.
+  echo Set JAVA_HOME to the directory of your local JDK to avoid this message.
+) else (
+  set "JAVA=%JAVA_HOME%\bin\java"
+)
+
+rem Find jboss-modules.jar, or we can't continue
+if exist "%JBOSS_HOME%\jboss-modules.jar" (
+    set "RUNJAR=%JBOSS_HOME%\jboss-modules.jar"
+) else (
+  echo Could not locate "%JBOSS_HOME%\jboss-modules.jar".
+  echo Please check that you are in the bin directory when running this script.
+  goto END
+)
+
+rem Set default module root paths
+if "x%JBOSS_MODULEPATH%" == "x" (
+  set "JBOSS_MODULEPATH=%JBOSS_HOME%\modules"
+)
+
+rem Uncomment to override standalone and domain user location
+rem set "JAVA_OPTS=%JAVA_OPTS% -Djboss.server.config.user.dir=..\standalone\configuration -Djboss.domain.config.user.dir=..\domain\configuration"
+
+"%JAVA%" %JAVA_OPTS% ^
+    -jar "%JBOSS_HOME%\jboss-modules.jar" ^
+    -mp "%JBOSS_MODULEPATH%" ^
+     org.keycloak.keycloak-wildfly-adduser ^
+     %*
+
+:END
+if "x%NOPAUSE%" == "x" pause
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/add-user.sh b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/add-user.sh
new file mode 100755
index 0000000..1f6dfff
--- /dev/null
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/add-user.sh
@@ -0,0 +1,72 @@
+#!/bin/sh
+
+# Add User Utility
+#
+# A simple utility for adding new users to the properties file used
+# for domain management authentication out of the box.
+#
+
+DIRNAME=`dirname "$0"`
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false;
+if  [ `uname|grep -i CYGWIN` ]; then
+    cygwin=true;
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+    [ -n "$JBOSS_HOME" ] &&
+        JBOSS_HOME=`cygpath --unix "$JBOSS_HOME"`
+    [ -n "$JAVA_HOME" ] &&
+        JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+    [ -n "$JAVAC_JAR" ] &&
+        JAVAC_JAR=`cygpath --unix "$JAVAC_JAR"`
+fi
+
+# Setup JBOSS_HOME
+RESOLVED_JBOSS_HOME=`cd "$DIRNAME/.."; pwd`
+if [ "x$JBOSS_HOME" = "x" ]; then
+    # get the full path (without any relative bits)
+    JBOSS_HOME=$RESOLVED_JBOSS_HOME
+else
+ SANITIZED_JBOSS_HOME=`cd "$JBOSS_HOME"; pwd`
+ if [ "$RESOLVED_JBOSS_HOME" != "$SANITIZED_JBOSS_HOME" ]; then
+   echo "WARNING: The JBOSS_HOME ($SANITIZED_JBOSS_HOME) that this script uses points to a different installation than the one that this script resides in ($RESOLVED_JBOSS_HOME). Unpredictable results may occur."
+   echo ""
+ fi
+fi
+export JBOSS_HOME
+
+# Setup the JVM
+if [ "x$JAVA" = "x" ]; then
+    if [ "x$JAVA_HOME" != "x" ]; then
+        JAVA="$JAVA_HOME/bin/java"
+    else
+        JAVA="java"
+    fi
+fi
+
+if [ "x$JBOSS_MODULEPATH" = "x" ]; then
+    JBOSS_MODULEPATH="$JBOSS_HOME/modules"
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+    JBOSS_HOME=`cygpath --path --windows "$JBOSS_HOME"`
+    JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+    JBOSS_MODULEPATH=`cygpath --path --windows "$JBOSS_MODULEPATH"`
+fi
+
+# Sample JPDA settings for remote socket debugging
+#JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,address=8787,server=y,suspend=y"
+# Uncomment to override standalone and domain user location  
+#JAVA_OPTS="$JAVA_OPTS -Djboss.server.config.user.dir=../standalone/configuration -Djboss.domain.config.user.dir=../domain/configuration"
+
+JAVA_OPTS="$JAVA_OPTS"
+
+eval \"$JAVA\" $JAVA_OPTS \
+         -jar \""$JBOSS_HOME"/jboss-modules.jar\" \
+         -mp \""${JBOSS_MODULEPATH}"\" \
+         org.keycloak.keycloak-wildfly-adduser \
+         '"$@"'
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/jboss/aesh/0.65/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/jboss/aesh/0.65/module.xml
new file mode 100644
index 0000000..4166dbd
--- /dev/null
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/jboss/aesh/0.65/module.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2010, Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags. See the copyright.txt file in the
+  ~ distribution for a full listing of individual contributors.
+  ~
+  ~ This is free software; you can redistribute it and/or modify it
+  ~ under the terms of the GNU Lesser General Public License as
+  ~ published by the Free Software Foundation; either version 2.1 of
+  ~ the License, or (at your option) any later version.
+  ~
+  ~ This software is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  ~ Lesser General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU Lesser General Public
+  ~ License along with this software; if not, write to the Free
+  ~ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  ~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  -->
+
+<module xmlns="urn:jboss:module:1.3" name="org.jboss.aesh" slot="0.65">
+    <properties>
+        <property name="jboss.api" value="private"/>
+    </properties>
+
+    <resources>
+        <artifact name="${org.jboss.aesh:aesh}"/>
+    </resources>
+
+    <dependencies>
+        <module name="org.fusesource.jansi" />
+    </dependencies>
+</module>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/module.xml
index d6e7d81..8002aa2 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/module.xml
@@ -29,6 +29,6 @@
     </resources>
 
     <dependencies>
-        <module name="org.keycloak.keycloak-wf9-server-subsystem" services="export" export="true"/>
+        <module name="org.keycloak.keycloak-wildfly-server-subsystem" services="export" export="true"/>
     </dependencies>
 </module>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-adduser/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-adduser/main/module.xml
new file mode 100755
index 0000000..d27499b
--- /dev/null
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-adduser/main/module.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-wildfly-adduser">
+    <main-class name="org.keycloak.wildfly.adduser.AddUser"/>
+    <resources>
+        <artifact name="${org.keycloak:keycloak-wildfly-adduser}"/>
+    </resources>
+    <dependencies>
+        <module name="org.keycloak.keycloak-common"/>
+        <module name="org.keycloak.keycloak-core"/>
+        <module name="org.keycloak.keycloak-model-api"/>
+        <module name="org.jboss.aesh" slot="0.65"/>
+        <module name="org.jboss.as.domain-management"/>
+        <module name="org.codehaus.jackson.jackson-core-asl"/>
+    </dependencies>
+</module>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/build.xml b/distribution/server-overlay/eap6/eap6-server-modules/build.xml
index 9f60836..e8086dc 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/build.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/build.xml
@@ -274,8 +274,8 @@
 
         <!-- subsystems -->
 
-        <module-def name="org.keycloak.keycloak-as7-server-subsystem">
-            <maven-resource group="org.keycloak" artifact="keycloak-as7-server-subsystem"/>
+        <module-def name="org.keycloak.keycloak-eap6-server-subsystem">
+            <maven-resource group="org.keycloak" artifact="keycloak-eap6-server-subsystem"/>
         </module-def>
 
         <module-def name="org.keycloak.keycloak-server-subsystem"/>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/pom.xml b/distribution/server-overlay/eap6/eap6-server-modules/pom.xml
index 91b2969..b3225fd 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/pom.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/pom.xml
@@ -32,7 +32,7 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-as7-server-subsystem</artifactId>
+            <artifactId>keycloak-eap6-server-subsystem</artifactId>
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-server-subsystem/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-server-subsystem/main/module.xml
index 90939b0..4829258 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-server-subsystem/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-server-subsystem/main/module.xml
@@ -30,6 +30,6 @@
     </resources>
 
     <dependencies>
-        <module name="org.keycloak.keycloak-as7-server-subsystem" services="export" export="true"/>
+        <module name="org.keycloak.keycloak-eap6-server-subsystem" services="export" export="true"/>
     </dependencies>
 </module>
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..5078f10 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">
@@ -37,7 +38,6 @@
                 <!ENTITY UserFederation SYSTEM "modules/user-federation.xml">
                 <!ENTITY Kerberos SYSTEM "modules/kerberos.xml">
                 <!ENTITY ExportImport SYSTEM "modules/export-import.xml">
-                <!ENTITY AdminRecovery SYSTEM "modules/admin-recovery.xml">
                 <!ENTITY ServerCache SYSTEM "modules/cache.xml">
                 <!ENTITY SecurityVulnerabilities SYSTEM "modules/security-vulnerabilities.xml">
                 <!ENTITY Clustering SYSTEM "modules/clustering.xml">
@@ -115,6 +115,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/client-registration.xml b/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml
index 0070cc0..fab7119 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml
@@ -10,15 +10,15 @@
     <para>
         The Client Registration Service provides built-in support for Keycloak Client Representations, OpenID Connect
         Client Meta Data and SAML Entity Descriptors. It's also possible to plugin custom client registration providers
-        if required. The Client Registration Service endpoint is <literal>&lt;KEYCLOAK URL&gt;/clients/&lt;provider&gt;</literal>.
+        if required. The Client Registration Service endpoint is <literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/clients/&lt;provider&gt;</literal>.
     </para>
     <para>
         The built-in supported <literal>providers</literal> are:
         <itemizedlist>
             <listitem><literal>default</literal> Keycloak Representations</listitem>
             <listitem><literal>install</literal> Keycloak Adapter Configuration</listitem>
-            <!--<listitem><literal>openid-connect</literal> OpenID Connect Dynamic Client Registration</listitem>-->
-            <!--<listitem><literal>saml-ed</literal> SAML Entity Descriptors</listitem>-->
+            <listitem><literal>openid-connect</literal> OpenID Connect Dynamic Client Registration</listitem>
+            <listitem><literal>saml2-entity-descriptor</literal> SAML Entity Descriptors</listitem>
         </itemizedlist>
         The following sections will describe how to use the different providers.
     </para>
@@ -106,30 +106,30 @@ Authorization: bearer eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiJmMjJmNzQyYy04ZjNlLTQ2M....
         </para>
         <para>
             To create a client create a Client Representation (JSON) then do a HTTP POST to:
-            <literal>&lt;KEYCLOAK URL&gt;/clients/&lt;provider&gt;/default</literal>. It will return a Client Representation
+            <literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/clients/&lt;provider&gt;/default</literal>. It will return a Client Representation
             that also includes the registration access token. You should save the registration access token somewhere
             if you want to retrieve the config, update or delete the client later.
         </para>
         <para>
             To retrieve the Client Representation then do a HTTP GET to:
-            <literal>&lt;KEYCLOAK URL&gt;/clients/&lt;provider&gt;/default/&lt;client id&gt;</literal>. It will also
+            <literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;clients/&lt;provider&gt;/default/&lt;client id&gt;</literal>. It will also
             return a new registration access token.
         </para>
         <para>
             To update the Client Representation then do a HTTP PUT to with the updated Client Representation to:
-            <literal>&lt;KEYCLOAK URL&gt;/clients/&lt;provider&gt;/default/&lt;client id&gt;</literal>. It will also
+            <literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/clients/&lt;provider&gt;/default/&lt;client id&gt;</literal>. It will also
             return a new registration access token.
         </para>
         <para>
             To delete the Client Representation then do a HTTP DELETE to:
-            <literal>&lt;KEYCLOAK URL&gt;/clients/&lt;provider&gt;/default/&lt;client id&gt;</literal>
+            <literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/clients/&lt;provider&gt;/default/&lt;client id&gt;</literal>
         </para>
     </section>
 
     <section>
         <title>Keycloak Adapter Configuration</title>
         <para>
-            The <default>installation</default> client registration provider can be used to retrieve the adapter configuration
+            The <literal>installation</literal> client registration provider can be used to retrieve the adapter configuration
             for a client. In addition to token authentication you can also authenticate with client credentials using
             HTTP basic authentication. To do this include the following header in the request:
 <programlisting><![CDATA[
@@ -138,7 +138,7 @@ Authorization: basic BASE64(client-id + ':' + client-secret)
         </para>
         <para>
             To retrieve the Adapter Configuration then do a HTTP GET to:
-            <literal>&lt;KEYCLOAK URL&gt;/clients/&lt;provider&gt;/installation/&lt;client id&gt;</literal>
+            <literal>&lt;KEYCLOAK URL&gt;//realms/&lt;realm&gt;clients/&lt;provider&gt;/installation/&lt;client id&gt;</literal>
         </para>
         <para>
             No authentication is required for public clients. This means that for the JavaScript adapter you can
@@ -146,23 +146,36 @@ Authorization: basic BASE64(client-id + ':' + client-secret)
         </para>
     </section>
 
-    <!--
     <section>
         <title>OpenID Connect Dynamic Client Registration</title>
         <para>
-            TODO
+            Keycloak implements <ulink url="https://openid.net/specs/openid-connect-registration-1_0.html">OpenID Connect Dynamic Client Registration</ulink>,
+            which extends <ulink url="https://tools.ietf.org/html/rfc7591">OAuth 2.0 Dynamic Client Registration Protocol</ulink> and
+            <ulink url="https://tools.ietf.org/html/rfc7592">OAuth 2.0 Dynamic Client Registration Management Protocol</ulink>.
+        </para>
+        <para>
+            The endpoint to use these specifications to register clients in Keycloak is:
+            <literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/clients/&lt;provider&gt;/oidc[/&lt;client id&gt;]</literal>.
+        </para>
+        <para>
+            This endpoints can also be found in the OpenID Connect Discovery endpoint for the realm:
+            <literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/.well-known/openid-configuration</literal>.
         </para>
     </section>
-    -->
 
-    <!--
     <section>
         <title>SAML Entity Descriptors</title>
         <para>
-            TODO
+            The SAML Entity Descriptor endpoint only supports using SAML v2 Entity Descriptors to create clients. It
+            doesn't support retrieving, updating or deleting clients. For those operations the Keycloak representation
+            endpoints should be used. When creating a client a Keycloak Client Representation is returned with details
+            about the created client, including a registration access token.
+        </para>
+        <para>
+            To create a client do a HTTP POST with the SAML Entity Descriptor to:
+            <literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/clients/&lt;provider&gt;/saml2-entity-descriptor</literal>.
         </para>
     </section>
-    -->
 
     <section>
         <title>Client Registration Java API</title>
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/auth-server-docs/reference/en/en-US/modules/server-installation.xml b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
index 78d9a4b..01ad7e6 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
@@ -128,6 +128,25 @@ cd &lt;WILDFLY_HOME&gt;/bin
             </itemizedlist>
         </para>
         <section>
+            <title>Admin User</title>
+            <para>
+                To access the admin console you need an account to login. Currently, there's a default account added
+                with the username <literal>admin</literal> and password <literal>admin</literal>. You will be required
+                to change the password on first login. We are planning on removing the built-in account soon and will
+                instead have an initial step to create the user.
+            </para>
+            <para>
+                You can also create a user with the <literal>add-user</literal> script found in <literal>bin</literal>.
+                This script will create a temporary file with the details of the user, which are imported at startup.
+                To add a user with this script run:
+<programlisting><![CDATA[
+bin/add-user.[sh|bat] -r master -u <username> -p <password>
+]]></programlisting>
+                Then restart the server.
+            </para>
+        </section>
+
+        <section>
             <title>Relational Database Configuration</title>
             <para>
                 You might want to use a better relational database for Keycloak like PostgreSQL or MySQL.  You might also
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/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java
index 020d349..0a253d1 100644
--- a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java
+++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java
@@ -153,6 +153,10 @@ public class ExtendingThemeManager implements ThemeProvider {
 
         private List<Theme> themes;
 
+        private Properties properties;
+
+        private ConcurrentHashMap<String, ConcurrentHashMap<Locale, Properties>> messages = new ConcurrentHashMap<>();
+
         public ExtendingTheme(List<Theme> themes) {
             this.themes = themes;
         }
@@ -229,28 +233,41 @@ public class ExtendingThemeManager implements ThemeProvider {
 
         @Override
         public Properties getMessages(String baseBundlename, Locale locale) throws IOException {
-            Properties messages = new Properties();
-            ListIterator<Theme> itr = themes.listIterator(themes.size());
-            while (itr.hasPrevious()) {
-                Properties m = itr.previous().getMessages(baseBundlename, locale);
-                if (m != null) {
-                    messages.putAll(m);
+            if (messages.get(baseBundlename) == null || messages.get(baseBundlename).get(locale) == null) {
+                Properties messages = new Properties();
+                ListIterator<Theme> itr = themes.listIterator(themes.size());
+                while (itr.hasPrevious()) {
+                    Properties m = itr.previous().getMessages(baseBundlename, locale);
+                    if (m != null) {
+                        messages.putAll(m);
+                    }
                 }
+
+                this.messages.putIfAbsent(baseBundlename, new ConcurrentHashMap<Locale, Properties>());
+                this.messages.get(baseBundlename).putIfAbsent(locale, messages);
+
+                return messages;
+            } else {
+                return messages.get(baseBundlename).get(locale);
             }
-            return messages;
         }
 
         @Override
         public Properties getProperties() throws IOException {
-            Properties properties = new Properties();
-            ListIterator<Theme> itr = themes.listIterator(themes.size());
-            while (itr.hasPrevious()) {
-                Properties p = itr.previous().getProperties();
-                if (p != null) {
-                    properties.putAll(p);
+            if (properties == null) {
+                Properties properties = new Properties();
+                ListIterator<Theme> itr = themes.listIterator(themes.size());
+                while (itr.hasPrevious()) {
+                    Properties p = itr.previous().getProperties();
+                    if (p != null) {
+                        properties.putAll(p);
+                    }
                 }
+                this.properties = properties;
+                return properties;
+            } else {
+                return properties;
             }
-            return properties;
         }
 
     }
diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_es.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_es.properties
index 40743f8..2b0914c 100644
--- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_es.properties
+++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_es.properties
@@ -96,7 +96,7 @@ revoke=Revocar permiso
 configureAuthenticators=Autenticadores configurados
 mobile=M\u00F3vil
 totpStep1=Instala <a href=\"https://fedorahosted.org/freeotp/\" target=\"_blank\">FreeOTP</a> o Google Authenticator en tu tel\u00E9fono m\u00F3vil. Ambas aplicaciones est\u00E1n disponibles en <a href=\"https://play.google.com\">Google Play</a> y en la App Store de Apple.
-totpStep2=Abre la aplicacvi\u00F3n y escanea el c\u00F3digo o introduce la clave.
+totpStep2=Abre la aplicaci\u00F3n y escanea el c\u00F3digo o introduce la clave.
 totpStep3=Introduce el c\u00F3digo \u00FAnico que te muestra la aplicaci\u00F3n de autenticaci\u00F3n y haz clic en Enviar para finalizar la configuraci\u00F3n
 
 missingUsernameMessage=Por favor indica tu usuario.
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 09192ae..e274ca7 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -271,7 +271,7 @@ client-certificate-import=Client Certificate Import
 import-client-certificate=Import Client Certificate
 jwt-import.key-alias.tooltip=Archive alias for your certificate.
 secret=Secret
-regenerate-secret=Regenerate Secretsecret=Secret
+regenerate-secret=Regenerate Secret
 registrationAccessToken=Registration access token
 registrationAccessToken.regenerate=Regenerate registration access token
 registrationAccessToken.tooltip=The registration access token provides access for clients to the client registration service.
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties
index 170720a..5a353ab 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties
@@ -17,7 +17,7 @@ false=No
 # Realm settings
 realm-detail.enabled.tooltip=Los usuarios y clientes solo pueden acceder a un dominio si est\u00E1 habilitado
 registrationAllowed=Registro de usuario
-registrationAllowed.tooltip=Habilitar/deshabilitar la p\u00E1gina de registro. Un enlace para el registro se motrar\u00E1 tambi\u00E9n en la p\u00E1gina de inicio de sesi\u00F3n.
+registrationAllowed.tooltip=Habilitar/deshabilitar la p\u00E1gina de registro. Un enlace para el registro se mostrar\u00E1 tambi\u00E9n en la p\u00E1gina de inicio de sesi\u00F3n.
 registrationEmailAsUsername=Email como nombre de usuario
 registrationEmailAsUsername.tooltip=Si est\u00E1 habilitado el nombre de usuario queda oculto del formulario de registro y el email se usa como nombre de usuario para los nuevos usuarios.
 editUsernameAllowed=Editar nombre de usuario
@@ -32,7 +32,7 @@ sslRequired=Solicitar SSL
 sslRequired.option.all=todas las peticiones
 sslRequired.option.external=peticiones externas
 sslRequired.option.none=ninguna
-sslRequired.tooltip=\u00BFEs HTTP obligatorio? 'ninguna' significa que HTTPS no es obligatorio para ninguna direcic\u00F3n IP de cliente, 'peticiones externas' indica que localhost y las direcciones IP privadas pueden acceder sin HTTPS, 'todas las peticiones' significa que HTTPS es obligatorio para todas las direcciones IP.
+sslRequired.tooltip=\u00BFEs HTTP obligatorio? ''ninguna'' significa que HTTPS no es obligatorio para ninguna direcic\u00F3n IP de cliente, ''peticiones externas'' indica que localhost y las direcciones IP privadas pueden acceder sin HTTPS, ''todas las peticiones'' significa que HTTPS es obligatorio para todas las direcciones IP.
 publicKey=Clave p\u00FAblica
 gen-new-keys=Generar nuevas claves
 certificate=Certificado
@@ -51,7 +51,7 @@ password=Contrase\u00F1a
 login-password=Contrase\u00F1a
 login-theme=Tema de inicio de sesi\u00F3n
 select-one=Selecciona uno...
-login-theme.tooltip=Selecciona el tema para las p\u00E1gina de inicio de sesi\u00F3n, TOTP, permisos, registro y recordatorio de contrase\u00F1a.
+login-theme.tooltip=Selecciona el tema para las p\u00E1ginas de inicio de sesi\u00F3n, TOTP, permisos, registro y recordatorio de contrase\u00F1a.
 account-theme=Tema de cuenta
 account-theme.tooltip=Selecciona el tema para las p\u00E1ginas de gesti\u00F3n de la cuenta de usuario.
 admin-console-theme=Tema de consola de administraci\u00F3n
@@ -75,23 +75,23 @@ hours=Horas
 days=D\u00EDas
 sso-session-max=Tiempo m\u00E1ximo sesi\u00F3n SSO
 sso-session-idle.tooltip=Tiempo m\u00E1ximo que una sesi\u00F3n puede estar inactiva antes de que expire. Los tokens y sesiones de navegador son invalidadas cuando la sesi\u00F3n expira.
-sso-session-max.tooltip=Tiempo m\u00E1ximo antes de que una sesi\u00F3n expire. Los tokesn y sesiones de navegador son invalidados cuando una sesi\u00F3n expira.
+sso-session-max.tooltip=Tiempo m\u00E1ximo antes de que una sesi\u00F3n expire. Los tokens y sesiones de navegador son invalidados cuando una sesi\u00F3n expira.
 offline-session-idle=Inactividad de sesi\u00F3n sin conexi\u00F3n
 offline-session-idle.tooltip=Tiempo m\u00E1ximo inactivo de una sesi\u00F3n sin conexi\u00F3n antes de que expire. Necesitas usar un token sin conexi\u00F3n para refrescar al menos una vez dentro de este periodo, en otro caso la sesi\u00F3n sin conexi\u00F3n expirar\u00E1.
 access-token-lifespan=Duraci\u00F3n del token de acceso
-access-token-lifespan.tooltip=Tiempo m\u00E1ximo antes de que un token de acceso expire. Se recomiena que esta valor sea corto en relaci\u00F3n al tiempo m\u00E1ximo de SSO
+access-token-lifespan.tooltip=Tiempo m\u00E1ximo antes de que un token de acceso expire. Se recomienda que este valor sea corto en relaci\u00F3n al tiempo m\u00E1ximo de SSO
 client-login-timeout=Tiempo m\u00E1ximo de autenticaci\u00F3n
-client-login-timeout.tooltip=Tiempo m\u00E1ximo que un cliente tien para finalizar el protocolo de obtenci\u00F3n del token de acceso. Deber\u00EDa ser normalmente del orden de 1 minuto.
+client-login-timeout.tooltip=Tiempo m\u00E1ximo que un cliente tiene para finalizar el protocolo de obtenci\u00F3n del token de acceso. Deber\u00EDa ser normalmente del orden de 1 minuto.
 login-timeout=Tiempo m\u00E1ximo de desconexi\u00F3n
-login-timeout.tooltip=Tiempo m\u00E1xmo que un usuario tiene para completar el inicio de sesi\u00F3n. Se recomienda que sea relativamente alto. 30 minutos o m\u00E1s.
+login-timeout.tooltip=Tiempo m\u00E1ximo que un usuario tiene para completar el inicio de sesi\u00F3n. Se recomienda que sea relativamente alto. 30 minutos o m\u00E1s.
 login-action-timeout=Tiempo m\u00E1ximo de acci\u00F3n en el inicio de sesi\u00F3n
 login-action-timeout.tooltip=Tiempo m\u00E1ximo que un usuario tiene para completar acciones relacionadas con el inicio de sesi\u00F3n, como la actualizaci\u00F3n de contrase\u00F1a o configuraci\u00F3n de TOTP. Es recomendado que sea relativamente alto. 5 minutos o m\u00E1s.
 headers=Cabeceras
 brute-force-detection=Detecci\u00F3n de ataques por fuerza bruta
 x-frame-options=X-Frame-Options
-click-label-for-info=Haz clic en el enlace de la etiqueta para obtener m\u00E1s informaci\u00F3n. El valor por defecto evita que las p\u00E1ginas sean incluidaos desde iframes externos.
+click-label-for-info=Haz clic en el enlace de la etiqueta para obtener m\u00E1s informaci\u00F3n. El valor por defecto evita que las p\u00E1ginas sean incluidas desde iframes externos.
 content-sec-policy=Content-Security-Policy
-max-login-failures=N\u00FAmero m\u00E1ximo de fallos de inicios de sesi\u00F3n
+max-login-failures=N\u00FAmero m\u00E1ximo de fallos de inicio de sesi\u00F3n
 max-login-failures.tooltip=Indica cuantos fallos se permiten antes de que se dispare una espera.
 wait-increment=Incremento de espera
 wait-increment.tooltip=Cuando se ha alcanzado el umbral de fallo, \u00BFcuanto tiempo debe estar un usuario bloqueado?
@@ -107,7 +107,7 @@ realm-tab-login=Inicio de sesi\u00F3n
 realm-tab-keys=Claves
 realm-tab-email=Email
 realm-tab-themes=Temas
-realm-tab-cache=Cache
+realm-tab-cache=Cach\u00E9
 realm-tab-tokens=Tokens
 realm-tab-security-defenses=Defensas de seguridad
 realm-tab-general=General
@@ -123,11 +123,11 @@ not-before=No antes de
 not-before.tooltip=Revocar cualquier token emitido antes de esta fecha.
 set-to-now=Fijar a ahora
 push=Push
-push.tooltip=Para cada cliente que tiene una URL de administraci\u00F3n, notificarlos the las nuevas pol\u00EDticas de revocaci\u00F3n.
+push.tooltip=Para cada cliente que tiene una URL de administraci\u00F3n, notificarlos las nuevas pol\u00EDticas de revocaci\u00F3n.
 
 #Protocol Mapper
 usermodel.prop.label=Propiedad
-usermodel.prop.tooltip=Nombre del m\u00E9todo de propiedad in la interfaz UserModel. Por ejemplo, un valor de 'email' referenciar\u00EDa al m\u00E9todo UserModel.getEmail().
+usermodel.prop.tooltip=Nombre del m\u00E9todo de propiedad en la interfaz UserModel. Por ejemplo, un valor de ''email'' referenciar\u00EDa al m\u00E9todo UserModel.getEmail().
 usermodel.attr.label=Atributo de usuario
 usermodel.attr.tooltip=Nombre del atributo de usuario almacenado que es el nombre del atributo dentro del map UserModel.attribute.
 userSession.modelNote.label=Nota sesi\u00F3n usuario
@@ -137,7 +137,7 @@ multivalued.tooltip=Indica si el atributo soporta m\u00FAltiples valores. Si est
 selectRole.label=Selecciona rol
 selectRole.tooltip=Introduce el rol en la caja de texto de la izquierda, o haz clic en este bot\u00F3n para navegar y buscar el rol que quieres.
 tokenClaimName.label=Nombre de reclamo del token
-tokenClaimName.tooltip=Nombre del reclamo a insertar en el token. Puede ser un nombre completo como 'address.street'. En este caso, se crear\u00E1 un objeto JSON anidado.
+tokenClaimName.tooltip=Nombre del reclamo a insertar en el token. Puede ser un nombre completo como ''address.street''. En este caso, se crear\u00E1 un objeto JSON anidado.
 jsonType.label=Tipo JSON de reclamaci\u00F3n
 jsonType.tooltip=El tipo de JSON que deber\u00EDa ser usado para rellenar la petici\u00F3n de JSON en el token. long, int, boolean y String son valores v\u00E1lidos
 includeInIdToken.label=A\u00F1adir al token de ID
@@ -162,21 +162,21 @@ add-client=A\u00F1adir Cliente
 select-file=Selecciona archivo
 view-details=Ver detalles
 clear-import=Limpiar importaci\u00F3n
-client-id.tooltip=Indica el identificador (ID) referenciado en URIs y tokens. Por ejemplo 'my-client'
-client.name.tooltip=Indica el nombre visible del cliente. Por ejemplo 'My Client'. Tambi\u00E9n soporta claves para vallores localizados. Por ejemplo: ${my_client}
+client-id.tooltip=Indica el identificador (ID) referenciado en URIs y tokens. Por ejemplo ''my-client''
+client.name.tooltip=Indica el nombre visible del cliente. Por ejemplo ''My Client''. Tambi\u00E9n soporta claves para valores localizados. Por ejemplo: ${my_client}
 client.enabled.tooltip=Los clientes deshabilitados no pueden iniciar una identificaci\u00F3n u obtener c\u00F3digos de acceso.
 consent-required=Consentimiento necesario
 consent-required.tooltip=Si est\u00E1 habilitado, los usuarios tienen que consentir el acceso del cliente.
 direct-grants-only=Solo permisos directos
 direct-grants-only.tooltip=Cuando est\u00E1 habilitado, el cliente solo puede obtener permisos de la API REST.
 client-protocol=Protocolo del Cliente
-client-protocol.tooltip='OpenID connect' permite a los clientes verificar la identidad del usuario final basado en la autenticaci\u00F3n realizada por un servidor de autorizaci\u00F3n. 'SAML' habilita la autenticaci\u00F3n y autorizaci\u00F3n de escenarios basados en web incluyendo cross-domain y single sign-on (SSO) y utiliza tokdne de seguridad que contienen afirmaciones para pasar informaci\u00F3n.
+client-protocol.tooltip=''OpenID connect'' permite a los clientes verificar la identidad del usuario final basado en la autenticaci\u00F3n realizada por un servidor de autorizaci\u00F3n. ''SAML'' habilita la autenticaci\u00F3n y autorizaci\u00F3n de escenarios basados en web incluyendo cross-domain y single sign-on (SSO) y utiliza tokens de seguridad que contienen afirmaciones para pasar informaci\u00F3n.
 access-type=Tipo de acceso
-access-type.tooltip=Los clientes 'Confidential' necesitan un secreto para iniciar el protocolo de identificaci\u00F3n. Los clientes 'Public' no requieren un secreto. Los clientes 'Bearer-only' son servicios web que nunca inician un login.
+access-type.tooltip=Los clientes ''Confidential'' necesitan un secreto para iniciar el protocolo de identificaci\u00F3n. Los clientes ''Public'' no requieren un secreto. Los clientes ''Bearer-only'' son servicios web que nunca inician un login.
 service-accounts-enabled=Cuentas de servicio habilitadas
-service-accounts-enabled.tooltip=Permitir autenticar este cliente contra Keycloak y recivir un token de acceso dedicado para este cliente.
+service-accounts-enabled.tooltip=Permitir autenticar este cliente contra Keycloak y recibir un token de acceso dedicado para este cliente.
 include-authnstatement=Incluir AuthnStatement
-include-authnstatement.tooltip=null
+include-authnstatement.tooltip=\u00BFDeber\u00EDa incluirse una declaraci\u00F3n especificando el m\u00E9todo y la marca de tiempo en la respuesta de inicio de sesi\u00F3n?
 sign-documents=Firmar documentos
 sign-documents.tooltip=\u00BFDeber\u00EDa el dominio firmar los documentos SAML?
 sign-assertions=Firmar aserciones
@@ -200,22 +200,22 @@ name-id-format.tooltip=El formato de NameID que se usar\u00E1 para el t\u00EDtul
 root-url=URL ra\u00EDz
 root-url.tooltip=URL ra\u00EDz a\u00F1adida a las URLs relativas
 valid-redirect-uris=URIs de redirecci\u00F3n v\u00E1lidas
-valid-redirect-uris.tooltip=Patr\u00F3n de URI v\u00E1lida para la cual un navegador puede solicitar la redirecci\u00F3n tras un inicio o cierre de sesi\u00F3n completado. Se permiten comodines simples p.ej. 'http://example.com/*'. Tambi\u00E9n se pueden indicar rutas relativas p.ej. '/my/relative/path/*'. Las rutas relativas generar\u00E1n una URI de redirecci\u00F3n usando el host y puerto de la petici\u00F3n. Para SAML, se deben fijar patrones de URI v\u00E1lidos si quieres confiar en la URL del servicio del consumidor indicada en la petici\u00F3n de inicio de sesi\u00F3n.
+valid-redirect-uris.tooltip=Patr\u00F3n de URI v\u00E1lida para la cual un navegador puede solicitar la redirecci\u00F3n tras un inicio o cierre de sesi\u00F3n completado. Se permiten comodines simples p.ej. ''http://example.com/*''. Tambi\u00E9n se pueden indicar rutas relativas p.ej. ''/my/relative/path/*''. Las rutas relativas generar\u00E1n una URI de redirecci\u00F3n usando el host y puerto de la petici\u00F3n. Para SAML, se deben fijar patrones de URI v\u00E1lidos si quieres confiar en la URL del servicio del consumidor indicada en la petici\u00F3n de inicio de sesi\u00F3n.
 base-url.tooltip=URL por defecto para usar cuando el servidor de autorizaci\u00F3n necesita redirigir o enviar de vuelta al cliente.
 admin-url=URL de administraci\u00F3n
 admin-url.tooltip=URL a la interfaz de administraci\u00F3n del cliente. Fija este valor si el cliente soporta el adaptador de REST. Esta API REST permite al servidor de autenticaci\u00F3n enviar al cliente pol\u00EDticas de revocaci\u00F3n y otras tareas administrativas. Normalment se fija a la URL base del cliente.
 master-saml-processing-url=URL principal de procesamiento SAML
 master-saml-processing-url.tooltip=Si est\u00E1 configurada, esta URL se usar\u00E1 para cada enlace al proveedor del servicio del consumidor de aserciones y servicios de desconexi\u00F3n \u00FAnicos. Puede ser sobreescrito de forma individual para cada enlace y servicio en el punto final de configuraci\u00F3n fina de SAML.
 idp-sso-url-ref=Nombre de la URL de un SSO iniciado por el IDP
-idp-sso-url-ref.tooltip=Nombre del fragmento de la URL para referenciar al cliente cuando quieres un SSO iniciado por el IDP. Dejanto esto vac\u00EDo deshabilita los SSO iniciados por el IDP. La URL referenciada desde el navegador ser\u00E1: {server-root}/realms/{realm}/protocol/saml/clients/{client-url-name}
+idp-sso-url-ref.tooltip=Nombre del fragmento de la URL para referenciar al cliente cuando quieres un SSO iniciado por el IDP. Dejando esto vac\u00EDo deshabilita los SSO iniciados por el IDP. La URL referenciada desde el navegador ser\u00E1: {server-root}/realms/{realm}/protocol/saml/clients/{client-url-name}
 idp-sso-relay-state=Estado de retransmisi\u00F3n de un SSO iniciado por el IDP
-idp-sso-relay-state.tooltip=Estado de retransmisi\u00F3n que quiees enviar con una petici\u00F3n SAML cuando se inicia un SSO inidicado por el IDP
-web-origins=Origenes web
-web-origins.tooltip=Origenes CORS permitidos. Para permitir todos los or\u00EDgenes de URIs de redirecci\u00F3n v\u00E1lidas a\u00F1ade '+'. Para permitir todos los or\u00EDgenes a\u00F1ade '*'.
+idp-sso-relay-state.tooltip=Estado de retransmisi\u00F3n que quieres enviar con una petici\u00F3n SAML cuando se inicia un SSO iniciado por el IDP
+web-origins=Or\u00EDgenes web
+web-origins.tooltip=Or\u00EDgenes CORS permitidos. Para permitir todos los or\u00EDgenes de URIs de redirecci\u00F3n v\u00E1lidas a\u00F1ade ''+''. Para permitir todos los or\u00EDgenes a\u00F1ade ''*''.
 fine-saml-endpoint-conf=Fine Grain SAML Endpoint Configuration
 fine-saml-endpoint-conf.tooltip=Expande esta secci\u00F3n para configurar las URL exactas para Assertion Consumer y Single Logout Service.
 assertion-consumer-post-binding-url=Assertion Consumer Service POST Binding URL
-assertion-consumer-post-binding-url.tooltip=SAML POST Binding URL for the client's assertion consumer service (login responses).  You can leave this blank if you do not have a URL for this binding.
+assertion-consumer-post-binding-url.tooltip=SAML POST Binding URL for the client''s assertion consumer service (login responses).  You can leave this blank if you do not have a URL for this binding.
 assertion-consumer-redirect-binding-url=Assertion Consumer Service Redirect Binding URL
 assertion-consumer-redirect-binding-url.tooltip=Assertion Consumer Service Redirect Binding URL
 logout-service-binding-post-url=URL de enlace SAML POST para la desconexi\u00F3n
@@ -252,7 +252,7 @@ client-authenticator=Cliente autenticador
 client-authenticator.tooltip=Cliente autenticador usado para autenticar este cliente contra el servidor Keycloak
 certificate.tooltip=Certificado de clinete para validar los JWT emitidos por este cliente y firmados con la clave privada del cliente de tu almac\u00E9n de claves.
 no-client-certificate-configured=No se ha configurado el certificado de cliente
-gen-new-keys-and-cert=Genearr nuevas claves y certificado
+gen-new-keys-and-cert=Generar nuevas claves y certificado
 import-certificate=Importar Certificado
 gen-client-private-key=Generar clave privada de cliente
 generate-private-key=Generar clave privada
@@ -278,7 +278,7 @@ no-client-roles-available=No hay roles de cliente disponibles
 scope-param-required=Par\u00E1metro de \u00E1mbito obligatorio
 scope-param-required.tooltip=Este rol solo ser\u00E1 concedido si el par\u00E1metro de \u00E1mbito con el nombre del rol es usado durante la petici\u00F3n de autorizaci\u00F3n/obtenci\u00F3n de token.
 composite-roles=Roles compuestos
-composite-roles.tooltip=Cuanto este rol es asignado/desasignado a un usuario cualquier rol asociado con \u00E9l ser\u00E1 asignado/desasignado de forma impl\u00EDcita.
+composite-roles.tooltip=Cuando este rol es asignado/desasignado a un usuario cualquier rol asociado con \u00E9l ser\u00E1 asignado/desasignado de forma impl\u00EDcita.
 realm-roles=Roles de dominio
 available-roles=Roles Disponibles
 add-selected=A\u00F1adir seleccionado
@@ -317,7 +317,7 @@ test-cluster-availability=Probar disponibilidad del cluster
 last-registration=\u00DAltimo registro
 node-host=Host del nodo
 no-registered-cluster-nodes=No hay nodos de cluster registrados disponibles
-cluster-nodes=Nodos de cluster
+cluster-nodes=Nodos de cl\u00FAster
 add-node=A\u00F1adir Nodo
 active-sessions.tooltip=N\u00FAmero total de sesiones activas para este cliente.
 show-sessions=Mostrar sesiones
@@ -362,7 +362,7 @@ protocol=Protocolo
 protocol.tooltip=Protocolo.
 id=ID
 mapper.name.tooltip=Nombre del asignador.
-mapper.consent-required.tooltip=Cuando se concede acceso temporal, \u00BFes necesario el consentimiento del usuario para proporcinar estos datos al cliente cliente?
+mapper.consent-required.tooltip=Cuando se concede acceso temporal, \u00BFes necesario el consentimiento del usuario para proporcinar estos datos al cliente?
 consent-text=Texto del consentimiento
 consent-text.tooltip=Texto para mostrar en la p\u00E1gina de consentimiento.
 mapper-type=Tipo de asignador
@@ -381,14 +381,14 @@ identity-provider.enabled.tooltip=Habilita/deshabilita este proveedor de identid
 authenticate-by-default=Autenticar por defecto
 identity-provider.authenticate-by-default.tooltip=Indica si este proveedor deber\u00EDa ser probado por defecto para autenticacaci\u00F3n incluso antes de mostrar la p\u00E1gina de inicio de sesi\u00F3n.
 store-tokens=Almacenar tokens
-identity-provider.store-tokens.tooltip=Hablitar/deshabilitar si los tokens deben ser almacenados despu\u00E9s de autenticar a los usuarios.
+identity-provider.store-tokens.tooltip=Habiltar/deshabilitar si los tokens deben ser almacenados despu\u00E9s de autenticar a los usuarios.
 stored-tokens-readable=Tokens almacenados legibles
-identity-provider.stored-tokens-readable.tooltip=Habilitar/deshabilitar is los nuevos usuarios pueden leear los tokens almacenados. Esto asigna el rol 'broker.read-token'.
+identity-provider.stored-tokens-readable.tooltip=Habilitar/deshabilitar si los nuevos usuarios pueden leer los tokens almacenados. Esto asigna el rol ''broker.read-token''.
 update-profile-on-first-login=Actualizar perfil en el primer inicio de sesi\u00F3n
 on=Activado
 on-missing-info=Si falta informaci\u00F3n
 off=Desactivado
-update-profile-on-first-login.tooltip=Define condiciones bajos las cuales un usuario tiene que actualizar su perfil durante el primer inicio de sesi\u00F3n.
+update-profile-on-first-login.tooltip=Define condiciones bajo las cuales un usuario tiene que actualizar su perfil durante el primer inicio de sesi\u00F3n.
 trust-email=Confiar en el email
 trust-email.tooltip=Si est\u00E1 habilitado, el email recibido de este proveedor no se verificar\u00E1 aunque la verificaci\u00F3n est\u00E9 habilitada para el dominio.
 gui-order.tooltip=N\u00FAmero que define el orden del proveedor en la interfaz gr\u00E1fica (GUI) (ej. en la p\u00E1gina de inicio de sesi\u00F3n)
@@ -403,7 +403,7 @@ identity-provider.logout-url.tooltip=Punto de cierre de sesi\u00F3n para usar en
 backchannel-logout=Backchannel Logout
 backchannel-logout.tooltip=Does the external IDP support backchannel logout?
 user-info-url=URL de informaci\u00F3n de usuario
-user-info-url.tooltip=.La URL de informaci\u00F3n de usuario. Opcional
+user-info-url.tooltip=La URL de informaci\u00F3n de usuario. Opcional.
 identity-provider.client-id.tooltip=El cliente o identificador de cliente registrado en el proveedor de identidad.
 client-secret=Secreto de Cliente
 show-secret=Mostrar secreto
@@ -412,7 +412,7 @@ client-secret.tooltip=El cliente o el secreto de cliente registrado en el provee
 issuer=Emisor
 issuer.tooltip=El identificador del emisor para el emisor de la respuesta. Si no se indica, no se realizar\u00E1 ninguna validaci\u00F3n.
 default-scopes=\u00C1mbitos por defecto
-identity-provider.default-scopes.tooltip=Los \u00E1mbitos que se enviar\u00E1n cuando se solicite autorizaci\u00F3n. Puede ser una lista de \u00E1mbitos separados por espacios. El valor por defecto es 'openid'.
+identity-provider.default-scopes.tooltip=Los \u00E1mbitos que se enviar\u00E1n cuando se solicite autorizaci\u00F3n. Puede ser una lista de \u00E1mbitos separados por espacios. El valor por defecto es ''openid''.
 prompt=Prompt
 unspecified.option=no especificado
 none.option=ninguno
@@ -431,7 +431,7 @@ identity-provider.import-from-url.tooltip=Importar metadatos desde un descriptor
 import-from-file=Importar desde archivo
 identity-provider.import-from-file.tooltip=Importar metadatos desde un descriptor de un proveedor de identidad (IDP) descargado.
 saml-config=Configuraci\u00F3n SAML
-identity-provider.saml-config.tooltip=Configurci\u00F3n de proveedor SAML e IDP externo
+identity-provider.saml-config.tooltip=Configuraci\u00F3n de proveedor SAML e IDP externo
 single-signon-service-url=URL de servicio de conexi\u00F3n \u00FAnico (SSO)
 saml.single-signon-service-url.tooltip=La URL que debe ser usada para enviar peticiones de autenticaci\u00F3n (SAML AuthnRequest).
 single-logout-service-url=URL de servicio de desconexi\u00F3n \u00FAnico
@@ -441,7 +441,7 @@ nameid-policy-format.tooltip=Indica la referencia a la URI correspondiente a un 
 http-post-binding-response=HTTP-POST enlace de respuesta
 http-post-binding-response.tooltip=Indica si se reponde a las peticiones usando HTTP-POST. Si no est\u00E1 activado, se usa HTTP-REDIRECT. 
 http-post-binding-for-authn-request=HTTP-POST para AuthnRequest
-http-post-binding-for-authn-request.tooltip=Indica si AuthnRequest debe ser envianda usando HTTP-POST. Si no est\u00E1 activado se hace HTTP-REDIRECT.
+http-post-binding-for-authn-request.tooltip=Indica si AuthnRequest debe ser enviada usando HTTP-POST. Si no est\u00E1 activado se hace HTTP-REDIRECT.
 want-authn-requests-signed=Firmar AuthnRequests
 want-authn-requests-signed.tooltip=Indica si el proveedor de identidad espera recibir firmadas las AuthnRequest.
 force-authentication=Forzar autenticaci\u00F3n
@@ -463,4 +463,4 @@ realm=Dominio
 identity-provider-mappers=Asignadores de proveedores de identidad (IDP)
 create-identity-provider-mapper=Crear asignador de proveedor de identidad (IDP)
 add-identity-provider-mapper=A\u00F1adir asignador de proveedor de identidad
-client.description.tooltip=Indica la descripci\u00F3n del cliente. Por ejemplo 'My Client for TimeSheets'. Tambi\u00E9n soporta claves para valores localizzados. Por ejemplo: ${my_client_description}
+client.description.tooltip=Indica la descripci\u00F3n del cliente. Por ejemplo ''My Client for TimeSheets''. Tambi\u00E9n soporta claves para valores localizados. Por ejemplo: ${my_client_description}
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/messages_es.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/messages_es.properties
index b5b7ca8..70e3d73 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/messages/messages_es.properties
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/messages_es.properties
@@ -1,6 +1,6 @@
 invalidPasswordMinLengthMessage=Contrase\u00F1a incorrecta: longitud m\u00EDnima {0}.
 invalidPasswordMinLowerCaseCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} letras min\u00FAsculas.
-invalidPasswordMinDigitsMessage=Contrase\u00F1a incorrecta: debe contaner al menos {0} caracteres num\u00E9ricos.
+invalidPasswordMinDigitsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} caracteres num\u00E9ricos.
 invalidPasswordMinUpperCaseCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} letras may\u00FAsculas.
 invalidPasswordMinSpecialCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} caracteres especiales.
 invalidPasswordNotUsernameMessage=Contrase\u00F1a incorrecta: no puede ser igual al nombre de usuario.
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index a837f4f..451e3d5 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -327,6 +327,8 @@ module.controller('RealmLoginSettingsCtrl', function($scope, Current, Realm, rea
 });
 
 module.controller('RealmOtpPolicyCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
+    $scope.optionsDigits = [ 6, 8 ];
+
     genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/authentication/otp-policy");
 });
 
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html
index eaaf811..4abe5a2 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html
@@ -34,9 +34,7 @@
             <label class="col-md-2 control-label" for="digits">Number of Digits</label>
             <div class="col-md-2">
                 <div>
-                    <select id="digits" ng-model="realm.otpPolicyDigits" class="form-control">
-                        <option value="6">6</option>
-                        <option value="8">8</option>
+                    <select id="digits" ng-model="realm.otpPolicyDigits" class="form-control" ng-options="item as item for item in optionsDigits">
                     </select>
                 </div>
             </div>
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/as7-eap6/pom.xml b/integration/as7-eap6/pom.xml
index e1bca6a..52a1053 100755
--- a/integration/as7-eap6/pom.xml
+++ b/integration/as7-eap6/pom.xml
@@ -17,6 +17,5 @@
         <module>as7-adapter-spi</module>
         <module>as7-adapter</module>
         <module>as7-subsystem</module>
-        <module>as7-server-subsystem</module>
     </modules>
 </project>
\ No newline at end of file
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/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/integration/wildfly/pom.xml b/integration/wildfly/pom.xml
index 141cd87..3afdcc6 100644
--- a/integration/wildfly/pom.xml
+++ b/integration/wildfly/pom.xml
@@ -15,9 +15,7 @@
 
     <modules>
         <module>wildfly-adapter</module>
-        <module>wildfly-extensions</module>
         <module>wf8-subsystem</module>
         <module>wf9-subsystem</module>
-        <module>wf9-server-subsystem</module>
     </modules>
 </project>
\ No newline at end of file
diff --git a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java
index aff7d37..3674334 100755
--- a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java
+++ b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java
@@ -76,6 +76,8 @@ public class PasswordPolicy implements Serializable {
                 list.add(new PasswordHistory(arg));
             } else if (name.equals(ForceExpiredPasswordChange.NAME)) {
                 list.add(new ForceExpiredPasswordChange(arg));
+            } else {
+                throw new IllegalArgumentException("Unsupported policy");
             }
         }
         return list;
diff --git a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
index 7ce15d8..af9b6b5 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
@@ -51,6 +51,10 @@ public class CredentialValidation {
     }
 
     public static boolean validateHashedCredential(RealmModel realm, UserModel user, String unhashedCredValue, UserCredentialValueModel credential) {
+        if(unhashedCredValue == null){
+            return false;
+        }
+
         boolean validated = new Pbkdf2PasswordEncoder(credential.getSalt()).verify(unhashedCredValue, credential.getValue(), credential.getHashIterations());
         if (validated) {
             int iterations = hashIterations(realm);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 3916fd1..dd82098 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -996,14 +996,14 @@ public class RepresentationToModel {
 
         // Import users just to user storage. Don't federate
         UserModel user = session.userStorage().addUser(newRealm, userRep.getId(), userRep.getUsername(), false, false);
-        user.setEnabled(userRep.isEnabled());
+        user.setEnabled(userRep.isEnabled() != null && userRep.isEnabled());
         user.setCreatedTimestamp(userRep.getCreatedTimestamp());
         user.setEmail(userRep.getEmail());
-        user.setEmailVerified(userRep.isEmailVerified());
+        if (userRep.isEmailVerified() != null) user.setEmailVerified(userRep.isEmailVerified());
         user.setFirstName(userRep.getFirstName());
         user.setLastName(userRep.getLastName());
         user.setFederationLink(userRep.getFederationLink());
-        user.setOtpEnabled(userRep.isTotp());
+        if (userRep.isTotp() != null) user.setOtpEnabled(userRep.isTotp());
         if (userRep.getAttributes() != null) {
             for (Map.Entry<String, Object> entry : userRep.getAttributes().entrySet()) {
                 Object value = entry.getValue();
diff --git a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java
index df76588..8c662fb 100755
--- a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java
+++ b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java
@@ -83,6 +83,15 @@ public class PasswordPolicyTest {
         Assert.assertEquals("invalidPasswordNotUsernameMessage", policy.validate("jdoe", "jdoe").getMessage());
         Assert.assertNull(policy.validate("jdoe", "ab&d1234"));
     }
+
+    @Test
+    public void testInvalidPolicyName() {
+        try {
+            PasswordPolicy policy = new PasswordPolicy("noSuchPolicy");
+            Assert.fail("Expected exception");
+        } catch (IllegalArgumentException e) {
+        }
+    }
     
     @Test
     public void testRegexPatterns() {

pom.xml 12(+9 -3)

diff --git a/pom.xml b/pom.xml
index c137e59..62ae25c 100755
--- a/pom.xml
+++ b/pom.xml
@@ -76,7 +76,7 @@
         <log4j.version>1.2.17</log4j.version>
         <greenmail.version>1.3.1b</greenmail.version>
         <xmlsec.version>1.5.1</xmlsec.version>
-        <aesh.version>0.66</aesh.version>
+        <aesh.version>0.65.1</aesh.version>
 
         <enforcer.plugin.version>1.4</enforcer.plugin.version>
         <jboss.as.plugin.version>7.5.Final</jboss.as.plugin.version>
@@ -154,6 +154,7 @@
         <module>timer</module>
         <module>export-import</module>
         <module>util</module>
+        <module>wildfly</module>
     </modules>
 
     <dependencyManagement>
@@ -827,7 +828,7 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
-                <artifactId>keycloak-as7-server-subsystem</artifactId>
+                <artifactId>keycloak-eap6-server-subsystem</artifactId>
                 <version>${project.version}</version>
             </dependency>
             <dependency>
@@ -842,7 +843,7 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
-                <artifactId>keycloak-wf9-server-subsystem</artifactId>
+                <artifactId>keycloak-wildfly-server-subsystem</artifactId>
                 <version>${project.version}</version>
             </dependency>
             <dependency>
@@ -947,6 +948,11 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
+                <artifactId>keycloak-wildfly-adduser</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.keycloak</groupId>
                 <artifactId>keycloak-wildfly-extensions</artifactId>
                 <version>${project.version}</version>
             </dependency>
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/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java
index 81c2df4..1ee3cd2 100644
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java
@@ -1,63 +1,35 @@
 package org.keycloak.protocol.saml.clientregistration;
 
-import org.jboss.logging.Logger;
-import org.keycloak.events.EventBuilder;
+import org.keycloak.exportimport.ClientDescriptionConverter;
 import org.keycloak.models.KeycloakSession;
-import org.keycloak.services.clientregistration.ClientRegistrationAuth;
-import org.keycloak.services.clientregistration.ClientRegistrationProvider;
+import org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.services.clientregistration.AbstractClientRegistrationProvider;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.net.URI;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class EntityDescriptorClientRegistrationProvider implements ClientRegistrationProvider {
-
-    private static final Logger logger = Logger.getLogger(EntityDescriptorClientRegistrationProvider.class);
-
-    private KeycloakSession session;
-    private EventBuilder event;
-    private ClientRegistrationAuth auth;
+public class EntityDescriptorClientRegistrationProvider extends AbstractClientRegistrationProvider {
 
     public EntityDescriptorClientRegistrationProvider(KeycloakSession session) {
-        this.session = session;
-    }
-
-//    @POST
-//    @Consumes(MediaType.APPLICATION_XML)
-//    @Produces(MediaType.APPLICATION_JSON)
-//    public Response create(String descriptor) {
-//        event.event(EventType.CLIENT_REGISTER);
-//
-//        auth.requireCreate();
-//
-//        ClientRepresentation client = session.getProvider(ClientDescriptionConverter.class, EntityDescriptorDescriptionConverter.ID).convertToInternal(descriptor);
-//
-//        try {
-//            ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
-//            client = ModelToRepresentation.toRepresentation(clientModel);
-//            URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
-//
-//            logger.infov("Created client {0}", client.getClientId());
-//
-//            event.client(client.getClientId()).success();
-//
-//            return Response.created(uri).entity(client).build();
-//        } catch (ModelDuplicateException e) {
-//            return ErrorResponse.exists("Client " + client.getClientId() + " already exists");
-//        }
-//    }
-
-    @Override
-    public void close() {
-    }
-
-    @Override
-    public void setAuth(ClientRegistrationAuth auth) {
-        this.auth = auth;
+        super(session);
     }
 
-    @Override
-    public void setEvent(EventBuilder event) {
-        this.event = event;
+    @POST
+    @Consumes(MediaType.APPLICATION_XML)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createSaml(String descriptor) {
+        ClientRepresentation client = session.getProvider(ClientDescriptionConverter.class, EntityDescriptorDescriptionConverter.ID).convertToInternal(descriptor);
+        client = create(client);
+        URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
+        return Response.created(uri).entity(client).build();
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java
index 0506f21..5afaa3d 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java
@@ -148,24 +148,17 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth
     public boolean validatePassword(AuthenticationFlowContext context, UserModel user, MultivaluedMap<String, String> inputData) {
         List<UserCredentialModel> credentials = new LinkedList<>();
         String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
-        if (password == null || password.isEmpty()) {
-            invalidPassword(context, user);
-            return false;
-        }
         credentials.add(UserCredentialModel.password(password));
         boolean valid = context.getSession().users().validCredentials(context.getRealm(), user, credentials);
         if (!valid) {
-            invalidPassword(context, user);
+            context.getEvent().user(user);
+            context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
+            Response challengeResponse = invalidCredentials(context);
+            context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse);
+            context.clearUser();
             return false;
         }
         return true;
     }
 
-    private void invalidPassword(AuthenticationFlowContext context, UserModel user) {
-        context.getEvent().user(user);
-        context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
-        Response challengeResponse = invalidCredentials(context);
-        context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse);
-        context.clearUser();
-    }
 }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidatePassword.java b/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidatePassword.java
index cff7f37..21aa18d 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidatePassword.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidatePassword.java
@@ -31,15 +31,6 @@ public class ValidatePassword extends AbstractDirectGrantAuthenticator {
         MultivaluedMap<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
         List<UserCredentialModel> credentials = new LinkedList<>();
         String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
-        if (password == null || password.isEmpty()) {
-            if (context.getUser() != null) {
-                context.getEvent().user(context.getUser());
-            }
-            context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
-            Response challengeResponse = errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "invalid_grant", "Invalid user credentials");
-            context.failure(AuthenticationFlowError.INVALID_USER, challengeResponse);
-            return;
-        }
         credentials.add(UserCredentialModel.password(password));
         boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
         if (!valid) {
diff --git a/services/src/main/java/org/keycloak/exportimport/ExportImportManager.java b/services/src/main/java/org/keycloak/exportimport/ExportImportManager.java
index 89dc097..cedcbdd 100644
--- a/services/src/main/java/org/keycloak/exportimport/ExportImportManager.java
+++ b/services/src/main/java/org/keycloak/exportimport/ExportImportManager.java
@@ -60,7 +60,8 @@ public class ExportImportManager {
 	                        
 	                        // Check if master realm was exported. If it's not, then it needs to be created before other realms are imported
 	                        if (!importProvider.isMasterRealmExported()) {
-	                            new ApplianceBootstrap().bootstrap(sessionFactory, contextPath);
+	                            ApplianceBootstrap.setupDefaultRealm(sessionFactory, contextPath);
+                                ApplianceBootstrap.setupDefaultUser(sessionFactory);
 	                        }
 	
 	                        importProvider.importModel(sessionFactory, strategy);
@@ -69,7 +70,8 @@ public class ExportImportManager {
 	
 	                        if (!realmName.equals(Config.getAdminRealm())) {
 	                            // Check if master realm exists. If it's not, then it needs to be created before other realm is imported
-	                            new ApplianceBootstrap().bootstrap(sessionFactory, contextPath);
+                                ApplianceBootstrap.setupDefaultRealm(sessionFactory, contextPath);
+                                ApplianceBootstrap.setupDefaultUser(sessionFactory);
 	                        }
 	
 	                        importProvider.importRealm(sessionFactory, realmName, strategy);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java
index 955dfe4..6caacd6 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java
@@ -5,8 +5,7 @@ import org.keycloak.exportimport.ClientDescriptionConverter;
 import org.keycloak.exportimport.ClientDescriptionConverterFactory;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
-import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
+import org.keycloak.representations.oidc.OIDCClientRepresentation;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.services.clientregistration.oidc.DescriptionConverter;
 import org.keycloak.util.JsonSerialization;
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
index 714c0d1..60e9f9d 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
@@ -4,6 +4,8 @@ import org.keycloak.OAuth2Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
+import org.keycloak.services.clientregistration.ClientRegistrationService;
+import org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory;
 import org.keycloak.services.resources.RealmsResource;
 import org.keycloak.services.Urls;
 import org.keycloak.wellknown.WellKnownProvider;
@@ -48,6 +50,7 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
         config.setUserinfoEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "issueUserInfo").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
         config.setLogoutEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "logout").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
         config.setJwksUri(uriBuilder.clone().path(OIDCLoginProtocolService.class, "certs").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
+        config.setRegistrationEndpoint(RealmsResource.clientRegistrationUrl(uriInfo).path(ClientRegistrationService.class, "provider").build(realm.getName(), OIDCClientRegistrationProviderFactory.ID).toString());
 
         config.setIdTokenSigningAlgValuesSupported(DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED);
         config.setResponseTypesSupported(DEFAULT_RESPONSE_TYPES_SUPPORTED);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
index 9245e58..0226331 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
@@ -7,7 +7,6 @@ import org.codehaus.jackson.annotate.JsonProperty;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -48,6 +47,9 @@ public class OIDCConfigurationRepresentation {
     @JsonProperty("response_modes_supported")
     private List<String> responseModesSupported;
 
+    @JsonProperty("registration_endpoint")
+    private String registrationEndpoint;
+
     protected Map<String, Object> otherClaims = new HashMap<String, Object>();
 
     public String getIssuer() {
@@ -138,6 +140,14 @@ public class OIDCConfigurationRepresentation {
         this.responseModesSupported = responseModesSupported;
     }
 
+    public String getRegistrationEndpoint() {
+        return registrationEndpoint;
+    }
+
+    public void setRegistrationEndpoint(String registrationEndpoint) {
+        this.registrationEndpoint = registrationEndpoint;
+    }
+
     @JsonAnyGetter
     public Map<String, Object> getOtherClaims() {
         return otherClaims;
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java
new file mode 100644
index 0000000..0c95a37
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java
@@ -0,0 +1,125 @@
+package org.keycloak.services.clientregistration;
+
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.models.ClientInitialAccessModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.services.ErrorResponseException;
+import org.keycloak.services.ForbiddenException;
+
+import javax.ws.rs.core.Response;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public abstract class AbstractClientRegistrationProvider implements ClientRegistrationProvider {
+
+    protected KeycloakSession session;
+    protected EventBuilder event;
+    protected ClientRegistrationAuth auth;
+
+    public AbstractClientRegistrationProvider(KeycloakSession session) {
+        this.session = session;
+    }
+
+    public ClientRepresentation create(ClientRepresentation client) {
+        event.event(EventType.CLIENT_REGISTER);
+
+        auth.requireCreate();
+
+        try {
+            ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
+            if (client.getClientId() == null) {
+                clientModel.setClientId(clientModel.getId());
+            }
+
+            client = ModelToRepresentation.toRepresentation(clientModel);
+
+            String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, clientModel);
+
+            client.setRegistrationAccessToken(registrationAccessToken);
+
+            if (auth.isInitialAccessToken()) {
+                ClientInitialAccessModel initialAccessModel = auth.getInitialAccessModel();
+                initialAccessModel.decreaseRemainingCount();
+            }
+
+            event.client(client.getClientId()).success();
+            return client;
+        } catch (ModelDuplicateException e) {
+            throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client Identifier in use", Response.Status.BAD_REQUEST);
+        }
+    }
+
+    public ClientRepresentation get(String clientId) {
+        event.event(EventType.CLIENT_INFO);
+
+        ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
+        auth.requireView(client);
+
+        ClientRepresentation rep = ModelToRepresentation.toRepresentation(client);
+
+        if (auth.isRegistrationAccessToken()) {
+            String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, client);
+            rep.setRegistrationAccessToken(registrationAccessToken);
+        }
+
+        event.client(client.getClientId()).success();
+        return rep;
+    }
+
+    public ClientRepresentation update(String clientId, ClientRepresentation rep) {
+        event.event(EventType.CLIENT_UPDATE).client(clientId);
+
+        ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
+        auth.requireUpdate(client);
+
+        if (!client.getClientId().equals(rep.getClientId())) {
+            throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client Identifier modified", Response.Status.BAD_REQUEST);
+        }
+
+        RepresentationToModel.updateClient(rep, client);
+        rep = ModelToRepresentation.toRepresentation(client);
+
+        if (auth.isRegistrationAccessToken()) {
+            String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, client);
+            rep.setRegistrationAccessToken(registrationAccessToken);
+        }
+
+        event.client(client.getClientId()).success();
+        return rep;
+    }
+
+    public void delete(String clientId) {
+        event.event(EventType.CLIENT_DELETE).client(clientId);
+
+        ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
+        auth.requireUpdate(client);
+
+        if (session.getContext().getRealm().removeClient(client.getId())) {
+            event.client(client.getClientId()).success();
+        } else {
+            throw new ForbiddenException();
+        }
+    }
+
+    @Override
+    public void setAuth(ClientRegistrationAuth auth) {
+        this.auth = auth;
+    }
+
+    @Override
+    public void setEvent(EventBuilder event) {
+        this.event = event;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
index 0581388..621d5fb 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
@@ -25,7 +25,7 @@ public class ClientRegistrationService {
     }
 
     @Path("{provider}")
-    public Object getProvider(@PathParam("provider") String providerId) {
+    public Object provider(@PathParam("provider") String providerId) {
         checkSsl();
 
         ClientRegistrationProvider provider = session.getProvider(ClientRegistrationProvider.class, providerId);
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
index 38d71f2..126d00a 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
@@ -2,14 +2,11 @@ package org.keycloak.services.clientregistration;
 
 import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventType;
-import org.keycloak.models.ClientInitialAccessModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.ModelDuplicateException;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.representations.idm.ClientRepresentation;
-import org.keycloak.services.ErrorResponse;
 
 import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
@@ -19,101 +16,41 @@ import java.net.URI;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class DefaultClientRegistrationProvider implements ClientRegistrationProvider {
-
-    private KeycloakSession session;
-    private EventBuilder event;
-    private ClientRegistrationAuth auth;
+public class DefaultClientRegistrationProvider extends AbstractClientRegistrationProvider {
 
     public DefaultClientRegistrationProvider(KeycloakSession session) {
-        this.session = session;
+        super(session);
     }
 
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
-    public Response create(ClientRepresentation client) {
-        event.event(EventType.CLIENT_REGISTER);
-
-        auth.requireCreate();
-
-        try {
-            ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
-            client = ModelToRepresentation.toRepresentation(clientModel);
-
-            String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, clientModel);
-
-            client.setRegistrationAccessToken(registrationAccessToken);
-
-            URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
-
-            if (auth.isInitialAccessToken()) {
-                ClientInitialAccessModel initialAccessModel = auth.getInitialAccessModel();
-                initialAccessModel.decreaseRemainingCount();
-            }
-
-            event.client(client.getClientId()).success();
-            return Response.created(uri).entity(client).build();
-        } catch (ModelDuplicateException e) {
-            return ErrorResponse.exists("Client " + client.getClientId() + " already exists");
-        }
+    public Response createDefault(ClientRepresentation client) {
+        client = create(client);
+        URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
+        return Response.created(uri).entity(client).build();
     }
 
     @GET
     @Path("{clientId}")
     @Produces(MediaType.APPLICATION_JSON)
-    public Response get(@PathParam("clientId") String clientId) {
-        event.event(EventType.CLIENT_INFO);
-
-        ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
-        auth.requireView(client);
-
-        ClientRepresentation rep = ModelToRepresentation.toRepresentation(client);
-
-        if (auth.isRegistrationAccessToken()) {
-            String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, client);
-            rep.setRegistrationAccessToken(registrationAccessToken);
-        }
-
-        event.client(client.getClientId()).success();
-        return Response.ok(rep).build();
+    public Response getDefault(@PathParam("clientId") String clientId) {
+        ClientRepresentation client = get(clientId);
+        return Response.ok(client).build();
     }
 
     @PUT
     @Path("{clientId}")
     @Consumes(MediaType.APPLICATION_JSON)
-    public Response update(@PathParam("clientId") String clientId, ClientRepresentation rep) {
-        event.event(EventType.CLIENT_UPDATE).client(clientId);
-
-        ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
-        auth.requireUpdate(client);
-
-        RepresentationToModel.updateClient(rep, client);
-        rep = ModelToRepresentation.toRepresentation(client);
-
-        if (auth.isRegistrationAccessToken()) {
-            String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, client);
-            rep.setRegistrationAccessToken(registrationAccessToken);
-        }
-
-        event.client(client.getClientId()).success();
-        return Response.ok(rep).build();
+    public Response updateDefault(@PathParam("clientId") String clientId, ClientRepresentation client) {
+        client = update(clientId, client);
+        return Response.ok(client).build();
     }
 
     @DELETE
     @Path("{clientId}")
-    public Response delete(@PathParam("clientId") String clientId) {
-        event.event(EventType.CLIENT_DELETE).client(clientId);
-
-        ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
-        auth.requireUpdate(client);
-
-        if (session.getContext().getRealm().removeClient(client.getId())) {
-            event.client(client.getClientId()).success();
-            return Response.ok().build();
-        } else {
-            return Response.status(Response.Status.NOT_FOUND).build();
-        }
+    public void deleteDefault(@PathParam("clientId") String clientId) {
+        delete(clientId);
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ErrorCodes.java b/services/src/main/java/org/keycloak/services/clientregistration/ErrorCodes.java
new file mode 100644
index 0000000..ed491de
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ErrorCodes.java
@@ -0,0 +1,12 @@
+package org.keycloak.services.clientregistration;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ErrorCodes {
+
+    String INVALID_REDIRECT_URI = "invalid_redirect_uri";
+
+    String INVALID_CLIENT_METADATA = "invalid_client_metadata";
+
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
index 1e0784c..a7f9f2c 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
@@ -1,8 +1,9 @@
 package org.keycloak.services.clientregistration.oidc;
 
-import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
 import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.oidc.OIDCClientRepresentation;
+
+import java.net.URI;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -11,27 +12,22 @@ public class DescriptionConverter {
 
     public static ClientRepresentation toInternal(OIDCClientRepresentation clientOIDC) {
         ClientRepresentation client = new ClientRepresentation();
-        client.setClientId(KeycloakModelUtils.generateId());
+        client.setClientId(clientOIDC.getClientId());
         client.setName(clientOIDC.getClientName());
         client.setRedirectUris(clientOIDC.getRedirectUris());
         client.setBaseUrl(clientOIDC.getClientUri());
         return client;
     }
 
-    public static OIDCClientResponseRepresentation toExternalResponse(ClientRepresentation client) {
-        OIDCClientResponseRepresentation response = new OIDCClientResponseRepresentation();
+    public static OIDCClientRepresentation toExternalResponse(ClientRepresentation client, URI uri) {
+        OIDCClientRepresentation response = new OIDCClientRepresentation();
         response.setClientId(client.getClientId());
-
         response.setClientName(client.getName());
         response.setClientUri(client.getBaseUrl());
-
         response.setClientSecret(client.getSecret());
-        response.setClientSecretExpiresAt(0);
-
         response.setRedirectUris(client.getRedirectUris());
-
         response.setRegistrationAccessToken(client.getRegistrationAccessToken());
-
+        response.setRegistrationClientUri(uri.toString());
         return response;
     }
 
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java
index 961f28d..e60720b 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java
@@ -1,72 +1,70 @@
 package org.keycloak.services.clientregistration.oidc;
 
-import org.jboss.logging.Logger;
+import org.keycloak.common.util.Time;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.oidc.OIDCClientRepresentation;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.services.ErrorResponseException;
+import org.keycloak.services.clientregistration.AbstractClientRegistrationProvider;
 import org.keycloak.services.clientregistration.ClientRegistrationAuth;
-import org.keycloak.services.clientregistration.ClientRegistrationProvider;
+import org.keycloak.services.clientregistration.ErrorCodes;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.net.URI;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class OIDCClientRegistrationProvider implements ClientRegistrationProvider {
+public class OIDCClientRegistrationProvider extends AbstractClientRegistrationProvider {
 
-    private static final Logger logger = Logger.getLogger(OIDCClientRegistrationProvider.class);
+    public OIDCClientRegistrationProvider(KeycloakSession session) {
+        super(session);
+    }
 
-    private KeycloakSession session;
-    private EventBuilder event;
-    private ClientRegistrationAuth auth;
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createOIDC(OIDCClientRepresentation clientOIDC) {
+        if (clientOIDC.getClientId() != null) {
+            throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client Identifier included", Response.Status.BAD_REQUEST);
+        }
 
-    public OIDCClientRegistrationProvider(KeycloakSession session) {
-        this.session = session;
+        ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
+        client = create(client);
+        URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
+        clientOIDC = DescriptionConverter.toExternalResponse(client, uri);
+        clientOIDC.setClientIdIssuedAt(Time.currentTime());
+        return Response.created(uri).entity(clientOIDC).build();
     }
 
-//    @POST
-//    @Consumes(MediaType.APPLICATION_JSON)
-//    @Produces(MediaType.APPLICATION_JSON)
-//    public Response create(OIDCClientRepresentation clientOIDC) {
-//        event.event(EventType.CLIENT_REGISTER);
-//
-//        auth.requireCreate();
-//
-//        ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
-//
-//        try {
-//            ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
-//
-//            client = ModelToRepresentation.toRepresentation(clientModel);
-//
-//            String registrationAccessToken = TokenGenerator.createRegistrationAccessToken();
-//
-//            clientModel.setRegistrationToken(registrationAccessToken);
-//
-//            URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
-//
-//            logger.infov("Created client {0}", client.getClientId());
-//
-//            event.client(client.getClientId()).success();
-//
-//            OIDCClientResponseRepresentation response = DescriptionConverter.toExternalResponse(client);
-//
-//            response.setClientName(client.getName());
-//            response.setClientUri(client.getBaseUrl());
-//
-//            response.setClientSecret(client.getSecret());
-//            response.setClientSecretExpiresAt(0);
-//
-//            response.setRedirectUris(client.getRedirectUris());
-//
-//            response.setRegistrationAccessToken(registrationAccessToken);
-//            response.setRegistrationClientUri(uri.toString());
-//
-//            return Response.created(uri).entity(response).build();
-//        } catch (ModelDuplicateException e) {
-//            return ErrorResponse.exists("Client " + client.getClientId() + " already exists");
-//        }
-//    }
+    @GET
+    @Path("{clientId}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response getOIDC(@PathParam("clientId") String clientId) {
+        ClientRepresentation client = get(clientId);
+        OIDCClientRepresentation clientOIDC = DescriptionConverter.toExternalResponse(client, session.getContext().getUri().getRequestUri());
+        return Response.ok(clientOIDC).build();
+    }
 
-    @Override
-    public void close() {
+    @PUT
+    @Path("{clientId}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response updateOIDC(@PathParam("clientId") String clientId, OIDCClientRepresentation clientOIDC) {
+        ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
+        client = update(clientId, client);
+        URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
+        clientOIDC = DescriptionConverter.toExternalResponse(client, uri);
+        return Response.ok(clientOIDC).build();
+    }
+
+    @DELETE
+    @Path("{clientId}")
+    public void deleteOIDC(@PathParam("clientId") String clientId) {
+        delete(clientId);
     }
 
     @Override
@@ -79,4 +77,8 @@ public class OIDCClientRegistrationProvider implements ClientRegistrationProvide
         this.event = event;
     }
 
+    @Override
+    public void close() {
+    }
+
 }
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProviderFactory.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProviderFactory.java
index 6f112f8..0144e79 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProviderFactory.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProviderFactory.java
@@ -11,6 +11,8 @@ import org.keycloak.services.clientregistration.ClientRegistrationProviderFactor
  */
 public class OIDCClientRegistrationProviderFactory implements ClientRegistrationProviderFactory {
 
+    public static final String ID = "openid-connect";
+
     @Override
     public ClientRegistrationProvider create(KeycloakSession session) {
         return new OIDCClientRegistrationProvider(session);
@@ -30,7 +32,7 @@ public class OIDCClientRegistrationProviderFactory implements ClientRegistration
 
     @Override
     public String getId() {
-        return "openid-connect";
+        return ID;
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
index 5178cec..0fa1b92 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
@@ -23,62 +23,71 @@ public class ApplianceBootstrap {
 
     private static final Logger logger = Logger.getLogger(ApplianceBootstrap.class);
 
-    public void bootstrap(KeycloakSessionFactory sessionFactory, String contextPath) {
+    public static boolean setupDefaultRealm(KeycloakSessionFactory sessionFactory, String contextPath) {
         KeycloakSession session = sessionFactory.create();
         session.getTransaction().begin();
 
         try {
-            bootstrap(session, contextPath);
+            String adminRealmName = Config.getAdminRealm();
+            if (session.realms().getRealm(adminRealmName) != null) {
+                return false;
+            }
+
+            logger.info("Initializing " + adminRealmName + " realm");
+
+            RealmManager manager = new RealmManager(session);
+            manager.setContextPath(contextPath);
+            RealmModel realm = manager.createRealm(adminRealmName, adminRealmName);
+            realm.setName(adminRealmName);
+            realm.setEnabled(true);
+            realm.addRequiredCredential(CredentialRepresentation.PASSWORD);
+            realm.setSsoSessionIdleTimeout(1800);
+            realm.setAccessTokenLifespan(60);
+            realm.setSsoSessionMaxLifespan(36000);
+            realm.setOfflineSessionIdleTimeout(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT);
+            realm.setAccessCodeLifespan(60);
+            realm.setAccessCodeLifespanUserAction(300);
+            realm.setAccessCodeLifespanLogin(1800);
+            realm.setSslRequired(SslRequired.EXTERNAL);
+            realm.setRegistrationAllowed(false);
+            realm.setRegistrationEmailAsUsername(false);
+            KeycloakModelUtils.generateRealmKeys(realm);
+
             session.getTransaction().commit();
+            return true;
         } finally {
             session.close();
         }
     }
 
-    public void bootstrap(KeycloakSession session, String contextPath) {
-        String adminRealmName = Config.getAdminRealm();
-        if (session.realms().getRealm(adminRealmName) != null) {
-            return;
-        }
-
-        logger.info("Initializing " + adminRealmName + " realm");
-
-        RealmManager manager = new RealmManager(session);
-        manager.setContextPath(contextPath);
-        RealmModel realm = manager.createRealm(adminRealmName, adminRealmName);
-        realm.setName(adminRealmName);
-        realm.setEnabled(true);
-        realm.addRequiredCredential(CredentialRepresentation.PASSWORD);
-        realm.setSsoSessionIdleTimeout(1800);
-        realm.setAccessTokenLifespan(60);
-        realm.setSsoSessionMaxLifespan(36000);
-        realm.setOfflineSessionIdleTimeout(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT);
-        realm.setAccessCodeLifespan(60);
-        realm.setAccessCodeLifespanUserAction(300);
-        realm.setAccessCodeLifespanLogin(1800);
-        realm.setSslRequired(SslRequired.EXTERNAL);
-        realm.setRegistrationAllowed(false);
-        realm.setRegistrationEmailAsUsername(false);
-        KeycloakModelUtils.generateRealmKeys(realm);
+    public static boolean setupDefaultUser(KeycloakSessionFactory sessionFactory) {
+        KeycloakSession session = sessionFactory.create();
+        session.getTransaction().begin();
 
-        UserModel adminUser = session.users().addUser(realm, "admin");
-        setupAdminUser(session, realm, adminUser, "admin");
-    }
+        try {
+            RealmModel realm = session.realms().getRealm(Config.getAdminRealm());
+            if (session.users().getUserByUsername("admin", realm) == null) {
+                UserModel adminUser = session.users().addUser(realm, "admin");
 
-    public static void setupAdminUser(KeycloakSession session, RealmModel realm, UserModel adminUser, String password) {
-        adminUser.setEnabled(true);
-        UserCredentialModel usrCredModel = new UserCredentialModel();
-        usrCredModel.setType(UserCredentialModel.PASSWORD);
-        usrCredModel.setValue(password);
-        session.users().updateCredential(realm, adminUser, usrCredModel);
-        adminUser.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
+                adminUser.setEnabled(true);
+                UserCredentialModel usrCredModel = new UserCredentialModel();
+                usrCredModel.setType(UserCredentialModel.PASSWORD);
+                usrCredModel.setValue("admin");
+                session.users().updateCredential(realm, adminUser, usrCredModel);
+                adminUser.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
 
-        RoleModel adminRole = realm.getRole(AdminRoles.ADMIN);
-        adminUser.grantRole(adminRole);
+                RoleModel adminRole = realm.getRole(AdminRoles.ADMIN);
+                adminUser.grantRole(adminRole);
 
-        ClientModel accountApp = realm.getClientNameMap().get(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
-        for (String r : accountApp.getDefaultRoles()) {
-            adminUser.grantRole(accountApp.getRole(r));
+                ClientModel accountApp = realm.getClientNameMap().get(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
+                for (String r : accountApp.getDefaultRoles()) {
+                    adminUser.grantRole(accountApp.getRole(r));
+                }
+            }
+            session.getTransaction().commit();
+            return true;
+        } finally {
+            session.close();
         }
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index a85c9d2..2028610 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -148,7 +148,7 @@ public class UsersResource {
                 attrsToRemove = Collections.emptySet();
             }
 
-            if (rep.isEnabled()) {
+            if (rep.isEnabled() != null && rep.isEnabled()) {
                 UsernameLoginFailureModel failureModel = session.sessions().getUserLoginFailure(realm, rep.getUsername());
                 if (failureModel != null) {
                     failureModel.clearFailures();
@@ -219,9 +219,9 @@ public class UsersResource {
         user.setFirstName(rep.getFirstName());
         user.setLastName(rep.getLastName());
 
-        user.setEnabled(rep.isEnabled());
-        user.setOtpEnabled(rep.isTotp());
-        user.setEmailVerified(rep.isEmailVerified());
+        if (rep.isEnabled() != null) user.setEnabled(rep.isEnabled());
+        if (rep.isTotp() != null) user.setOtpEnabled(rep.isTotp());
+        if (rep.isEmailVerified() != null) user.setEmailVerified(rep.isEmailVerified());
 
         List<String> reqActions = rep.getRequiredActions();
 
@@ -708,7 +708,7 @@ public class UsersResource {
         } catch (ModelReadOnlyException mre) {
             throw new BadRequestException("Can't reset password as account is read only");
         }
-        if (pass.isTemporary()) user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
+        if (pass.isTemporary() != null && pass.isTemporary()) user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
 
         adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index a07999d..125b6ee 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -2,6 +2,7 @@ package org.keycloak.services.resources;
 
 import org.codehaus.jackson.JsonNode;
 import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.type.TypeReference;
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.core.Dispatcher;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
@@ -11,9 +12,11 @@ import org.keycloak.migration.MigrationModelManager;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.PostMigrationEvent;
-import org.keycloak.offlineconfig.AdminRecovery;
+import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.services.DefaultKeycloakSessionFactory;
 import org.keycloak.services.managers.ApplianceBootstrap;
 import org.keycloak.services.managers.BruteForceProtector;
@@ -36,10 +39,7 @@ import javax.ws.rs.core.UriInfo;
 import java.io.*;
 import java.net.URI;
 import java.net.URL;
-import java.util.HashSet;
-import java.util.Properties;
-import java.util.Set;
-import java.util.StringTokenizer;
+import java.util.*;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -81,7 +81,7 @@ public class KeycloakApplication extends Application {
 
         singletons.add(new ObjectMapperResolver(Boolean.parseBoolean(System.getProperty("keycloak.jsonPrettyPrint", "false"))));
 
-        setupDefaultRealm(context.getContextPath());
+        boolean defaultRealmCreated = ApplianceBootstrap.setupDefaultRealm(sessionFactory, context.getContextPath());
 
         migrateModel();
         sessionFactory.publish(new PostMigrationEvent());
@@ -89,7 +89,11 @@ public class KeycloakApplication extends Application {
         new ExportImportManager().checkExportImport(this.sessionFactory, context.getContextPath());
         importRealms(context);
 
-        AdminRecovery.recover(sessionFactory);
+        importAddUser();
+
+        if (defaultRealmCreated) {
+            ApplianceBootstrap.setupDefaultUser(sessionFactory);
+        }
 
         setupScheduledTasks(sessionFactory);
     }
@@ -153,10 +157,6 @@ public class KeycloakApplication extends Application {
         }
     }
 
-    protected void setupDefaultRealm(String contextPath) {
-        new ApplianceBootstrap().bootstrap(sessionFactory, contextPath);
-    }
-
     public static KeycloakSessionFactory createSessionFactory() {
         DefaultKeycloakSessionFactory factory = new DefaultKeycloakSessionFactory();
         factory.init();
@@ -254,6 +254,44 @@ public class KeycloakApplication extends Application {
         }
     }
 
+    public void importAddUser() {
+        String configDir = System.getProperty("jboss.server.config.dir");
+        if (configDir != null) {
+            File addUserFile = new File(configDir + File.separator + "keycloak-add-user.json");
+            if (addUserFile.isFile()) {
+                log.info("Importing users from '" + addUserFile + "'");
+
+                KeycloakSession session = sessionFactory.create();
+                try {
+                    session.getTransaction().begin();
+
+                    List<RealmRepresentation> realms = JsonSerialization.readValue(new FileInputStream(addUserFile), new TypeReference<List<RealmRepresentation>>() {});
+                    for (RealmRepresentation r : realms) {
+                        RealmModel realm = session.realms().getRealmByName(r.getRealm());
+                        if (realm == null) {
+                            throw new Exception("Realm '" + r.getRealm() + "' not found");
+                        }
+
+                        for (UserRepresentation u : r.getUsers()) {
+                            RepresentationToModel.createUser(session, realm, u, realm.getClientNameMap());
+                        }
+                    }
+
+                    session.getTransaction().commit();
+
+                    if (!addUserFile.delete()) {
+                        log.error("Failed to delete '" + addUserFile + "'");
+                    }
+                } catch (Throwable t) {
+                    session.getTransaction().rollback();
+                    log.error("Failed to import users from '" + addUserFile + "'", t);
+                } finally {
+                    session.close();
+                }
+            }
+        }
+    }
+
     private static <T> T loadJson(InputStream is, Class<T> type) {
         try {
             return JsonSerialization.readValue(is, type);
diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index 4b5e4f2..cc1e49a 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -59,6 +59,10 @@ public class RealmsResource {
         return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getProtocol");
     }
 
+    public static UriBuilder clientRegistrationUrl(UriInfo uriInfo) {
+        return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getClientsService");
+    }
+
     public static UriBuilder brokerUrl(UriInfo uriInfo) {
         return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getBrokerService");
     }
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index a14a006..2865040 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -32,6 +32,10 @@
             <artifactId>keycloak-admin-client</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-wildfly-adduser</artifactId>
+        </dependency>
+        <dependency>
             <groupId>log4j</groupId>
             <artifactId>log4j</artifactId>
         </dependency>
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/adduser/AddUserTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java
new file mode 100644
index 0000000..c3f68db
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java
@@ -0,0 +1,64 @@
+package org.keycloak.testsuite.adduser;
+
+import org.junit.*;
+import org.junit.rules.TemporaryFolder;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.models.Constants;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.KeycloakServer;
+import org.keycloak.wildfly.adduser.AddUser;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AddUserTest {
+
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder();
+
+    private File dir;
+
+    @Before
+    public void before() throws IOException {
+        dir = folder.newFolder();
+        System.setProperty("jboss.server.config.user.dir", dir.getAbsolutePath());
+        System.setProperty("jboss.server.config.dir", dir.getAbsolutePath());
+    }
+
+    @After
+    public void after() {
+        System.getProperties().remove("jboss.server.config.user.dir");
+        System.getProperties().remove("jboss.server.config.dir");
+    }
+
+    @Test
+    public void addUserTest() throws Throwable {
+        AddUser.main(new String[]{"-u", "addusertest-admin", "-p", "password"});
+        Assert.assertEquals(1, dir.listFiles().length);
+
+        KeycloakServer server = new KeycloakServer();
+        try {
+            server.start();
+
+            Keycloak keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "addusertest-admin", "password", Constants.ADMIN_CONSOLE_CLIENT_ID);
+            keycloak.realms().findAll();
+
+            RealmRepresentation testRealm = new RealmRepresentation();
+            testRealm.setEnabled(true);
+            testRealm.setId("test");
+            testRealm.setRealm("test");
+
+            keycloak.realms().create(testRealm);
+
+            keycloak.close();
+
+            Assert.assertEquals(0, dir.listFiles().length);
+        } finally {
+            server.stop();
+        }
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
index bb33515..42870a6 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
@@ -180,7 +180,6 @@ public class RealmTest extends AbstractClientTest {
         String description = IOUtils.toString(getClass().getResourceAsStream("/client-descriptions/client-oidc.json"));
 
         ClientRepresentation converted = realm.convertClientDescription(description);
-        assertEquals(36, converted.getClientId().length());
         assertEquals(1, converted.getRedirectUris().size());
         assertEquals("http://localhost", converted.getRedirectUris().get(0));
     }
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/KeycloakServer.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java
index 126d0e5..28f0915 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java
@@ -170,6 +170,10 @@ public class KeycloakServer {
                 System.setProperty("keycloak.theme.cacheTemplates", "false");
             }
 
+            if (!System.getProperties().containsKey("keycloak.theme.cacheThemes")) {
+                System.setProperty("keycloak.theme.cacheThemes", "false");
+            }
+
             if (!System.getProperties().containsKey("keycloak.theme.staticMaxAge")) {
                 System.setProperty("keycloak.theme.staticMaxAge", "-1");
             }
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/rule/KeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java
index 74df6c8..8fc4cd6 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java
@@ -22,6 +22,8 @@
 package org.keycloak.testsuite.rule;
 
 import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
 import org.keycloak.Config;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
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/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/otppolicy/OTPPolicyForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/otppolicy/OTPPolicyForm.java
index 1e88cb3..8414de9 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/otppolicy/OTPPolicyForm.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/otppolicy/OTPPolicyForm.java
@@ -26,6 +26,8 @@ import org.openqa.selenium.WebElement;
 import org.openqa.selenium.support.FindBy;
 import org.openqa.selenium.support.ui.Select;
 
+import java.util.List;
+
 /**
  *
  * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
@@ -53,7 +55,8 @@ public class OTPPolicyForm extends Form {
     public void setValues(OTPType otpType, OTPHashAlg otpHashAlg, Digits digits, String lookAhead, String periodOrCounter) {
         this.otpType.selectByValue(otpType.getName());
         this.otpHashAlg.selectByValue(otpHashAlg.getName());
-        this.digits.selectByValue(digits.getName());
+        this.digits.selectByVisibleText("" + digits.getName());
+
         setInputValue(this.lookAhead, lookAhead);
         
         switch (otpType) {
@@ -102,17 +105,16 @@ public class OTPPolicyForm extends Form {
     
     public enum Digits {
 
-        EMPTY("? number:6 ?"),
-        SIX("6"),
-        EIGHT("8");
+        SIX(6),
+        EIGHT(8);
 
-        private final String name;
+        private final int name;
 
-        private Digits(String name) {
+        private Digits(int name) {
             this.name = name;
         }
 
-        public String getName() {
+        public int getName() {
             return name;
         }
     }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/users/UserAttributesForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/users/UserAttributesForm.java
index 85b32e6..e508e71 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/users/UserAttributesForm.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/users/UserAttributesForm.java
@@ -130,8 +130,8 @@ public class UserAttributesForm extends Form {
         setEmail(user.getEmail());
         setFirstName(user.getFirstName());
         setLastName(user.getLastName());
-        setEnabled(user.isEnabled());
-        setEmailVerified(user.isEmailVerified());
+        if (user.isEnabled() != null) setEnabled(user.isEnabled());
+        if (user.isEmailVerified() != null) setEmailVerified(user.isEmailVerified());
         setRequiredActions(user.getRequiredActions());
     }
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
new file mode 100644
index 0000000..9696274
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
@@ -0,0 +1,85 @@
+package org.keycloak.testsuite.client;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.client.registration.Auth;
+import org.keycloak.client.registration.ClientRegistrationException;
+import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+import org.keycloak.representations.oidc.OIDCClientRepresentation;
+
+import java.util.Collections;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+
+        ClientInitialAccessPresentation token = adminClient.realm(REALM_NAME).clientInitialAccess().create(new ClientInitialAccessCreatePresentation(0, 10));
+        reg.auth(Auth.token(token));
+    }
+
+    public OIDCClientRepresentation create() throws ClientRegistrationException {
+        OIDCClientRepresentation client = new OIDCClientRepresentation();
+        client.setClientName("RegistrationAccessTokenTest");
+        client.setClientUri("http://root");
+        client.setRedirectUris(Collections.singletonList("http://redirect"));
+
+        OIDCClientRepresentation response = reg.oidc().create(client);
+
+        return response;
+    }
+
+    @Test
+    public void createClient() throws ClientRegistrationException {
+        OIDCClientRepresentation response = create();
+
+        assertNotNull(response.getRegistrationAccessToken());
+        assertNotNull(response.getClientIdIssuedAt());
+        assertNotNull(response.getClientId());
+        assertNull(response.getClientSecretExpiresAt());
+        assertNotNull(response.getRegistrationClientUri());
+        assertEquals("RegistrationAccessTokenTest", response.getClientName());
+        assertEquals("http://root", response.getClientUri());
+        assertEquals(1, response.getRedirectUris().size());
+        assertEquals("http://redirect", response.getRedirectUris().get(0));
+    }
+
+    @Test
+    public void getClient() throws ClientRegistrationException {
+        OIDCClientRepresentation response = create();
+        reg.auth(Auth.token(response));
+
+        OIDCClientRepresentation rep = reg.oidc().get(response.getClientId());
+        assertNotNull(rep);
+        assertNotEquals(response.getRegistrationAccessToken(), rep.getRegistrationAccessToken());
+    }
+
+    @Test
+    public void updateClient() throws ClientRegistrationException {
+        OIDCClientRepresentation response = create();
+        reg.auth(Auth.token(response));
+
+        response.setRedirectUris(Collections.singletonList("http://newredirect"));
+
+        OIDCClientRepresentation updated = reg.oidc().update(response);
+
+        assertEquals(1, updated.getRedirectUris().size());
+        assertEquals("http://newredirect", updated.getRedirectUris().get(0));
+    }
+
+    @Test
+    public void deleteClient() throws ClientRegistrationException {
+        OIDCClientRepresentation response = create();
+        reg.auth(Auth.token(response));
+
+        reg.oidc().delete(response);
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/SAMLClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/SAMLClientRegistrationTest.java
new file mode 100644
index 0000000..1f5eab8
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/SAMLClientRegistrationTest.java
@@ -0,0 +1,42 @@
+package org.keycloak.testsuite.client;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.client.registration.Auth;
+import org.keycloak.client.registration.ClientRegistrationException;
+import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.oidc.OIDCClientRepresentation;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class SAMLClientRegistrationTest extends AbstractClientRegistrationTest {
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+
+        ClientInitialAccessPresentation token = adminClient.realm(REALM_NAME).clientInitialAccess().create(new ClientInitialAccessCreatePresentation(0, 10));
+        reg.auth(Auth.token(token));
+    }
+
+    @Test
+    public void createClient() throws ClientRegistrationException, IOException {
+        String entityDescriptor = IOUtils.toString(getClass().getResourceAsStream("/clientreg-test/saml-entity-descriptor.xml"));
+        ClientRepresentation response = reg.saml().create(entityDescriptor);
+
+        assertNotNull(response.getRegistrationAccessToken());
+        assertEquals("loadbalancer-9.siroe.com", response.getClientId());
+        assertEquals(1, response.getRedirectUris().size());
+        assertEquals("https://LoadBalancer-9.siroe.com:3443/federation/Consumer/metaAlias/sp", response.getRedirectUris().get(0));
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/OTPPolicyTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/OTPPolicyTest.java
index d9f8f89..5e431bb 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/OTPPolicyTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/OTPPolicyTest.java
@@ -69,17 +69,17 @@ public class OTPPolicyTest extends AbstractConsoleTest {
     @Test
     @Ignore //KEYCLOAK-2051 when you close notification, it is not displayed again
     public void invalidValuesTest() {
-        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "", "30");
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.SIX, "", "30");
         assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
         otpPolicyPage.closeNotification();
         otpPolicyPage.navigateTo();// workaround: input.clear() doesn't work when <input type="number" ...
         
-        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, " ", "30");
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.SIX, " ", "30");
         assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
         otpPolicyPage.closeNotification();
         otpPolicyPage.navigateTo();
         
-        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "no number", "30");
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.SIX, "no number", "30");
         assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
         otpPolicyPage.closeNotification();
         otpPolicyPage.navigateTo();
@@ -87,17 +87,17 @@ public class OTPPolicyTest extends AbstractConsoleTest {
         RealmRepresentation realm = testRealmResource().toRepresentation();
         assertEquals(Integer.valueOf(1), realm.getOtpPolicyLookAheadWindow());
 
-        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "");
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.SIX, "1", "");
         assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
         otpPolicyPage.closeNotification();
         otpPolicyPage.navigateTo();
         
-        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", " ");
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.SIX, "1", " ");
         assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
         otpPolicyPage.closeNotification();
         otpPolicyPage.navigateTo();
         
-        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "no number");
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.SIX, "1", "no number");
         assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
         otpPolicyPage.closeNotification();
         otpPolicyPage.navigateTo();
@@ -105,17 +105,17 @@ public class OTPPolicyTest extends AbstractConsoleTest {
         realm = testRealmResource().toRepresentation();
         assertEquals(Integer.valueOf(30), realm.getOtpPolicyPeriod());
         
-        otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "");
+        otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.SIX, "1", "");
         assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
         otpPolicyPage.closeNotification();
         otpPolicyPage.navigateTo();
         
-        otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", " ");
+        otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.SIX, "1", " ");
         assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
         otpPolicyPage.closeNotification();
         otpPolicyPage.navigateTo();
         
-        otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "no number");
+        otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.SIX, "1", "no number");
         assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
         otpPolicyPage.closeNotification();
         otpPolicyPage.navigateTo();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/clientreg-test/saml-entity-descriptor.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/clientreg-test/saml-entity-descriptor.xml
new file mode 100644
index 0000000..b00ab25
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/clientreg-test/saml-entity-descriptor.xml
@@ -0,0 +1,82 @@
+<EntityDescriptor
+    xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+    entityID="loadbalancer-9.siroe.com">
+    <SPSSODescriptor
+        AuthnRequestsSigned="false"
+        WantAssertionsSigned="false"
+        protocolSupportEnumeration=
+            "urn:oasis:names:tc:SAML:2.0:protocol">
+        <KeyDescriptor use="signing">
+            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
+                <X509Data>
+                    <X509Certificate>
+MIICYDCCAgqgAwIBAgICBoowDQYJKoZIhvcNAQEEBQAwgZIxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
+EwpDYWxpZm9ybmlhMRQwEgYDVQQHEwtTYW50YSBDbGFyYTEeMBwGA1UEChMVU3VuIE1pY3Jvc3lz
+dGVtcyBJbmMuMRowGAYDVQQLExFJZGVudGl0eSBTZXJ2aWNlczEcMBoGA1UEAxMTQ2VydGlmaWNh
+dGUgTWFuYWdlcjAeFw0wNjExMDIxOTExMzRaFw0xMDA3MjkxOTExMzRaMDcxEjAQBgNVBAoTCXNp
+cm9lLmNvbTEhMB8GA1UEAxMYbG9hZGJhbGFuY2VyLTkuc2lyb2UuY29tMIGfMA0GCSqGSIb3DQEB
+AQUAA4GNADCBiQKBgQCjOwa5qoaUuVnknqf5pdgAJSEoWlvx/jnUYbkSDpXLzraEiy2UhvwpoBgB
+EeTSUaPPBvboCItchakPI6Z/aFdH3Wmjuij9XD8r1C+q//7sUO0IGn0ORycddHhoo0aSdnnxGf9V
+tREaqKm9dJ7Yn7kQHjo2eryMgYxtr/Z5Il5F+wIDAQABo2AwXjARBglghkgBhvhCAQEEBAMCBkAw
+DgYDVR0PAQH/BAQDAgTwMB8GA1UdIwQYMBaAFDugITflTCfsWyNLTXDl7cMDUKuuMBgGA1UdEQQR
+MA+BDW1hbGxhQHN1bi5jb20wDQYJKoZIhvcNAQEEBQADQQB/6DOB6sRqCZu2OenM9eQR0gube85e
+nTTxU4a7x1naFxzYXK1iQ1vMARKMjDb19QEJIEJKZlDK4uS7yMlf1nFS
+                    </X509Certificate>
+                </X509Data>
+            </KeyInfo>
+        </KeyDescriptor>
+        <KeyDescriptor use="encryption">
+            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
+                <X509Data>
+                    <X509Certificate>
+MIICTDCCAfagAwIBAgICBo8wDQYJKoZIhvcNAQEEBQAwgZIxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
+EwpDYWxpZm9ybmlhMRQwEgYDVQQHEwtTYW50YSBDbGFyYTEeMBwGA1UEChMVU3VuIE1pY3Jvc3lz
+dGVtcyBJbmMuMRowGAYDVQQLExFJZGVudGl0eSBTZXJ2aWNlczEcMBoGA1UEAxMTQ2VydGlmaWNh
+dGUgTWFuYWdlcjAeFw0wNjExMDcyMzU2MTdaFw0xMDA4MDMyMzU2MTdaMCMxITAfBgNVBAMTGGxv
+YWRiYWxhbmNlci05LnNpcm9lLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAw574iRU6
+HsSO4LXW/OGTXyfsbGv6XRVOoy3v+J1pZ51KKejcDjDJXNkKGn3/356AwIaqbcymWd59T0zSqYfR
+Hn+45uyjYxRBmVJseLpVnOXLub9jsjULfGx0yjH4w+KsZSZCXatoCHbj/RJtkzuZY6V9to/hkH3S
+InQB4a3UAgMCAwEAAaNgMF4wEQYJYIZIAYb4QgEBBAQDAgZAMA4GA1UdDwEB/wQEAwIE8DAfBgNV
+HSMEGDAWgBQ7oCE35Uwn7FsjS01w5e3DA1CrrjAYBgNVHREEETAPgQ1tYWxsYUBzdW4uY29tMA0G
+CSqGSIb3DQEBBAUAA0EAMlbfBg/ff0Xkv4DOR5LEqmfTZKqgdlD81cXynfzlF7XfnOqI6hPIA90I
+x5Ql0ejivIJAYcMGUyA+/YwJg2FGoA==
+                    </X509Certificate>
+                </X509Data>
+            </KeyInfo>
+            <EncryptionMethod Algorithm=
+                "https://www.w3.org/2001/04/xmlenc#aes128-cbc">
+                <KeySize xmlns="https://www.w3.org/2001/04/xmlenc#">128</KeySize>
+            </EncryptionMethod>
+        </KeyDescriptor>
+        <SingleLogoutService
+            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+            Location="https://LoadBalancer-9.siroe.com:3443/federation/SPSloRedirect/metaAlias/sp"
+            ResponseLocation="https://LoadBalancer-9.siroe.com:3443/federation/SPSloRedirect/metaAlias/sp"/>
+        <SingleLogoutService
+            Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+            Location="https://LoadBalancer-9.siroe.com:3443/federation/SPSloSoap/metaAlias/sp"/>
+       <ManageNameIDService
+            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+            Location="https://LoadBalancer-9.siroe.com:3443/federation/SPMniRedirect/metaAlias/sp"
+            ResponseLocation="https://LoadBalancer-9.siroe.com:3443/federation/SPMniRedirect/metaAlias/sp"/>
+        <ManageNameIDService
+            Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+            Location="https://LoadBalancer-9.siroe.com:3443/federation/SPMniSoap/metaAlias/sp"
+            ResponseLocation="https://LoadBalancer-9.siroe.com:3443/federation/SPMniSoap/metaAlias/sp"/>
+        <NameIDFormat>
+            urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
+        </NameIDFormat>
+        <NameIDFormat>
+            urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+        </NameIDFormat>
+        <AssertionConsumerService
+            isDefault="true"
+            index="0"
+            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
+            Location="https://LoadBalancer-9.siroe.com:3443/federation/Consumer/metaAlias/sp"/>
+        <AssertionConsumerService
+            index="1"
+            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+            Location="https://LoadBalancer-9.siroe.com:3443/federation/Consumer/metaAlias/sp"/>
+    </SPSSODescriptor>
+</EntityDescriptor>
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>
diff --git a/wildfly/adduser/pom.xml b/wildfly/adduser/pom.xml
new file mode 100755
index 0000000..8a2bc8b
--- /dev/null
+++ b/wildfly/adduser/pom.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+~ Copyright 2013 JBoss Inc
+~
+~ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.keycloak</groupId>
+        <artifactId>keycloak-wildfly-parent</artifactId>
+        <version>1.7.0.Final-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>keycloak-wildfly-adduser</artifactId>
+    <name>Keycloak WildFly Add User Script</name>
+    <description/>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-model-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.wildfly.core</groupId>
+            <artifactId>wildfly-domain-management</artifactId>
+            <version>${wildfly.core.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.aesh</groupId>
+            <artifactId>aesh</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java b/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java
new file mode 100644
index 0000000..7be53c6
--- /dev/null
+++ b/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java
@@ -0,0 +1,292 @@
+package org.keycloak.wildfly.adduser;
+
+import org.codehaus.jackson.type.TypeReference;
+import org.jboss.aesh.cl.CommandDefinition;
+import org.jboss.aesh.cl.Option;
+import org.jboss.aesh.cl.parser.ParserGenerator;
+import org.jboss.aesh.console.command.Command;
+import org.jboss.aesh.console.command.CommandNotFoundException;
+import org.jboss.aesh.console.command.CommandResult;
+import org.jboss.aesh.console.command.container.CommandContainer;
+import org.jboss.aesh.console.command.invocation.CommandInvocation;
+import org.jboss.aesh.console.command.registry.AeshCommandRegistryBuilder;
+import org.jboss.aesh.console.command.registry.CommandRegistry;
+import org.keycloak.common.util.Base64;
+import org.keycloak.models.Constants;
+import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.util.JsonSerialization;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AddUser {
+
+    private static final String COMMAND_NAME = "add-user";
+    private static final int DEFAULT_HASH_ITERATIONS = 100000;
+
+    public static void main(String[] args) throws Exception {
+        AddUserCommand command = new AddUserCommand();
+        try {
+            ParserGenerator.parseAndPopulate(command, COMMAND_NAME, args);
+        } catch (Exception e) {
+            System.err.println(e.getMessage());
+            System.exit(1);
+        }
+
+        if (command.isContainer()) {
+            List<String> l = new LinkedList<>(Arrays.asList(args));
+            l.remove("--container");
+            args = l.toArray(new String[l.size()]);
+
+            org.jboss.as.domain.management.security.adduser.AddUser.main(args);
+        } else  if (command.isHelp()) {
+            printHelp(command);
+        } else {
+            try {
+                checkRequired(command, "user");
+                checkRequired(command, "password");
+
+                File addUserFile = getAddUserFile(command);
+
+                createUser(addUserFile, command.getRealm(), command.getUser(), command.getPassword(), command.getRoles(), command.getIterations());
+            } catch (Exception e) {
+                System.err.println(e.getMessage());
+                System.exit(1);
+            }
+        }
+    }
+
+    private static File getAddUserFile(AddUserCommand command) throws Exception {
+        File configDir;
+        if (command.isDomain()) {
+            if (command.getDc() != null) {
+                configDir = new File(command.getDc());
+            } else if (System.getProperty("jboss.domain.config.user.dir") != null) {
+                configDir = new File(System.getProperty("jboss.domain.config.user.dir"));
+            } else if (System.getenv("JBOSS_HOME") != null) {
+                configDir = new File(System.getenv("JBOSS_HOME") + File.separator + "domain" + File.separator + "configuration");
+            } else {
+                throw new Exception("Could not find domain configuration directory");
+            }
+        } else {
+            if (command.getSc() != null) {
+                configDir = new File(command.getSc());
+            } else if (System.getProperty("jboss.server.config.user.dir") != null) {
+                configDir = new File(System.getProperty("jboss.server.config.user.dir"));
+            } else if (System.getenv("JBOSS_HOME") != null) {
+                configDir = new File(System.getenv("JBOSS_HOME") + File.separator + "standalone" + File.separator + "configuration");
+            } else {
+                throw new Exception("Could not find standalone configuration directory");
+            }
+        }
+
+        if (!configDir.isDirectory()) {
+            throw new Exception("'" + configDir + "' does not exist or is not a directory");
+        }
+
+        File addUserFile = new File(configDir, "keycloak-add-user.json");
+        return addUserFile;
+    }
+
+    private static void createUser(File addUserFile, String realmName, String userName, String password, String rolesString, int iterations) throws Exception {
+        List<RealmRepresentation> realms;
+        if (addUserFile.isFile()) {
+            realms = JsonSerialization.readValue(new FileInputStream(addUserFile), new TypeReference<List<RealmRepresentation>>() {});
+        } else {
+            realms = new LinkedList<>();
+        }
+
+        if (realmName == null) {
+            realmName = "master";
+        }
+
+        RealmRepresentation realm = null;
+        for (RealmRepresentation r : realms) {
+            if (r.getRealm().equals(realmName)) {
+                realm = r;
+            }
+        }
+
+        if (realm == null) {
+            realm = new RealmRepresentation();
+            realm.setRealm(realmName);
+            realms.add(realm);
+            realm.setUsers(new LinkedList<UserRepresentation>());
+        }
+
+        for (UserRepresentation u : realm.getUsers()) {
+            if (u.getUsername().equals(userName)) {
+                throw new Exception("User with username '" + userName + "' already added to '" + addUserFile + "'");
+            }
+        }
+
+        UserRepresentation user = new UserRepresentation();
+        user.setEnabled(true);
+        user.setUsername(userName);
+        user.setCredentials(new LinkedList<CredentialRepresentation>());
+
+        byte[] salt = Pbkdf2PasswordEncoder.getSalt();
+        iterations = iterations > 0 ? iterations : DEFAULT_HASH_ITERATIONS;
+
+        CredentialRepresentation credentials = new CredentialRepresentation();
+        credentials.setType(CredentialRepresentation.PASSWORD);
+        credentials.setHashIterations(iterations);
+        credentials.setSalt(Base64.encodeBytes(salt));
+        credentials.setHashedSaltedValue(new Pbkdf2PasswordEncoder(salt).encode(password, iterations));
+
+        user.getCredentials().add(credentials);
+
+        String[] roles;
+        if (rolesString != null) {
+            roles = rolesString.split(",");
+        } else {
+            if (realmName.equals("master")) {
+                roles = new String[] { "admin" };
+            } else {
+                roles = new String[] { "realm-management/realm-admin" };
+            }
+        }
+
+        for (String r : roles) {
+            if (r.indexOf('/') != -1) {
+                String[] cr = r.split("/");
+                String client = cr[0];
+                String clientRole = cr[1];
+
+                if (user.getClientRoles() == null) {
+                    user.setClientRoles(new HashMap<String, List<String>>());
+                }
+
+                if (user.getClientRoles().get(client) == null) {
+                    user.getClientRoles().put(client, new LinkedList<String>());
+                }
+
+                user.getClientRoles().get(client).add(clientRole);
+            } else {
+                if (user.getRealmRoles() == null) {
+                    user.setRealmRoles(new LinkedList<String>());
+                }
+                user.getRealmRoles().add(r);
+            }
+        }
+
+        realm.getUsers().add(user);
+
+        JsonSerialization.writeValuePrettyToStream(new FileOutputStream(addUserFile), realms);
+        System.out.println("Added '" + userName + "' to '" + addUserFile + "', restart server to load user");
+    }
+
+    private static void checkRequired(Command command, String field) throws Exception {
+        Method m = command.getClass().getMethod("get" + Character.toUpperCase(field.charAt(0)) + field.substring(1));
+        if (m.invoke(command) == null) {
+            Option option = command.getClass().getDeclaredField(field).getAnnotation(Option.class);
+            String optionName;
+            if (option != null && option.shortName() != '\u0000') {
+                optionName = "-" + option.shortName() + ", --" + field;
+            } else {
+                optionName = "--" + field;
+            }
+            throw new Exception("Option: " + optionName + " is required");
+        }
+    }
+
+    private static void printHelp(Command command) throws CommandNotFoundException {
+        CommandRegistry registry = new AeshCommandRegistryBuilder().command(command).create();
+        CommandContainer commandContainer = registry.getCommand(command.getClass().getAnnotation(CommandDefinition.class).name(), null);
+        String help = commandContainer.printHelp(null);
+        System.out.println(help);
+    }
+
+    @CommandDefinition(name= COMMAND_NAME, description = "[options...]")
+    public static class AddUserCommand implements Command {
+
+        @Option(shortName = 'r', hasValue = true, description = "Name of realm to add user to")
+        private String realm;
+
+        @Option(shortName = 'u', hasValue = true, description = "Name of the user")
+        private String user;
+
+        @Option(shortName = 'p', hasValue = true, description = "Password of the user")
+        private String password;
+
+        @Option(hasValue = true, description = "Roles to add to the user")
+        private String roles;
+
+        @Option(hasValue = true, description = "Hash iterations")
+        private int iterations;
+
+        @Option(hasValue = false, description = "Enable domain mode")
+        private boolean domain;
+
+        @Option(hasValue = false, description = "Add user to underlying container. For usage use '--container --help'")
+        private boolean container;
+
+        @Option(hasValue = true, description = "Define the location of the server config directory")
+        private String sc;
+
+        @Option(hasValue = true, description = "Define the location of the domain config directory")
+        private String dc;
+
+        @Option(shortName = 'h', hasValue = false, description = "Display this help and exit")
+        private boolean help;
+
+        @Override
+        public CommandResult execute(CommandInvocation commandInvocation) throws IOException, InterruptedException {
+            return CommandResult.SUCCESS;
+        }
+
+        public String getRealm() {
+            return realm;
+        }
+
+        public String getUser() {
+            return user;
+        }
+
+        public String getPassword() {
+            return password;
+        }
+
+        public String getRoles() {
+            return roles;
+        }
+
+        public int getIterations() {
+            return iterations;
+        }
+
+        public boolean isDomain() {
+            return domain;
+        }
+
+        public boolean isContainer() {
+            return container;
+        }
+
+        public String getSc() {
+            return sc;
+        }
+
+        public String getDc() {
+            return dc;
+        }
+
+        public boolean isHelp() {
+            return help;
+        }
+    }
+
+}
\ No newline at end of file

wildfly/pom.xml 22(+22 -0)

diff --git a/wildfly/pom.xml b/wildfly/pom.xml
new file mode 100755
index 0000000..0d24ee8
--- /dev/null
+++ b/wildfly/pom.xml
@@ -0,0 +1,22 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.7.0.Final-SNAPSHOT</version>
+    </parent>
+
+    <name>Keycloak WildFly Integration</name>
+    <description/>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-wildfly-parent</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>adduser</module>
+        <module>extensions</module>
+        <module>server-subsystem</module>
+        <module>server-eap6-subsystem</module>
+    </modules>
+</project>