keycloak-aplcache

Changes

.gitignore 5(+5 -0)

examples/pom.xml 1(+1 -0)

Details

.gitignore 5(+5 -0)

diff --git a/.gitignore b/.gitignore
index e9d50ff..319769b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,11 @@
 .settings
 .classpath
 
+
+# NetBeans #
+############
+nb-configuration.xml
+
 # Compiled source #
 ###################
 *.com
diff --git a/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
index a5c7e97..7fa4bc7 100755
--- a/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
+++ b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
@@ -96,8 +96,8 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
         }
         Configuration invalidationCacheConfiguration = invalidationConfigBuilder.build();
 
-        cacheManager.defineConfiguration("realms", invalidationCacheConfiguration);
-        cacheManager.defineConfiguration("users", invalidationCacheConfiguration);
+        cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationCacheConfiguration);
+        cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_CACHE_NAME, invalidationCacheConfiguration);
 
         ConfigurationBuilder sessionConfigBuilder = new ConfigurationBuilder();
         if (clustered) {
@@ -115,7 +115,9 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
                     .numSegments(config.getInt("sessionsSegments", 60)).build();
         }
 
-        cacheManager.defineConfiguration("sessions", sessionConfigBuilder.build());
+        Configuration sessionCacheConfiguration = sessionConfigBuilder.build();
+        cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCacheConfiguration);
+        cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfiguration);
     }
 
 }
diff --git a/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
index 098d00a..05cfdb9 100644
--- a/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
+++ b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
@@ -8,6 +8,11 @@ import org.keycloak.provider.Provider;
  */
 public interface InfinispanConnectionProvider extends Provider {
 
+    static final String REALM_CACHE_NAME = "realms";
+    static final String USER_CACHE_NAME = "users";
+    static final String SESSION_CACHE_NAME = "sessions";
+    static final String LOGIN_FAILURE_CACHE_NAME = "loginFailures";
+
     <K, V> Cache<K, V> getCache(String name);
 
 }
diff --git a/connections/jpa-liquibase/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/AddRealmCodeSecret.java b/connections/jpa-liquibase/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/AddRealmCodeSecret.java
new file mode 100644
index 0000000..76a02a7
--- /dev/null
+++ b/connections/jpa-liquibase/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/AddRealmCodeSecret.java
@@ -0,0 +1,79 @@
+package org.keycloak.connections.jpa.updater.liquibase.custom;
+
+import liquibase.change.custom.CustomSqlChange;
+import liquibase.database.Database;
+import liquibase.database.jvm.JdbcConnection;
+import liquibase.exception.CustomChangeException;
+import liquibase.exception.SetupException;
+import liquibase.exception.ValidationErrors;
+import liquibase.resource.ResourceAccessor;
+import liquibase.statement.SqlStatement;
+import liquibase.statement.core.UpdateStatement;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.util.ArrayList;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AddRealmCodeSecret implements CustomSqlChange {
+
+    private String confirmationMessage;
+
+    @Override
+    public SqlStatement[] generateStatements(Database database) throws CustomChangeException {
+        try {
+            StringBuilder sb = new StringBuilder();
+            sb.append("Generated codeSecret for realms: ");
+
+            Connection connection = ((JdbcConnection) (database.getConnection())).getWrappedConnection();
+            ResultSet resultSet = connection.createStatement().executeQuery("SELECT ID FROM REALM WHERE CODE_SECRET IS NULL");
+
+            ArrayList<SqlStatement> statements = new ArrayList<SqlStatement>();
+            while (resultSet.next()) {
+                String id = resultSet.getString(1);
+
+                UpdateStatement statement = new UpdateStatement(null, null, "REALM")
+                        .addNewColumnValue("CODE_SECRET", KeycloakModelUtils.generateCodeSecret())
+                        .setWhereClause("ID='" + id + "'");
+                statements.add(statement);
+
+                if (!resultSet.isFirst()) {
+                    sb.append(", ");
+                }
+                sb.append(id);
+            }
+
+            if (!statements.isEmpty()) {
+                confirmationMessage = sb.toString();
+            }
+
+            return statements.toArray(new SqlStatement[statements.size()]);
+        } catch (Exception e) {
+            throw new CustomChangeException("Failed to add realm code secret", e);
+        }
+    }
+
+    @Override
+    public String getConfirmationMessage() {
+        return confirmationMessage;
+    }
+
+    @Override
+    public void setUp() throws SetupException {
+
+    }
+
+    @Override
+    public void setFileOpener(ResourceAccessor resourceAccessor) {
+
+    }
+
+    @Override
+    public ValidationErrors validate(Database database) {
+        return null;
+    }
+
+}
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.1.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.1.0.Beta1.xml
index 8344266..c94b206 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.1.0.Beta1.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.1.0.Beta1.xml
@@ -43,9 +43,10 @@
         </addColumn>
         <addColumn tableName="REALM">
             <column name="CERTIFICATE" type="VARCHAR(2048)"/>
+            <column name="CODE_SECRET" type="VARCHAR(255)"/>
         </addColumn>
         <addColumn tableName="CLIENT">
-            <column name="NODE_REREG_TIMEOUT" type="INT"/>
+            <column name="NODE_REREG_TIMEOUT" type="INT" defaultValue="0"/>
         </addColumn>
         <addPrimaryKey columnNames="CLIENT_ID, NAME" constraintName="CONSTRAINT_3C" tableName="CLIENT_ATTRIBUTES"/>
         <addPrimaryKey columnNames="CLIENT_SESSION, NAME" constraintName="CONSTRAINT_5E" tableName="CLIENT_SESSION_NOTE"/>
@@ -53,5 +54,6 @@
         <addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="CLIENT_ATTRIBUTES" constraintName="FK3C47C64BEACCA966" referencedColumnNames="ID" referencedTableName="CLIENT"/>
         <addForeignKeyConstraint baseColumnNames="CLIENT_SESSION" baseTableName="CLIENT_SESSION_NOTE" constraintName="FK5EDFB00FF51C2736" referencedColumnNames="ID" referencedTableName="CLIENT_SESSION"/>
         <addForeignKeyConstraint baseColumnNames="APPLICATION_ID" baseTableName="APP_NODE_REGISTRATIONS" constraintName="FK8454723BA992F594" referencedColumnNames="ID" referencedTableName="CLIENT"/>
+        <customChange class="org.keycloak.connections.jpa.updater.liquibase.custom.AddRealmCodeSecret"/>
     </changeSet>
 </databaseChangeLog>
\ No newline at end of file
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update1_1_0_Beta1.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update1_1_0_Beta1.java
index 89d372f..f080244 100644
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update1_1_0_Beta1.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update1_1_0_Beta1.java
@@ -1,5 +1,13 @@
 package org.keycloak.connections.mongo.updater.updates;
 
+import com.mongodb.DBCollection;
+import com.mongodb.DBCursor;
+import com.mongodb.DBObject;
+import com.mongodb.QueryBuilder;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import java.util.Arrays;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -14,6 +22,24 @@ public class Update1_1_0_Beta1 extends Update {
     public void update() {
         deleteEntries("clientSessions");
         deleteEntries("sessions");
+
+        addRealmCodeSecret();
+    }
+
+    private void addRealmCodeSecret() {
+        DBCollection realms = db.getCollection("realms");
+
+        DBObject query = new QueryBuilder()
+                .and("codeSecret").is(null).get();
+
+        DBCursor objects = realms.find(query);
+        while (objects.hasNext()) {
+            DBObject object = objects.next();
+            object.put("codeSecret", KeycloakModelUtils.generateCodeSecret());
+            realms.save(object);
+
+            log.debugv("Added realm.codeSecret, id={0}", object.get("id"));
+        }
     }
 
 }
diff --git a/core/src/main/java/org/keycloak/KeycloakSecurityContext.java b/core/src/main/java/org/keycloak/KeycloakSecurityContext.java
index 4658c68..830a34e 100755
--- a/core/src/main/java/org/keycloak/KeycloakSecurityContext.java
+++ b/core/src/main/java/org/keycloak/KeycloakSecurityContext.java
@@ -48,6 +48,10 @@ public class KeycloakSecurityContext implements Serializable {
         return idTokenString;
     }
 
+    public String getRealm() {
+        // Assumption that issuer contains realm name
+        return token.getIssuer();
+    }
 
     // SERIALIZATION
 
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 3346371..b0a0a21 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -44,6 +44,7 @@ public class RealmRepresentation {
     protected String privateKey;
     protected String publicKey;
     protected String certificate;
+    protected String codeSecret;
     protected RolesRepresentation roles;
     protected List<String> defaultRoles;
     protected Set<String> requiredCredentials;
@@ -229,6 +230,14 @@ public class RealmRepresentation {
         this.certificate = certificate;
     }
 
+    public String getCodeSecret() {
+        return codeSecret;
+    }
+
+    public void setCodeSecret(String codeSecret) {
+        this.codeSecret = codeSecret;
+    }
+
     public Boolean isPasswordCredentialGrantAllowed() {
         return passwordCredentialGrantAllowed;
     }
diff --git a/core/src/test/java/org/keycloak/SkeletonKeyTokenTest.java b/core/src/test/java/org/keycloak/SkeletonKeyTokenTest.java
index f3a89b6..f2ec502 100755
--- a/core/src/test/java/org/keycloak/SkeletonKeyTokenTest.java
+++ b/core/src/test/java/org/keycloak/SkeletonKeyTokenTest.java
@@ -96,12 +96,14 @@ public class SkeletonKeyTokenTest {
         Assert.assertTrue(token.getResourceAccess("foo").isUserInRole("admin"));
         Assert.assertTrue(token.getResourceAccess("bar").isUserInRole("user"));
         Assert.assertEquals("joe@email.cz", idToken.getEmail());
+        Assert.assertEquals("acme", ctx.getRealm());
         ois.close();
     }
 
     private AccessToken createSimpleToken() {
         AccessToken token = new AccessToken();
         token.id("111");
+        token.issuer("acme");
         token.addAccess("foo").addRole("admin");
         token.addAccess("bar").addRole("user");
         return token;
diff --git a/distribution/examples-docs-zip/build.xml b/distribution/examples-docs-zip/build.xml
index 3e4f1dc..a33d88d 100755
--- a/distribution/examples-docs-zip/build.xml
+++ b/distribution/examples-docs-zip/build.xml
@@ -42,6 +42,14 @@
                 <exclude name="**/subsystem-config.xml"/>
             </fileset>
         </copy>
+        <copy todir="target/examples/multi-tenant" overwrite="true">
+            <fileset dir="../../examples/multi-tenant">
+                <exclude name="**/target/**"/>
+                <exclude name="**/*.iml"/>
+                <exclude name="**/*.unconfigured"/>
+                <exclude name="**/subsystem-config.xml"/>
+            </fileset>
+        </copy>
         <copy todir="target/examples/themes" overwrite="true">
             <fileset dir="../../examples/themes">
                 <exclude name="**/target/**"/>
diff --git a/docbook/reference/en/en-US/master.xml b/docbook/reference/en/en-US/master.xml
index 6a70415..db6dead 100755
--- a/docbook/reference/en/en-US/master.xml
+++ b/docbook/reference/en/en-US/master.xml
@@ -36,6 +36,7 @@
                 <!ENTITY SecurityVulnerabilities SYSTEM "modules/security-vulnerabilities.xml">
                 <!ENTITY Clustering SYSTEM "modules/clustering.xml">
                 <!ENTITY ApplicationClustering SYSTEM "modules/application-clustering.xml">
+                <!ENTITY MultiTenancy SYSTEM "modules/multi-tenancy.xml">
                 ]>
 
 <book>
@@ -88,6 +89,7 @@ This one is short
         &JavascriptAdapter;
         &InstalledApplications;
         &Logout;
+        &MultiTenancy;
     </chapter>
 
     <chapter>
diff --git a/docbook/reference/en/en-US/modules/clustering.xml b/docbook/reference/en/en-US/modules/clustering.xml
index 613316f..8c24dea 100755
--- a/docbook/reference/en/en-US/modules/clustering.xml
+++ b/docbook/reference/en/en-US/modules/clustering.xml
@@ -53,7 +53,7 @@
             realm keys and password hashes) from being sent between the nodes.
         </para>
         <para>
-            User sessions supports either distributed caches or fully replicated caches. We recommend using a distributed
+            User sessions and login failures supports either distributed caches or fully replicated caches. We recommend using a distributed
             cache.
         </para>
         <para>
@@ -65,6 +65,7 @@
         <invalidation-cache name="realms" mode="SYNC"/>
         <invalidation-cache name="users" mode="SYNC"/>
         <distributed-cache name="sessions" mode="SYNC" owners="1" />
+        <distributed-cache name="loginFailures" mode="SYNC" owners="1" />
     </cache-container>
     ...
 </subsystem>
diff --git a/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml b/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml
index af5d66c..09109b3 100755
--- a/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml
+++ b/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml
@@ -1,134 +1,215 @@
 <chapter id="Migration_from_older_versions">
     <title>Migration from older versions</title>
-    <sect1>
-        <title>Migrating from 1.0.x.Final to 1.1.Beta1</title>
-        <itemizedlist>
-            <listitem>UserSessionModel JPA and Mongo storage schema has changed as these interfaces have been refactored</listitem>
-            <listitem>
-                Upgrade your adapters, old adapters are not compatible with Keycloak 1.1.  We interpreted JSON Web Token and OIDC ID Token specification incorrectly.  'aud'
-                claim must be the client id, we were storing the realm name in there and validating it.
-            </listitem>
-        </itemizedlist>
-    </sect1>
-    <sect1>
-        <title>Migrating from 1.0 RC-1 to RC-2</title>
-        <itemizedlist>
-            <listitem>A lot of info level logging has been changed to debug. Also, a realm no longer has the jboss-logging audit listener by default.
-                If you want log output when users login, logout, change passwords, etc. enable the jboss-logging audit listener through the admin console.</listitem>
-        </itemizedlist>
-    </sect1>
-    <sect1>
-        <title>Migrating from 1.0 Beta 4 to RC-1</title>
-        <itemizedlist>
-            <listitem>
-                logout REST API has been refactored.  The GET request on the logout URI does not take a session_state
-                parameter anymore.  You must be logged in in order to log out the session.
-                You can also POST to the logout REST URI.  This action requires a valid refresh token to perform the logout.
-                The signature is the same as refresh token minus the grant type form parameter.  See documentation for details.
-            </listitem>
-         </itemizedlist>
-    </sect1>
-    <sect1>
-        <title>Migrating from 1.0 Beta 1 to Beta 4</title>
-        <itemizedlist>
-            <listitem>
-                LDAP/AD configuration is changed.  It is no longer under the "Settings" page.  It is now under
-                Users->Federation.  Add Provider will show you an "ldap" option.
-            </listitem>
-            <listitem>
-                Authentication SPI has been removed and rewritten.  The new SPI is UserFederationProvider and is
-                more flexible.
-            </listitem>
-            <listitem>
-                <literal>ssl-not-required</literal> property in adapter config has been removed. Replaced with
-                <literal>ssl-required</literal>, valid values are <literal>all</literal> (require SSL for all requests), <literal>external</literal>
-                (require SSL only for external request) and <literal>none</literal> (SSL not required).
-            </listitem>
-            <listitem>
-                DB Schema has changed again.
-            </listitem>
-            <listitem>
-                Created applications now have a full scope by default.  This means that you don't have to configure
-                the scope of an application if you don't want to.
-            </listitem>
-            <listitem>
-                Format of JSON file for importing realm data was changed. Now role mappings is available under the JSON record of particular
-                user.
-            </listitem>
-        </itemizedlist>
-    </sect1>
-    <sect1>
-        <title>Migrating from 1.0 Alpha 4 to Beta 1</title>
-        <itemizedlist>
-            <listitem>
-                DB Schema has changed.  We have added export of the database to Beta 1, but not the ability to import
-                the database from older versions. This will be supported in future releases.
-            </listitem>
-            <listitem>
-                For all clients except bearer-only applications, you must specify at least one redirect uri.  Keycloak
-                will not allow you to log in unless you have specified a valid redirect uri for that application.
-            </listitem>
-            <listitem>
-                Resource Owner Password Credentials flow is now disabled by default. It can be enabled by setting the toggle
-                for <literal>Direct Grant API</literal> <literal>ON</literal> under realm config in the admin console.
-            </listitem>
-            <listitem>
-                Configuration is now done through <literal>standalone/configuration/keycloak-server.json</literal>. This
-                should mainly affect those that use MongoDB.
-            </listitem>
-            <listitem>
-                JavaScript adapter has been refactored. See the <link linkend='javascript-adapter'>JavaScript adapter</link> section for more details.
-            </listitem>
-            <listitem>
-                The "Central Login Lifespan" setting no longer exists.  Please see the <link linkend='session-timeouts'>Session Timeout</link> section
-                for me details.
-            </listitem>
-        </itemizedlist>
-    </sect1>
-    <sect1>
-        <title>Migrating from 1.0 Alpha 2 to Alpha 3</title>
-        <itemizedlist>
-            <listitem>
-                SkeletonKeyToken, SkeletonKeyScope, SkeletonKeyPrincipal, and SkeletonKeySession have been renamed to:
-                AccessToken, AccessScope, KeycloakPrincipal, and KeycloakAuthenticatedSession respectively.
-            </listitem>
-            <listitem>
-                ServleOAuthClient.getBearerToken() method signature has changed.  It now returns an AccessTokenResponse
-                so that you can obtain a refresh token too.
-            </listitem>
-            <listitem>
-                Adapters now check the access token expiration with every request.  If the token is expired, they will
-                attempt to invoke a refresh on the auth server using a saved refresh token.
-            </listitem>
-            <listitem>
-                Subject in AccessToken has been changed to the User ID.
-            </listitem>
-        </itemizedlist>
-    </sect1>
-    <sect1>
-        <title>Migrating from 1.0 Alpha 1 to Alpha 2</title>
-        <itemizedlist>
-            <listitem>
-                DB Schema has changed.  We don't have any data migration utilities yet as of Alpha 2.
-            </listitem>
-            <listitem>
-                JBoss and Wildfly adapters are now installed via a JBoss/Wildfly subsystem.  Please review the adapter
-                installation documentation.  Edits to standalone.xml are now required.
-            </listitem>
-            <listitem>
-                There is a new credential type "secret".  Unlike other credential types, it is stored in plain text in
-                the database and can be viewed in the admin console.
 
-            </listitem>
-            <listitem>
-                There is no longer required Application or OAuth Client credentials.  These client types are now
-                hard coded to use the "secret" credential type.
-            </listitem>
-            <listitem>
-                Because of the "secret" credential change to Application and OAuth Client, you'll have to update
-                your keycloak.json configuration files and regenarate a secret within the Application or OAuth Client
-                credentials tab in the administration console.
-            </listitem>
-        </itemizedlist>
-    </sect1>
+    <para>
+        To upgrade to a new version of Keycloak first download and install the new version of Keycloak. You then have to
+        migrate the database, keycloak-server.json, providers, themes and applications from the old version.
+    </para>
+
+    <section>
+        <title>Migrate database</title>
+        <para>
+            Keycloak provides automatic migration of the database. It's highly recommended that you backup your
+            database prior to upgrading Keycloak.
+        </para>
+        <para>
+            To enable automatic upgrading of the database if you're using a relational database make sure
+            <literal>databaseSchema</literal> is set to <literal>update</literal> for <literal>connectionsJpa</literal>:
+<programlisting>
+"connectionsJpa": {
+    "default": {
+        ...
+        "databaseSchema": "update"
+    }
+}
+</programlisting>
+        </para>
+        <para>
+            For MongoDB do the same, but for <literal>connectionsMongo</literal>:
+<programlisting>
+"connectionsMongo": {
+    "default": {
+        ...
+        "databaseSchema": "update"
+    }
+}
+</programlisting>
+        </para>
+        <para>
+            When you start the server with this setting your database will automatically be migrated if the database
+            schema has changed in the new version.
+        </para>
+    </section>
+
+    <section>
+        <title>Migrate keycloak-server.json</title>
+        <para>
+            You should copy <literal>standalone/configuration/keycloak-server.json</literal> from the old version to
+            make sure any configuration changes you've done are added to the new installation. The version specific
+            section below will list any changes done to this file that you have to do when upgrading from one version
+            to another.
+        </para>
+    </section>
+
+    <section>
+        <title>Migrate providers</title>
+        <para>
+            If you have implemented any SPI providers you need to copy them to the new server. The version
+            specific section below will mention if any of the SPI's have changed. If they have you may have to update
+            your code accordingly.
+        </para>
+    </section>
+
+    <section>
+        <title>Migrate themes</title>
+        <para>
+            If you have created a custom theme you need to copy them to the new server. The version specific section below
+            will mention if changes have been made to themes. If there is you may have to update your themes accordingly.
+        </para>
+    </section>
+
+    <section>
+        <title>Migrate application</title>
+        <para>
+            If you deploy applications directly to the Keycloak server you should copy them to the new server. For any
+            applications including those not deployed directly to the Keycloak server you should upgrade the adapter.
+            The version specific section below will mention if any changes are required to applications.
+        </para>
+    </section>
+
+    <section>
+        <title>Version specific migration</title>
+        <section>
+            <title>Migrating from 1.0.x.Final to 1.1.Beta1</title>
+            <itemizedlist>
+                <listitem>RealmModel JPA and Mongo storage schema has changed</listitem>
+                <listitem>UserSessionModel JPA and Mongo storage schema has changed as these interfaces have been refactored</listitem>
+                <listitem>
+                    Upgrade your adapters, old adapters are not compatible with Keycloak 1.1.  We interpreted JSON Web Token and OIDC ID Token specification incorrectly.  'aud'
+                    claim must be the client id, we were storing the realm name in there and validating it.
+                </listitem>
+            </itemizedlist>
+        </section>
+        <section>
+            <title>Migrating from 1.0 RC-1 to RC-2</title>
+            <itemizedlist>
+                <listitem>A lot of info level logging has been changed to debug. Also, a realm no longer has the jboss-logging audit listener by default.
+                    If you want log output when users login, logout, change passwords, etc. enable the jboss-logging audit listener through the admin console.</listitem>
+            </itemizedlist>
+        </section>
+        <section>
+            <title>Migrating from 1.0 Beta 4 to RC-1</title>
+            <itemizedlist>
+                <listitem>
+                    logout REST API has been refactored.  The GET request on the logout URI does not take a session_state
+                    parameter anymore.  You must be logged in in order to log out the session.
+                    You can also POST to the logout REST URI.  This action requires a valid refresh token to perform the logout.
+                    The signature is the same as refresh token minus the grant type form parameter.  See documentation for details.
+                </listitem>
+             </itemizedlist>
+        </section>
+        <section>
+            <title>Migrating from 1.0 Beta 1 to Beta 4</title>
+            <itemizedlist>
+                <listitem>
+                    LDAP/AD configuration is changed.  It is no longer under the "Settings" page.  It is now under
+                    Users->Federation.  Add Provider will show you an "ldap" option.
+                </listitem>
+                <listitem>
+                    Authentication SPI has been removed and rewritten.  The new SPI is UserFederationProvider and is
+                    more flexible.
+                </listitem>
+                <listitem>
+                    <literal>ssl-not-required</literal> property in adapter config has been removed. Replaced with
+                    <literal>ssl-required</literal>, valid values are <literal>all</literal> (require SSL for all requests), <literal>external</literal>
+                    (require SSL only for external request) and <literal>none</literal> (SSL not required).
+                </listitem>
+                <listitem>
+                    DB Schema has changed again.
+                </listitem>
+                <listitem>
+                    Created applications now have a full scope by default.  This means that you don't have to configure
+                    the scope of an application if you don't want to.
+                </listitem>
+                <listitem>
+                    Format of JSON file for importing realm data was changed. Now role mappings is available under the JSON record of particular
+                    user.
+                </listitem>
+            </itemizedlist>
+        </section>
+        <section>
+            <title>Migrating from 1.0 Alpha 4 to Beta 1</title>
+            <itemizedlist>
+                <listitem>
+                    DB Schema has changed.  We have added export of the database to Beta 1, but not the ability to import
+                    the database from older versions. This will be supported in future releases.
+                </listitem>
+                <listitem>
+                    For all clients except bearer-only applications, you must specify at least one redirect uri.  Keycloak
+                    will not allow you to log in unless you have specified a valid redirect uri for that application.
+                </listitem>
+                <listitem>
+                    Resource Owner Password Credentials flow is now disabled by default. It can be enabled by setting the toggle
+                    for <literal>Direct Grant API</literal> <literal>ON</literal> under realm config in the admin console.
+                </listitem>
+                <listitem>
+                    Configuration is now done through <literal>standalone/configuration/keycloak-server.json</literal>. This
+                    should mainly affect those that use MongoDB.
+                </listitem>
+                <listitem>
+                    JavaScript adapter has been refactored. See the <link linkend='javascript-adapter'>JavaScript adapter</link> section for more details.
+                </listitem>
+                <listitem>
+                    The "Central Login Lifespan" setting no longer exists.  Please see the <link linkend='session-timeouts'>Session Timeout</link> section
+                    for me details.
+                </listitem>
+            </itemizedlist>
+        </section>
+        <section>
+            <title>Migrating from 1.0 Alpha 2 to Alpha 3</title>
+            <itemizedlist>
+                <listitem>
+                    SkeletonKeyToken, SkeletonKeyScope, SkeletonKeyPrincipal, and SkeletonKeySession have been renamed to:
+                    AccessToken, AccessScope, KeycloakPrincipal, and KeycloakAuthenticatedSession respectively.
+                </listitem>
+                <listitem>
+                    ServleOAuthClient.getBearerToken() method signature has changed.  It now returns an AccessTokenResponse
+                    so that you can obtain a refresh token too.
+                </listitem>
+                <listitem>
+                    Adapters now check the access token expiration with every request.  If the token is expired, they will
+                    attempt to invoke a refresh on the auth server using a saved refresh token.
+                </listitem>
+                <listitem>
+                    Subject in AccessToken has been changed to the User ID.
+                </listitem>
+            </itemizedlist>
+        </section>
+        <section>
+            <title>Migrating from 1.0 Alpha 1 to Alpha 2</title>
+            <itemizedlist>
+                <listitem>
+                    DB Schema has changed.  We don't have any data migration utilities yet as of Alpha 2.
+                </listitem>
+                <listitem>
+                    JBoss and Wildfly adapters are now installed via a JBoss/Wildfly subsystem.  Please review the adapter
+                    installation documentation.  Edits to standalone.xml are now required.
+                </listitem>
+                <listitem>
+                    There is a new credential type "secret".  Unlike other credential types, it is stored in plain text in
+                    the database and can be viewed in the admin console.
+
+                </listitem>
+                <listitem>
+                    There is no longer required Application or OAuth Client credentials.  These client types are now
+                    hard coded to use the "secret" credential type.
+                </listitem>
+                <listitem>
+                    Because of the "secret" credential change to Application and OAuth Client, you'll have to update
+                    your keycloak.json configuration files and regenarate a secret within the Application or OAuth Client
+                    credentials tab in the administration console.
+                </listitem>
+            </itemizedlist>
+        </section>
+    </section>
 </chapter>
\ No newline at end of file
diff --git a/docbook/reference/en/en-US/modules/multi-tenancy.xml b/docbook/reference/en/en-US/modules/multi-tenancy.xml
new file mode 100644
index 0000000..410621f
--- /dev/null
+++ b/docbook/reference/en/en-US/modules/multi-tenancy.xml
@@ -0,0 +1,56 @@
+<section id="multi_tenancy">
+    <title>Multi Tenancy</title>
+    <para>
+        Multi Tenancy, in our context, means that one single target application (WAR) can be secured by a single (or clustered) Keycloak server, authenticating
+        its users against different realms. In practice, this means that one application needs to use different <literal>keycloak.json</literal> files.
+        For this case, there are two possible solutions:
+        <itemizedlist>
+
+            <listitem>
+                The same WAR file deployed under two different names, each with its own Keycloak configuration (probably via the Keycloak Subsystem).
+                This scenario is suitable when the number of realms is known in advance or when there's a dynamic provision of application instances.
+                One example would be a service provider that dinamically creates servers/deployments for their clients, like a PaaS.
+            </listitem>
+
+            <listitem>
+                A WAR file deployed once (possibly in a cluster), that decides which realm to authenticate against based on the request parameters.
+                This scenario is suitable when there are an undefined number of realms. One example would be a SaaS provider that have only one deployment
+                (perhaps in a cluster) serving several companies, differentiating between clients based on the hostname
+                (<literal>client1.acme.com</literal>, <literal>client2.acme.com</literal>) or path (<literal>/app/client1/</literal>,
+                <literal>/app/client2/</literal>) or even via a special HTTP Header.
+            </listitem>
+
+        </itemizedlist>
+
+        This chapter of the reference guide focus on this second scenario.
+    </para>
+
+    <para>
+        Keycloak provides an extension point for applications that need to evaluate the realm on a request basis. During the authentication
+        and authorization phase of the incoming request, Keycloak queries the application via this extension point and expects the application
+        to return a complete representation of the realm. With this, Keycloak then proceeds the authentication and authorization process,
+        accepting or refusing the request based on the incoming credentials and on the returned realm.
+
+        For this scenario, an application needs to:
+
+        <itemizedlist>
+
+            <listitem>
+                Add a context parameter to the <literal>web.xml</literal>, named <literal>keycloak.config.resolver</literal>.
+                The value of this property should be the fully qualified name of the a class extending
+                <literal>org.keycloak.adapters.KeycloakConfigResolver</literal>.
+            </listitem>
+
+            <listitem>
+                A concrete implementation of <literal>org.keycloak.adapters.KeycloakConfigResolver</literal>. Keycloak will call the
+                <literal>resolve(org.keycloak.adapters.HttpFacade.Request)</literal> method and expects a complete
+                <literal>org.keycloak.adapters.KeycloakDeployment</literal> in response. Note that Keycloak will call this for every request,
+                so, take the usual performance precautions.
+            </listitem>
+
+        </itemizedlist>
+    </para>
+    <para>
+        An implementation of this feature can be found on the examples.
+    </para>
+</section>
diff --git a/events/api/src/main/java/org/keycloak/events/EventType.java b/events/api/src/main/java/org/keycloak/events/EventType.java
index d41fb81..cd624b4 100755
--- a/events/api/src/main/java/org/keycloak/events/EventType.java
+++ b/events/api/src/main/java/org/keycloak/events/EventType.java
@@ -40,6 +40,8 @@ public enum EventType {
     SEND_VERIFY_EMAIL_ERROR,
     SEND_RESET_PASSWORD,
     SEND_RESET_PASSWORD_ERROR,
+    RESET_PASSWORD,
+    RESET_PASSWORD_ERROR,
 
     INVALID_SIGNATURE_ERROR,
     REGISTER_NODE,
diff --git a/examples/cors/pom.xml b/examples/cors/pom.xml
index 0317ffa..9b1c048 100755
--- a/examples/cors/pom.xml
+++ b/examples/cors/pom.xml
@@ -6,7 +6,7 @@
         <version>1.1.0-Alpha1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
-    <name>Examples</name>
+    <name>Keycloak Examples - CORS</name>
     <description/>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/examples/multi-tenant/pom.xml b/examples/multi-tenant/pom.xml
new file mode 100644
index 0000000..653632c
--- /dev/null
+++ b/examples/multi-tenant/pom.xml
@@ -0,0 +1,65 @@
+<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>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.1.0-Alpha1-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <name>Keycloak Examples - Multi Tenant</name>
+    <artifactId>multitenant</artifactId>
+    <packaging>war</packaging>
+
+    <description>
+        Keycloak Multi Tenants Example
+    </description>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.wildfly.bom</groupId>
+                <artifactId>jboss-javaee-7.0-with-all</artifactId>
+                <version>8.0.0.Final</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <dependency>
+                <groupId>org.keycloak</groupId>
+                <artifactId>keycloak-core</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.keycloak</groupId>
+                <artifactId>keycloak-adapter-core</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.jboss.spec.javax.servlet</groupId>
+            <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Contains KeycloakDeployment and KeycloakConfigResolver -->
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-core</artifactId>
+        </dependency>
+
+        <!-- Contains KeycloakPrincipal -->
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+        </dependency>
+    </dependencies>
+    <build>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+</project>
+
diff --git a/examples/multi-tenant/README.md b/examples/multi-tenant/README.md
new file mode 100644
index 0000000..8dd3c03
--- /dev/null
+++ b/examples/multi-tenant/README.md
@@ -0,0 +1,32 @@
+Keycloak Example - Multi Tenancy
+=======================================
+
+The following example was tested on Wildfly 8.1.0.Final and JBoss EAP 6.3. It should be compatible with any JBoss AS, JBoss EAP or Wildfly that supports Java EE 7.
+
+This example demonstrates the simplest possible scenario for Keycloak Multi Tenancy support. Multi Tenancy is understood on this context as a single application (WAR) that is deployed on a single or clustered application server, authenticating users from *different realms* against a single or clustered Keycloak server.
+
+The multi tenancy is achieved by having one realm per tenant on the server side and a per-request decision on which realm to authenticate the request against.
+
+This example contains only the minimal bits required for a multi tenant application.
+
+This example is composed of the following parts:
+
+- ProtectedServlet - A servlet that displays the username and realm from the current user
+- PathBasedKeycloakConfigResolver - A configuration resolver that takes the realm based on the path: /simple-multitenant/tenant2 means that the realm is "tenant2".
+
+Step 1: Setup a basic Keycloak server
+--------------------------------------------------------------
+Install Keycloak server and start it on port 8080. Check the Reference Guide if unsure on how to do it.
+
+Once the Keycloak server is up and running, import the two realms from "src/main/resources/", namely:
+
+- tenant1-realm.json
+- tenant2-realm.json
+
+Step 2: Deploy and run the example
+--------------------------------------------------------------
+
+- Build and deploy this sample's WAR file. For this example, deploy on the same server that is running the Keycloak Server, although this is not required for real world scenarios.
+- Access [http://localhost:8080/multitenant/tenant1](http://localhost:8080/multitenant/tenant1) and login as ``user-tenant1``, password ``user-tenant1``
+- Access [http://localhost:8080/multitenant/tenant2](http://localhost:8080/multitenant/tenant2) and login as ``user-tenant2``, password ``user-tenant2``
+
diff --git a/examples/multi-tenant/src/main/java/org/keycloak/example/multitenant/boundary/ProtectedServlet.java b/examples/multi-tenant/src/main/java/org/keycloak/example/multitenant/boundary/ProtectedServlet.java
new file mode 100644
index 0000000..991169d
--- /dev/null
+++ b/examples/multi-tenant/src/main/java/org/keycloak/example/multitenant/boundary/ProtectedServlet.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.example.multitenant.boundary;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.keycloak.KeycloakPrincipal;
+
+/**
+ *
+ * @author Juraci Paixão Kröhling <juraci at kroehling.de>
+ */
+@WebServlet(urlPatterns = "/*")
+public class ProtectedServlet extends HttpServlet {
+
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        String realm = req.getPathInfo().split("/")[1];
+        if (realm.contains("?")) {
+            realm = realm.split("\\?")[0];
+        }
+
+        if (req.getPathInfo().contains("logout")) {
+            req.logout();
+            resp.sendRedirect(req.getContextPath() + "/" + realm);
+            return;
+        }
+
+        KeycloakPrincipal principal = (KeycloakPrincipal) req.getUserPrincipal();
+
+        resp.setContentType("text/html");
+        PrintWriter writer = resp.getWriter();
+
+        writer.write("Realm: ");
+        writer.write(principal.getKeycloakSecurityContext().getIdToken().getIssuer());
+
+        writer.write("<br/>User: ");
+        writer.write(principal.getKeycloakSecurityContext().getIdToken().getPreferredUsername());
+
+        writer.write(String.format("<br/><a href=\"/multitenant/%s/logout\">Logout</a>", realm));
+    }
+ }
diff --git a/examples/multi-tenant/src/main/java/org/keycloak/example/multitenant/control/PathBasedKeycloakConfigResolver.java b/examples/multi-tenant/src/main/java/org/keycloak/example/multitenant/control/PathBasedKeycloakConfigResolver.java
new file mode 100644
index 0000000..4aa2fea
--- /dev/null
+++ b/examples/multi-tenant/src/main/java/org/keycloak/example/multitenant/control/PathBasedKeycloakConfigResolver.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.example.multitenant.control;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.KeycloakConfigResolver;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+
+/**
+ *
+ * @author Juraci Paixão Kröhling <juraci at kroehling.de>
+ */
+public class PathBasedKeycloakConfigResolver implements KeycloakConfigResolver {
+
+    private final Map<String, KeycloakDeployment> cache = new ConcurrentHashMap<String, KeycloakDeployment>();
+
+    @Override
+    public KeycloakDeployment resolve(HttpFacade.Request request) {
+        String path = request.getURI();
+        String realm = path.substring(path.indexOf("multitenant/")).split("/")[1];
+        if (realm.contains("?")) {
+            realm = realm.split("\\?")[0];
+        }
+
+        KeycloakDeployment deployment = cache.get(realm);
+        if (null == deployment) {
+            // not found on the simple cache, try to load it from the file system
+            InputStream is = getClass().getResourceAsStream("/" + realm + "-keycloak.json");
+            deployment = KeycloakDeploymentBuilder.build(is);
+            cache.put(realm, deployment);
+        }
+
+        return deployment;
+    }
+
+}
diff --git a/examples/multi-tenant/src/main/resources/tenant1-keycloak.json b/examples/multi-tenant/src/main/resources/tenant1-keycloak.json
new file mode 100644
index 0000000..57be277
--- /dev/null
+++ b/examples/multi-tenant/src/main/resources/tenant1-keycloak.json
@@ -0,0 +1,10 @@
+{
+  "realm" : "tenant1",
+  "resource" : "multi-tenant",
+  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+  "auth-server-url" : "http://localhost:8080/auth",
+  "ssl-required" : "external",
+  "credentials" : {
+      "secret": "password"
+   }
+}
\ No newline at end of file
diff --git a/examples/multi-tenant/src/main/resources/tenant2-keycloak.json b/examples/multi-tenant/src/main/resources/tenant2-keycloak.json
new file mode 100644
index 0000000..4f221dc
--- /dev/null
+++ b/examples/multi-tenant/src/main/resources/tenant2-keycloak.json
@@ -0,0 +1,10 @@
+{
+  "realm" : "tenant2",
+  "resource" : "multi-tenant",
+  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA0oJjgPQJhnVhOo51KauQGfLLreMFu64OJdKXRnfvAQJQTuKNwc5JrR63l/byyW1B6FgclABF818TtLvMCAkn4EuFwQZCZhg3x3+lFGiB/IzC6UAt4Bi0JQrTbdh83/U97GIPegvaDqiqEiQESEkbCZWxM6sh/34hQaAhCaFpMwIDAQAB",
+  "auth-server-url" : "http://localhost:8080/auth",
+  "ssl-required" : "external",
+  "credentials" : {
+      "secret": "password"
+   }
+}
\ No newline at end of file
diff --git a/examples/multi-tenant/src/main/webapp/WEB-INF/jboss-ejb3.xml b/examples/multi-tenant/src/main/webapp/WEB-INF/jboss-ejb3.xml
new file mode 100644
index 0000000..1693f7a
--- /dev/null
+++ b/examples/multi-tenant/src/main/webapp/WEB-INF/jboss-ejb3.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2014 Juraci Paixão Kröhling <juraci at kroehling.de>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+-->
+
+<jboss:jboss
+        xmlns="http://java.sun.com/xml/ns/javaee"
+        xmlns:jboss="http://www.jboss.com/xml/ns/javaee"
+        xmlns:s="urn:security:1.1"
+        version="3.1" impl-version="2.0">
+
+    <assembly-descriptor>
+        <s:security>
+            <ejb-name>*</ejb-name>
+            <s:security-domain>keycloak</s:security-domain>
+        </s:security>
+    </assembly-descriptor>
+</jboss:jboss>
\ No newline at end of file
diff --git a/examples/multi-tenant/src/main/webapp/WEB-INF/web.xml b/examples/multi-tenant/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..adfc73f
--- /dev/null
+++ b/examples/multi-tenant/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,29 @@
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+         version="3.0">
+    <display-name>Multi Tenant Example</display-name>
+
+    <context-param>
+        <param-name>keycloak.config.resolver</param-name>
+        <param-value>org.keycloak.example.multitenant.control.PathBasedKeycloakConfigResolver</param-value>
+    </context-param>
+
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>REST endpoints</web-resource-name>
+            <url-pattern>/*</url-pattern>
+        </web-resource-collection>
+        <auth-constraint>
+            <role-name>*</role-name>
+        </auth-constraint>
+    </security-constraint>
+
+    <login-config>
+        <auth-method>KEYCLOAK</auth-method>
+        <realm-name>not-important</realm-name>
+    </login-config>
+    <security-role>
+        <role-name>user</role-name>
+    </security-role>
+</web-app>
\ No newline at end of file
diff --git a/examples/multi-tenant/tenant1-realm.json b/examples/multi-tenant/tenant1-realm.json
new file mode 100644
index 0000000..76acce8
--- /dev/null
+++ b/examples/multi-tenant/tenant1-realm.json
@@ -0,0 +1,57 @@
+{
+    "id": "tenant1",
+    "realm": "tenant1",
+    "enabled": true,
+    "accessTokenLifespan": 3000,
+    "accessCodeLifespan": 10,
+    "accessCodeLifespanUserAction": 6000,
+    "sslRequired": "external",
+    "registrationAllowed": false,
+    "social": false,
+    "passwordCredentialGrantAllowed": true,
+    "updateProfileOnInitialSocialLogin": false,
+    "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
+    "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+    "requiredCredentials": [ "password" ],
+    "users" : [
+        {
+            "username" : "user-tenant1",
+            "enabled": true,
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "user-tenant1" }
+            ],
+            "realmRoles": [ "user" ],
+            "applicationRoles": {
+                "multi-tenant": [ "user" ]
+            }
+        }
+    ],
+    "roles" : {
+        "realm" : [
+            {
+                "name": "user",
+                "description": "User privileges"
+            }
+        ]
+    },
+    "scopeMappings": [
+        {
+            "client": "multi-tenant",
+            "roles": ["user"]
+        }
+
+    ],
+    "applications": [
+        {
+            "name": "multi-tenant",
+            "enabled": true,
+            "adminUrl": "http://localhost:8080/multitenant/tenant1",
+            "baseUrl": "http://localhost:8080/multitenant/tenant1",
+            "redirectUris": [
+                "http://localhost:8080/multitenant/tenant1/*"
+            ],
+            "secret": "password"
+        }
+    ]
+}
diff --git a/examples/multi-tenant/tenant2-realm.json b/examples/multi-tenant/tenant2-realm.json
new file mode 100644
index 0000000..295cb3f
--- /dev/null
+++ b/examples/multi-tenant/tenant2-realm.json
@@ -0,0 +1,57 @@
+{
+    "id": "tenant2",
+    "realm": "tenant2",
+    "enabled": true,
+    "accessTokenLifespan": 3000,
+    "accessCodeLifespan": 10,
+    "accessCodeLifespanUserAction": 6000,
+    "sslRequired": "external",
+    "registrationAllowed": false,
+    "social": false,
+    "passwordCredentialGrantAllowed": true,
+    "updateProfileOnInitialSocialLogin": false,
+    "privateKey": "MIICXQIBAAKBgQDA0oJjgPQJhnVhOo51KauQGfLLreMFu64OJdKXRnfvAQJQTuKNwc5JrR63l/byyW1B6FgclABF818TtLvMCAkn4EuFwQZCZhg3x3+lFGiB/IzC6UAt4Bi0JQrTbdh83/U97GIPegvaDqiqEiQESEkbCZWxM6sh/34hQaAhCaFpMwIDAQABAoGADwFSvEOQuh0IjWRtKZjwjOo4BrmlbRDJ3rf6x2LoemTttSouXzGxx/H87fSZdxNNuU9HbBHoY4ko4POzmZEWhS0gV6UjM7VArc4YjID6Hh2tfU9vCbuuKZrRs7RjxL70b51WxycKc49PQ4JiR3g04punrpq2UzToPrm66zI+ICECQQD2Jauo6cXXoxHR0QychQf4dityZwFXUoR/8oI/YFiu9XwcWgSMwrFKUdWWNKYmrIRNqCBzrGyeiGdaAjsw41T3AkEAyIpn+XL7bek/uLno5/7ULauf2dFI6MEaHJixQJD7S6Tfo/CGuDK93H4K0GAdjgR0LA0tCnB09yyPCd5NmAYKpQJBAO7+BH4s/PsyScr+vs/6GpMTqXuap6KxbBUO0YfXdEPr9mVQwboqDxmp+0esNua1+n+sDlZBw/TpW+/42p/NGmECQF0sOQyjyH+TfGCmN7j6I7ioYZeA7h/9/9TDeK8n7SmDC8kOanlQUfgMs5eG4JRoK1WANaoA/8cLc9XA7EoynGUCQQDx/Gjg6qyWheVujxjKufH1XkqDNiQHClDRM1ntChCmGq/RmpVmce+mYeOYZ9eofv7UJUCBdamllRlB+056Ld2h",
+    "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA0oJjgPQJhnVhOo51KauQGfLLreMFu64OJdKXRnfvAQJQTuKNwc5JrR63l/byyW1B6FgclABF818TtLvMCAkn4EuFwQZCZhg3x3+lFGiB/IzC6UAt4Bi0JQrTbdh83/U97GIPegvaDqiqEiQESEkbCZWxM6sh/34hQaAhCaFpMwIDAQAB",
+    "requiredCredentials": [ "password" ],
+    "users" : [
+        {
+            "username" : "user-tenant2",
+            "enabled": true,
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "user-tenant2" }
+            ],
+            "realmRoles": [ "user" ],
+            "applicationRoles": {
+                "multi-tenant": [ "user" ]
+            }
+        }
+    ],
+    "roles" : {
+        "realm" : [
+            {
+                "name": "user",
+                "description": "User privileges"
+            }
+        ]
+    },
+    "scopeMappings": [
+        {
+            "client": "multi-tenant",
+            "roles": ["user"]
+        }
+
+    ],
+    "applications": [
+        {
+            "name": "multi-tenant",
+            "enabled": true,
+            "adminUrl": "http://localhost:8080/multitenant/tenant2",
+            "baseUrl": "http://localhost:8080/multitenant/tenant2",
+            "redirectUris": [
+                "http://localhost:8080/multitenant/tenant2/*"
+            ],
+            "secret": "password"
+        }
+    ]
+}

examples/pom.xml 1(+1 -0)

diff --git a/examples/pom.xml b/examples/pom.xml
index ea6fac8..7ec8a31 100755
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -29,5 +29,6 @@
         <module>demo-template</module>
         <module>providers</module>
         <module>js-console</module>
+        <module>multi-tenant</module>
     </modules>
 </project>
diff --git a/examples/README.md b/examples/README.md
index 74970e9..cc42354 100755
--- a/examples/README.md
+++ b/examples/README.md
@@ -46,3 +46,9 @@ Themes
 ------
 
 Example themes to change the look and feel of login forms, account management console and admin console. For more information look at `themes/README.md`.
+
+
+Multi tenancy
+-------------
+
+A complete application, showing how to achieve multi tenancy of web applications by using one realm per account. For more information look at `multi-tenant/README.md`
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
index 8597d84..e6378ce 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
@@ -211,6 +211,12 @@ public class LDAPFederationProvider implements UserFederationProvider {
                 return null;
             }
 
+            // KEYCLOAK-808: Should we allow case-sensitivity to be configurable?
+            if (!username.equals(picketlinkUser.getLoginName())) {
+                logger.warnf("User found in LDAP but with different username. LDAP username: %s, Searched username: %s", username, picketlinkUser.getLoginName());
+                return null;
+            }
+
             return importUserFromPicketlink(realm, picketlinkUser);
         } catch (IdentityManagementException ie) {
             throw convertIDMException(ie);
@@ -223,6 +229,11 @@ public class LDAPFederationProvider implements UserFederationProvider {
 
     protected UserModel importUserFromPicketlink(RealmModel realm, User picketlinkUser) {
         String email = (picketlinkUser.getEmail() != null && picketlinkUser.getEmail().trim().length() > 0) ? picketlinkUser.getEmail() : null;
+
+        if (picketlinkUser.getLoginName() == null) {
+            throw new ModelException("User returned from LDAP has null username! Check configuration of your LDAP mappings. ID of user from LDAP: " + picketlinkUser.getId());
+        }
+
         UserModel imported = session.userStorage().addUser(realm, picketlinkUser.getLoginName());
         imported.setEnabled(true);
         imported.setEmail(email);
@@ -247,6 +258,13 @@ public class LDAPFederationProvider implements UserFederationProvider {
             if (picketlinkUser == null) {
                 return null;
             }
+
+            // KEYCLOAK-808: Should we allow case-sensitivity to be configurable?
+            if (!email.equals(picketlinkUser.getEmail())) {
+                logger.warnf("User found in LDAP but with different email. LDAP email: %s, Searched email: %s", email, picketlinkUser.getEmail());
+                return null;
+            }
+
             return importUserFromPicketlink(realm, picketlinkUser);
         } catch (IdentityManagementException ie) {
             throw convertIDMException(ie);
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
index 3e1eb51..4fd2de9 100755
--- a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
@@ -50,8 +50,6 @@ public interface LoginFormsProvider extends Provider {
 
     public LoginFormsProvider setClient(ClientModel client);
 
-    LoginFormsProvider setVerifyCode(String code);
-
     public LoginFormsProvider setQueryParams(MultivaluedMap<String, String> queryParams);
 
     public LoginFormsProvider setFormData(MultivaluedMap<String, String> formData);
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
index 9a76e78..744d588 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -51,7 +51,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
 
     private static final Logger logger = Logger.getLogger(FreeMarkerLoginFormsProvider.class);
 
-    private String verifyCode;
     private String message;
     private String accessCode;
     private Response.Status status = Response.Status.OK;
@@ -110,8 +109,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
             case VERIFY_EMAIL:
                 try {
                     UriBuilder builder = Urls.loginActionEmailVerificationBuilder(uriInfo.getBaseUri());
-                    builder.queryParam("code", accessCode);
-                    builder.queryParam("key", verifyCode);
+                    builder.queryParam("key", accessCode);
 
                     String link = builder.build(realm.getName()).toString();
                     long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
@@ -312,12 +310,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
     }
 
     @Override
-    public LoginFormsProvider setVerifyCode(String code) {
-        this.verifyCode = code;
-        return this;
-    }
-
-    @Override
     public LoginFormsProvider setQueryParams(MultivaluedMap<String, String> queryParams) {
         this.queryParams = queryParams;
         return this;
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
index 9fff2b2..9c107ae 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
@@ -27,27 +27,54 @@ import java.util.Map;
 public class AdapterDeploymentContext {
     private static final Logger log = Logger.getLogger(AdapterDeploymentContext.class);
     protected KeycloakDeployment deployment;
+    protected KeycloakConfigResolver configResolver;
 
     public AdapterDeploymentContext() {
     }
 
+    /**
+     * For single-tenant deployments, this constructor is to be used, as a
+     * full KeycloakDeployment is known at deployment time and won't change
+     * during the application deployment's life cycle.
+     *
+     * @param deployment A KeycloakConfigResolver, possibly missing the Auth
+     *                   Server URL and/or Realm Public Key
+     */
     public AdapterDeploymentContext(KeycloakDeployment deployment) {
         this.deployment = deployment;
     }
 
-    public KeycloakDeployment getDeployment() {
-        return deployment;
+    /**
+     * For multi-tenant deployments, this constructor is to be used, as a
+     * KeycloakDeployment is not known at deployment time. It defers the
+     * resolution of a KeycloakDeployment to a KeycloakConfigResolver,
+     * to be implemented by the target application.
+     *
+     * @param configResolver A KeycloakConfigResolver that will be used
+     *                       to resolve a KeycloakDeployment
+     */
+    public AdapterDeploymentContext(KeycloakConfigResolver configResolver) {
+        this.configResolver = configResolver;
     }
 
     /**
-     * Resolve adapter deployment based on partial adapter configuration.
-     * This will resolve a relative auth server url based on the current request
-     * This will lazily resolve the public key of the realm if it is not set already.
+     * For single-tenant deployments, it complements KeycloakDeployment
+     * by resolving a relative Auth Server's URL based on the current request
+     * and, if needed, will lazily resolve the Realm's Public Key.
+     *
+     * For multi-tenant deployments, defers the resolution of KeycloakDeployment
+     * to the KeycloakConfigResolver .
      *
+     * @param facade the Request/Response Façade , used to either determine
+     *               the Auth Server URL (single tenant) or pass thru to the
+     *               KeycloakConfigResolver.
      * @return
      */
     public KeycloakDeployment resolveDeployment(HttpFacade facade) {
-        KeycloakDeployment deployment = this.deployment;
+        if (null != configResolver) {
+            return configResolver.resolve(facade.getRequest());
+        }
+
         if (deployment == null) return null;
         if (deployment.getAuthServerBaseUrl() == null) return deployment;
 
@@ -411,6 +438,9 @@ public class AdapterDeploymentContext {
     }
 
     public void updateDeployment(AdapterConfig config) {
+        if (null != configResolver) {
+            throw new IllegalStateException("Cannot parse an adapter config and build an updated deployment when on a multi-tenant scenario.");
+        }
         deployment = KeycloakDeploymentBuilder.build(config);
     }
 }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakConfigResolver.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakConfigResolver.java
new file mode 100644
index 0000000..8ba4143
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakConfigResolver.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.adapters;
+
+import org.keycloak.adapters.HttpFacade.Request;
+
+/**
+ * On multi-tenant scenarios, Keycloak will defer the resolution of a
+ * KeycloakDeployment to the target application at the request-phase.
+ *
+ * A Request object is passed to the resolver and callers expect a complete
+ * KeycloakDeployment. Based on this KeycloakDeployment, Keycloak will resume
+ * authenticating and authorizing the request.
+ *
+ * The easiest way to build a KeycloakDeployment is to use
+ * KeycloakDeploymentBuilder , passing the InputStream of an existing
+ * keycloak.json to the build() method. 
+ *
+ * @see KeycloakDeploymentBuilder
+ * @author Juraci Paixão Kröhling <juraci at kroehling.de>
+ */
+public interface KeycloakConfigResolver {
+
+    public KeycloakDeployment resolve(Request facade);
+
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
index aea33dd..099697f 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
@@ -67,7 +67,7 @@ public class PreAuthActionsHandler {
 
     public boolean preflightCors() {
         // don't need to resolve deployment on cors requests.  Just need to know local cors config.
-        KeycloakDeployment deployment = deploymentContext.getDeployment();
+        KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
         if (!deployment.isCors()) return false;
         log.debugv("checkCorsPreflight {0}", facade.getRequest().getURI());
         if (!facade.getRequest().getMethod().equalsIgnoreCase("OPTIONS")) {
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
index 24ac814..8c0dc91 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
@@ -83,6 +83,11 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext 
 
         if (this.deployment == null || refreshToken == null) return false; // Might be serialized in HttpSession?
 
+        if (!this.getRealm().equals(this.deployment.getRealm())) {
+            // this should not happen, but let's check it anyway
+            return false;
+        }
+
         if (log.isTraceEnabled()) {
             log.trace("Doing refresh");
         }
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaCookieTokenStore.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaCookieTokenStore.java
index 406b786..359e84b 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaCookieTokenStore.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaCookieTokenStore.java
@@ -49,6 +49,12 @@ public class CatalinaCookieTokenStore implements AdapterTokenStore {
         if (authenticatedPrincipal != null) {
             log.debug("remote logged in already. Establish state from cookie");
             RefreshableKeycloakSecurityContext securityContext = authenticatedPrincipal.getKeycloakSecurityContext();
+
+            if (!securityContext.getRealm().equals(deployment.getRealm())) {
+                log.debug("Account from cookie is from a different realm than for the request.");
+                return false;
+            }
+
             securityContext.setCurrentRequestInfo(deployment, this);
             Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
             GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), authenticatedPrincipal, roles, securityContext);
@@ -92,6 +98,7 @@ public class CatalinaCookieTokenStore implements AdapterTokenStore {
         }
 
         RefreshableKeycloakSecurityContext session = principal.getKeycloakSecurityContext();
+
         if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return principal;
         boolean success = session.refreshExpiredToken(false);
         if (success && session.isActive()) return principal;
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaSessionTokenStore.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaSessionTokenStore.java
index 0146883..41266cf 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaSessionTokenStore.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaSessionTokenStore.java
@@ -37,8 +37,10 @@ public class CatalinaSessionTokenStore implements AdapterTokenStore {
         if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null) return;
         RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
         if (session == null) return;
+
         // just in case session got serialized
         if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this);
+
         if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
 
         // FYI: A refresh requires same scope, so same roles will be set.  Otherwise, refresh will fail and token will
@@ -62,16 +64,23 @@ public class CatalinaSessionTokenStore implements AdapterTokenStore {
         if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null)
             return false;
         log.debug("remote logged in already. Establish state from session");
-        GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
-        request.setUserPrincipal(principal);
-        request.setAuthType("KEYCLOAK");
 
         RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
         if (securityContext != null) {
+
+            if (!deployment.getRealm().equals(securityContext.getRealm())) {
+                log.debug("Account from cookie is from a different realm than for the request.");
+                return false;
+            }
+
             securityContext.setCurrentRequestInfo(deployment, this);
             request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
         }
 
+        GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
+        request.setUserPrincipal(principal);
+        request.setAuthType("KEYCLOAK");
+
         ((CatalinaRequestAuthenticator)authenticator).restoreRequest();
         return true;
     }
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
index c7d51d0..7b04d7c 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
@@ -6,27 +6,23 @@ import org.apache.catalina.LifecycleEvent;
 import org.apache.catalina.LifecycleException;
 import org.apache.catalina.LifecycleListener;
 import org.apache.catalina.Manager;
-import org.apache.catalina.Session;
 import org.apache.catalina.authenticator.FormAuthenticator;
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
 import org.apache.catalina.core.StandardContext;
 import org.apache.catalina.deploy.LoginConfig;
 import org.jboss.logging.Logger;
-import org.keycloak.KeycloakPrincipal;
 import org.keycloak.KeycloakSecurityContext;
 import org.keycloak.adapters.AdapterConstants;
 import org.keycloak.adapters.AdapterDeploymentContext;
 import org.keycloak.adapters.AdapterTokenStore;
 import org.keycloak.adapters.AuthChallenge;
 import org.keycloak.adapters.AuthOutcome;
-import org.keycloak.adapters.CookieTokenStore;
 import org.keycloak.adapters.HttpFacade;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
 import org.keycloak.adapters.NodesRegistrationManagement;
 import org.keycloak.adapters.PreAuthActionsHandler;
-import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
 import org.keycloak.enums.TokenStore;
 
 import javax.servlet.ServletContext;
@@ -37,6 +33,7 @@ import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import org.keycloak.adapters.KeycloakConfigResolver;
 
 /**
  * Web deployment whose security is managed by a remote OAuth Skeleton Key authentication server
@@ -69,7 +66,6 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
     public void logout(Request request) throws ServletException {
         KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
         if (ksc != null) {
-            request.removeAttribute(KeycloakSecurityContext.class.getName());
             CatalinaHttpFacade facade = new CatalinaHttpFacade(request, null);
             KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
             if (ksc instanceof RefreshableKeycloakSecurityContext) {
@@ -78,6 +74,7 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
 
             AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
             tokenStore.logout();
+            request.removeAttribute(KeycloakSecurityContext.class.getName());
         }
         super.logout(request);
     }
@@ -120,16 +117,41 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
     }
 
 
+    @SuppressWarnings("UseSpecificCatch")
     protected void init() {
-        InputStream configInputStream = getConfigInputStream(context);
-        KeycloakDeployment kd = null;
-        if (configInputStream == null) {
-            log.warn("No adapter configuration.  Keycloak is unconfigured and will deny all requests.");
-            kd = new KeycloakDeployment();
+        // Possible scenarios:
+        // 1) The deployment has a keycloak.config.resolver specified and it exists:
+        //    Outcome: adapter uses the resolver
+        // 2) The deployment has a keycloak.config.resolver and isn't valid (doesn't exists, isn't a resolver, ...) :
+        //    Outcome: adapter is left unconfigured
+        // 3) The deployment doesn't have a keycloak.config.resolver , but has a keycloak.json (or equivalent)
+        //    Outcome: adapter uses it
+        // 4) The deployment doesn't have a keycloak.config.resolver nor keycloak.json (or equivalent)
+        //    Outcome: adapter is left unconfigured
+
+        String configResolverClass = context.getServletContext().getInitParameter("keycloak.config.resolver");
+        if (configResolverClass != null) {
+            try {
+                KeycloakConfigResolver configResolver = (KeycloakConfigResolver) context.getLoader().getClassLoader().loadClass(configResolverClass).newInstance();
+                deploymentContext = new AdapterDeploymentContext(configResolver);
+                log.info("Using " + configResolverClass + " to resolve Keycloak configuration on a per-request basis.");
+            } catch (Exception ex) {
+                log.warn("The specified resolver " + configResolverClass + " could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: " + ex.getMessage());
+                deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
+            }
         } else {
-            kd = KeycloakDeploymentBuilder.build(configInputStream);
+            InputStream configInputStream = getConfigInputStream(context);
+            KeycloakDeployment kd;
+            if (configInputStream == null) {
+                log.warn("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
+                kd = new KeycloakDeployment();
+            } else {
+                kd = KeycloakDeploymentBuilder.build(configInputStream);
+            }
+            deploymentContext = new AdapterDeploymentContext(kd);
+            log.debug("Keycloak is using a per-deployment configuration.");
         }
-        deploymentContext = new AdapterDeploymentContext(kd);
+
         context.getServletContext().setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
         AuthenticatedActionsValve actions = new AuthenticatedActionsValve(deploymentContext, getNext(), getContainer(), getController());
         setNext(actions);
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaCookieTokenStore.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaCookieTokenStore.java
index 8ce88dc..c0c2274 100755
--- a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaCookieTokenStore.java
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaCookieTokenStore.java
@@ -47,6 +47,12 @@ public class CatalinaCookieTokenStore implements AdapterTokenStore {
         if (authenticatedPrincipal != null) {
             log.fine("remote logged in already. Establish state from cookie");
             RefreshableKeycloakSecurityContext securityContext = authenticatedPrincipal.getKeycloakSecurityContext();
+
+            if (!securityContext.getRealm().equals(deployment.getRealm())) {
+                log.fine("Account from cookie is from a different realm than for the request.");
+                return false;
+            }
+
             securityContext.setCurrentRequestInfo(deployment, this);
             Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
             GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), authenticatedPrincipal, roles, securityContext);
@@ -89,6 +95,7 @@ public class CatalinaCookieTokenStore implements AdapterTokenStore {
         }
 
         RefreshableKeycloakSecurityContext session = principal.getKeycloakSecurityContext();
+
         if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return principal;
         boolean success = session.refreshExpiredToken(false);
         if (success && session.isActive()) return principal;
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaSessionTokenStore.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaSessionTokenStore.java
index 99ef859..500adf1 100755
--- a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaSessionTokenStore.java
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaSessionTokenStore.java
@@ -35,8 +35,10 @@ public class CatalinaSessionTokenStore implements AdapterTokenStore {
         if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null) return;
         RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
         if (session == null) return;
+
         // just in case session got serialized
         if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this);
+
         if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
 
         // FYI: A refresh requires same scope, so same roles will be set.  Otherwise, refresh will fail and token will
@@ -60,16 +62,23 @@ public class CatalinaSessionTokenStore implements AdapterTokenStore {
         if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null)
             return false;
         log.fine("remote logged in already. Establish state from session");
-        GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
-        request.setUserPrincipal(principal);
-        request.setAuthType("KEYCLOAK");
 
         RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
         if (securityContext != null) {
+
+            if (!deployment.getRealm().equals(securityContext.getRealm())) {
+                log.fine("Account from cookie is from a different realm than for the request.");
+                return false;
+            }
+
             securityContext.setCurrentRequestInfo(deployment, this);
             request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
         }
 
+        GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
+        request.setUserPrincipal(principal);
+        request.setAuthType("KEYCLOAK");
+
         ((CatalinaRequestAuthenticator)authenticator).restoreRequest();
         return true;
     }
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java
index 408c4ce..80018f7 100755
--- a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java
@@ -6,7 +6,6 @@ import org.apache.catalina.LifecycleEvent;
 import org.apache.catalina.LifecycleException;
 import org.apache.catalina.LifecycleListener;
 import org.apache.catalina.Manager;
-import org.apache.catalina.Session;
 import org.apache.catalina.authenticator.FormAuthenticator;
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
@@ -23,8 +22,6 @@ import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
 import org.keycloak.adapters.NodesRegistrationManagement;
 import org.keycloak.adapters.PreAuthActionsHandler;
-import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
-import org.keycloak.adapters.ServerRequest;
 import org.keycloak.enums.TokenStore;
 
 import javax.servlet.ServletContext;
@@ -35,7 +32,9 @@ import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.logging.Level;
 import java.util.logging.Logger;
+import org.keycloak.adapters.KeycloakConfigResolver;
 
 /**
  * Web deployment whose security is managed by a remote OAuth Skeleton Key authentication server
@@ -74,7 +73,6 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
     public void logout(Request request) throws ServletException {
         KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
         if (ksc != null) {
-            request.removeAttribute(KeycloakSecurityContext.class.getName());
             CatalinaHttpFacade facade = new CatalinaHttpFacade(request, null);
             KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
             if (ksc instanceof RefreshableKeycloakSecurityContext) {
@@ -83,6 +81,7 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
 
             AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
             tokenStore.logout();
+            request.removeAttribute(KeycloakSecurityContext.class.getName());
         }
         super.logout(request);
     }
@@ -94,16 +93,42 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
         cache = false;
     }
 
+    @SuppressWarnings("UseSpecificCatch")
+    @Override
     public void initInternal() {
-        InputStream configInputStream = getConfigInputStream(context);
-        KeycloakDeployment kd = null;
-        if (configInputStream == null) {
-            log.warning("No adapter configuration.  Keycloak is unconfigured and will deny all requests.");
-            kd = new KeycloakDeployment();
+        // Possible scenarios:
+        // 1) The deployment has a keycloak.config.resolver specified and it exists:
+        //    Outcome: adapter uses the resolver
+        // 2) The deployment has a keycloak.config.resolver and isn't valid (doesn't exists, isn't a resolver, ...) :
+        //    Outcome: adapter is left unconfigured
+        // 3) The deployment doesn't have a keycloak.config.resolver , but has a keycloak.json (or equivalent)
+        //    Outcome: adapter uses it
+        // 4) The deployment doesn't have a keycloak.config.resolver nor keycloak.json (or equivalent)
+        //    Outcome: adapter is left unconfigured
+
+        String configResolverClass = context.getServletContext().getInitParameter("keycloak.config.resolver");
+        if (configResolverClass != null) {
+            try {
+                KeycloakConfigResolver configResolver = (KeycloakConfigResolver) context.getLoader().getClassLoader().loadClass(configResolverClass).newInstance();
+                deploymentContext = new AdapterDeploymentContext(configResolver);
+                log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
+            } catch (Exception ex) {
+                log.log(Level.FINE, "The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()});
+                deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
+            }
         } else {
-            kd = KeycloakDeploymentBuilder.build(configInputStream);
+            InputStream configInputStream = getConfigInputStream(context);
+            KeycloakDeployment kd;
+            if (configInputStream == null) {
+                log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
+                kd = new KeycloakDeployment();
+            } else {
+                kd = KeycloakDeploymentBuilder.build(configInputStream);
+            }
+            deploymentContext = new AdapterDeploymentContext(kd);
+            log.fine("Keycloak is using a per-deployment configuration.");
         }
-        deploymentContext = new AdapterDeploymentContext(kd);
+
         context.getServletContext().setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
         AuthenticatedActionsValve actions = new AuthenticatedActionsValve(deploymentContext, getNext(), getContainer());
         setNext(actions);
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
index 5c7a16b..9484021 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
@@ -44,6 +44,7 @@ import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.util.Map;
+import org.keycloak.adapters.KeycloakConfigResolver;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -94,22 +95,49 @@ public class KeycloakServletExtension implements ServletExtension {
 
 
     @Override
+    @SuppressWarnings("UseSpecificCatch")
     public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) {
         if (!isAuthenticationMechanismPresent(deploymentInfo, "KEYCLOAK")) {
             log.debug("auth-method is not keycloak!");
             return;
         }
         log.debug("KeycloakServletException initialization");
-        InputStream is = getConfigInputStream(servletContext);
-        final KeycloakDeployment deployment;
-        if (is == null) {
-            log.warn("No adapter configuration.  Keycloak is unconfigured and will deny all requests.");
-            deployment = new KeycloakDeployment();
-        } else {
-            deployment = KeycloakDeploymentBuilder.build(is);
 
+        // Possible scenarios:
+        // 1) The deployment has a keycloak.config.resolver specified and it exists:
+        //    Outcome: adapter uses the resolver
+        // 2) The deployment has a keycloak.config.resolver and isn't valid (doesn't exists, isn't a resolver, ...) :
+        //    Outcome: adapter is left unconfigured
+        // 3) The deployment doesn't have a keycloak.config.resolver , but has a keycloak.json (or equivalent)
+        //    Outcome: adapter uses it
+        // 4) The deployment doesn't have a keycloak.config.resolver nor keycloak.json (or equivalent)
+        //    Outcome: adapter is left unconfigured
+
+        KeycloakConfigResolver configResolver;
+        String configResolverClass = servletContext.getInitParameter("keycloak.config.resolver");
+        AdapterDeploymentContext deploymentContext;
+        if (configResolverClass != null) {
+            try {
+                configResolver = (KeycloakConfigResolver) deploymentInfo.getClassLoader().loadClass(configResolverClass).newInstance();
+                deploymentContext = new AdapterDeploymentContext(configResolver);
+                log.info("Using " + configResolverClass + " to resolve Keycloak configuration on a per-request basis.");
+            } catch (Exception ex) {
+                log.warn("The specified resolver " + configResolverClass + " could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: " + ex.getMessage());
+                deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
+            }
+        } else {
+            InputStream is = getConfigInputStream(servletContext);
+            final KeycloakDeployment deployment;
+            if (is == null) {
+                log.warn("No adapter configuration.  Keycloak is unconfigured and will deny all requests.");
+                deployment = new KeycloakDeployment();
+            } else {
+                deployment = KeycloakDeploymentBuilder.build(is);
+            }
+            deploymentContext = new AdapterDeploymentContext(deployment);
+            log.debug("Keycloak is using a per-deployment configuration.");
         }
-        AdapterDeploymentContext deploymentContext = new AdapterDeploymentContext(deployment);
+
         servletContext.setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
         UndertowUserSessionManagement userSessionManagement = new UndertowUserSessionManagement();
         final NodesRegistrationManagement nodesRegistrationManagement = new NodesRegistrationManagement();
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java
index fe0c6c9..3dccf8c 100644
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java
@@ -53,6 +53,12 @@ public class ServletSessionTokenStore implements AdapterTokenStore {
             log.debug("Account was not in session, returning null");
             return false;
         }
+
+        if (!deployment.getRealm().equals(account.getKeycloakSecurityContext().getRealm())) {
+            log.debug("Account in session belongs to a different realm than for this request.");
+            return false;
+        }
+
         account.setCurrentRequestInfo(deployment, this);
         if (account.checkActive()) {
             log.debug("Cached account found");
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java
index 4946ea5..65b6ab2 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java
@@ -45,6 +45,11 @@ public class UndertowCookieTokenStore implements AdapterTokenStore {
         }
         KeycloakUndertowAccount account = new KeycloakUndertowAccount(principal);
 
+        if (!deployment.getRealm().equals(account.getKeycloakSecurityContext().getRealm())) {
+            log.debug("Account in session belongs to a different realm than for this request.");
+            return false;
+        }
+
         if (account.checkActive()) {
             log.debug("Cached account found");
             securityContext.authenticationComplete(account, "KEYCLOAK", false);
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java
index 6362e43..cc9e3d9 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java
@@ -50,6 +50,12 @@ public class UndertowSessionTokenStore implements AdapterTokenStore {
             log.debug("Account was not in session, returning null");
             return false;
         }
+
+        if (!deployment.getRealm().equals(account.getKeycloakSecurityContext().getRealm())) {
+            log.debug("Account in session belongs to a different realm than for this request.");
+            return false;
+        }
+
         account.setCurrentRequestInfo(deployment, this);
         if (account.checkActive()) {
             log.debug("Cached account found");
diff --git a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
index 2c0f9dd..bdbc5c4 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
@@ -47,6 +47,7 @@ public interface ClientSessionModel {
         UPDATE_PROFILE,
         CONFIGURE_TOTP,
         UPDATE_PASSWORD,
+        RECOVER_PASSWORD,
         AUTHENTICATE,
         SOCIAL_CALLBACK
     }
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 4371e26..ef38009 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -41,6 +41,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
     private String publicKeyPem;
     private String privateKeyPem;
     private String certificatePem;
+    private String codeSecret;
 
     private String loginTheme;
     private String accountTheme;
@@ -271,6 +272,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
         this.privateKeyPem = privateKeyPem;
     }
 
+    public String getCodeSecret() {
+        return codeSecret;
+    }
+
+    public void setCodeSecret(String codeSecret) {
+        this.codeSecret = codeSecret;
+    }
+
     public String getLoginTheme() {
         return loginTheme;
     }
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index d1895ef..4601fcc 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -97,6 +97,10 @@ public interface RealmModel extends RoleContainerModel {
 
     void setPublicKey(PublicKey publicKey);
 
+    String getCodeSecret();
+
+    void setCodeSecret(String codeSecret);
+
     X509Certificate getCertificate();
     void setCertificate(X509Certificate certificate);
     String getCertificatePem();
diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index f5a1b3a..09111b4 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -119,6 +119,8 @@ public final class KeycloakModelUtils {
             throw new RuntimeException(e);
         }
         realm.setCertificate(certificate);
+
+        realm.setCodeSecret(generateCodeSecret());
     }
 
     public static void generateRealmCertificate(RealmModel realm) {
@@ -161,6 +163,10 @@ public final class KeycloakModelUtils {
         return secret;
     }
 
+    public static String generateCodeSecret() {
+        return UUID.randomUUID().toString();
+    }
+
     public static ApplicationModel createApplication(RealmModel realm, String name) {
         ApplicationModel app = realm.addApplication(name);
         generateSecret(app);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 85331ac..7d431c3 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -91,6 +91,7 @@ public class ModelToRepresentation {
             KeycloakModelUtils.generateRealmCertificate(realm);
         }
         rep.setCertificate(realm.getCertificatePem());
+        rep.setCodeSecret(realm.getCodeSecret());
         rep.setPasswordCredentialGrantAllowed(realm.isPasswordCredentialGrantAllowed());
         rep.setRegistrationAllowed(realm.isRegistrationAllowed());
         rep.setRememberMe(realm.isRememberMe());
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 9610395..5ba8d06 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
@@ -62,7 +62,7 @@ public class RepresentationToModel {
         else newRealm.setAccessTokenLifespan(300);
 
         if (rep.getSsoSessionIdleTimeout() != null) newRealm.setSsoSessionIdleTimeout(rep.getSsoSessionIdleTimeout());
-        else newRealm.setSsoSessionIdleTimeout(600);
+        else newRealm.setSsoSessionIdleTimeout(1800);
         if (rep.getSsoSessionMaxLifespan() != null) newRealm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan());
         else newRealm.setSsoSessionMaxLifespan(36000);
 
@@ -92,6 +92,12 @@ public class RepresentationToModel {
         } else {
             newRealm.setCertificatePem(rep.getCertificate());
         }
+        if (rep.getCodeSecret() == null) {
+            newRealm.setCodeSecret(KeycloakModelUtils.generateCodeSecret());
+        } else {
+            newRealm.setCodeSecret(rep.getCodeSecret());
+        }
+
         if (rep.getLoginTheme() != null) newRealm.setLoginTheme(rep.getLoginTheme());
         if (rep.getAccountTheme() != null) newRealm.setAccountTheme(rep.getAccountTheme());
         if (rep.getAdminTheme() != null) newRealm.setAdminTheme(rep.getAdminTheme());
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
index 67b1927..9bbbed9 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
@@ -21,7 +21,7 @@ public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFa
 
     @Override
     public CacheRealmProvider create(KeycloakSession session) {
-        Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache("realms");
+        Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
         RealmCache realmCache = new InfinispanRealmCache(cache, realmLookup);
         return new DefaultCacheRealmProvider(realmCache, session);
     }
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java
index c0ce985..cb88815 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java
@@ -41,7 +41,7 @@ public class InfinispanCacheUserProviderFactory implements CacheUserProviderFact
             synchronized (this) {
                 if (userCache == null) {
                     checkIspnVersion();
-                    Cache<String, CachedUser> cache = session.getProvider(InfinispanConnectionProvider.class).getCache("users");
+                    Cache<String, CachedUser> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.USER_CACHE_NAME);
                     cache.addListener(new CacheListener());
                     userCache = new InfinispanUserCache(cache, usernameLookup, emailLookup);
                 }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index d96a9ea..76c9204 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -57,6 +57,7 @@ public class CachedRealm {
     private String publicKeyPem;
     private String privateKeyPem;
     private String certificatePem;
+    private String codeSecret;
 
     private String loginTheme;
     private String accountTheme;
@@ -115,6 +116,7 @@ public class CachedRealm {
         publicKeyPem = model.getPublicKeyPem();
         privateKeyPem = model.getPrivateKeyPem();
         certificatePem = model.getCertificatePem();
+        codeSecret = model.getCodeSecret();
 
         loginTheme = model.getLoginTheme();
         accountTheme = model.getAccountTheme();
@@ -267,6 +269,10 @@ public class CachedRealm {
         return privateKeyPem;
     }
 
+    public String getCodeSecret() {
+        return codeSecret;
+    }
+
     public List<RequiredCredentialModel> getRequiredCredentials() {
         return requiredCredentials;
     }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index d471b4a..eaf9fca 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
@@ -374,7 +374,16 @@ public class RealmAdapter implements RealmModel {
         setPrivateKeyPem(privateKeyPem);
     }
 
+    @Override
+    public String getCodeSecret() {
+        return updated != null ? updated.getCodeSecret() : cached.getCodeSecret();
+    }
 
+    @Override
+    public void setCodeSecret(String codeSecret) {
+        getDelegateForUpdate();
+        updated.setCodeSecret(codeSecret);
+    }
 
     @Override
     public List<RequiredCredentialModel> getRequiredCredentials() {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index bcb5ad9..c78e916 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -82,6 +82,8 @@ public class RealmEntity {
     protected String privateKeyPem;
     @Column(name="CERTIFICATE", length = 2048)
     protected String certificatePem;
+    @Column(name="CODE_SECRET", length = 255)
+    protected String codeSecret;
 
     @Column(name="LOGIN_THEME")
     protected String loginTheme;
@@ -284,6 +286,14 @@ public class RealmEntity {
         this.privateKeyPem = privateKeyPem;
     }
 
+    public String getCodeSecret() {
+        return codeSecret;
+    }
+
+    public void setCodeSecret(String codeSecret) {
+        this.codeSecret = codeSecret;
+    }
+
     public Collection<RequiredCredentialEntity> getRequiredCredentials() {
         return requiredCredentials;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 5578b88..ee97080 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -434,6 +434,16 @@ public class RealmAdapter implements RealmModel {
         setPrivateKeyPem(privateKeyPem);
     }
 
+    @Override
+    public String getCodeSecret() {
+        return realm.getCodeSecret();
+    }
+
+    @Override
+    public void setCodeSecret(String codeSecret) {
+        realm.setCodeSecret(codeSecret);
+    }
+
     protected RequiredCredentialModel initRequiredCredentialModel(String type) {
         RequiredCredentialModel model = RequiredCredentialModel.BUILT_IN.get(type);
         if (model == null) {
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index ba51c13..5963ab0 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -420,6 +420,17 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     }
 
     @Override
+    public String getCodeSecret() {
+        return realm.getCodeSecret();
+    }
+
+    @Override
+    public void setCodeSecret(String codeSecret) {
+        realm.setCodeSecret(codeSecret);
+        updateRealm();
+    }
+
+    @Override
     public String getLoginTheme() {
         return realm.getLoginTheme();
     }
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 617f686..96e7f85 100644
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -210,6 +210,15 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         for (String id : map.keySet()) {
             removeUserSession(realm, id);
         }
+
+        map = new MapReduceTask(sessionCache)
+                .mappedWith(ClientSessionMapper.create(realm.getId()).expiredRefresh(expiredRefresh).requireNullUserSession(true).emitKey())
+                .reducedWith(new FirstResultReducer())
+                .execute();
+
+        for (String id : map.keySet()) {
+            tx.remove(sessionCache, id);
+        }
     }
 
     @Override
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
index 977eb3d..7d76335 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
@@ -15,14 +15,11 @@ import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
  */
 public class InfinispanUserSessionProviderFactory implements UserSessionProviderFactory {
 
-    private static final String SESSION_CACHE_NAME = "sessions";
-    private static final String LOGIN_FAILURE_CACHE_NAME = "loginFailures";
-
     @Override
     public UserSessionProvider create(KeycloakSession session) {
         InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
-        Cache<String, SessionEntity> cache = connections.getCache(SESSION_CACHE_NAME);
-        Cache<LoginFailureKey, LoginFailureEntity> loginFailures = connections.getCache(LOGIN_FAILURE_CACHE_NAME);
+        Cache<String, SessionEntity> cache = connections.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME);
+        Cache<LoginFailureKey, LoginFailureEntity> loginFailures = connections.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
         return new InfinispanUserSessionProvider(session, cache, loginFailures);
     }
 
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientSessionMapper.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientSessionMapper.java
index 369b7a6..1502b2c 100644
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientSessionMapper.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientSessionMapper.java
@@ -28,6 +28,10 @@ public class ClientSessionMapper implements Mapper<String, SessionEntity, String
 
     private String userSession;
 
+    private Long expiredRefresh;
+
+    private Boolean requireNullUserSession = false;
+
     public static ClientSessionMapper create(String realm) {
         return new ClientSessionMapper(realm);
     }
@@ -52,6 +56,16 @@ public class ClientSessionMapper implements Mapper<String, SessionEntity, String
         return this;
     }
 
+    public ClientSessionMapper expiredRefresh(long expiredRefresh) {
+        this.expiredRefresh = expiredRefresh;
+        return this;
+    }
+
+    public ClientSessionMapper requireNullUserSession(boolean requireNullUserSession) {
+        this.requireNullUserSession = requireNullUserSession;
+        return this;
+    }
+
     @Override
     public void map(String key, SessionEntity e, Collector collector) {
         if (!realm.equals(e.getRealm())) {
@@ -72,6 +86,14 @@ public class ClientSessionMapper implements Mapper<String, SessionEntity, String
             return;
         }
 
+        if (requireNullUserSession && entity.getUserSession() != null) {
+            return;
+        }
+
+        if (expiredRefresh != null && entity.getTimestamp() > expiredRefresh) {
+            return;
+        }
+
         switch (emit) {
             case KEY:
                 collector.emit(key, key);
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
index a19af58..cd5eef1 100755
--- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
@@ -194,7 +194,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
         Iterator<ClientSessionEntity> citr = clientSessions.values().iterator();
         while (citr.hasNext()) {
             ClientSessionEntity c = citr.next();
-            if (c.getSession() == null && c.getTimestamp() < Time.currentTime() - realm.getSsoSessionIdleTimeout()) {
+            if (c.getSession() == null && c.getRealmId().equals(realm.getId()) && c.getTimestamp() < Time.currentTime() - realm.getSsoSessionIdleTimeout()) {
                 citr.remove();
             }
         }
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
index 1c8c317..5bcbb87 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -16,6 +16,7 @@ import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.oidc.OpenIDConnectService;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.ClientSessionCode;
@@ -201,6 +202,7 @@ public class SamlService {
             clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL);
             clientSession.setRedirectUri(redirect);
             clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
+            clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
             clientSession.setNote(SamlProtocol.SAML_BINDING, getBindingType());
             clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
             clientSession.setNote(SamlProtocol.SAML_REQUEST_ID, requestAbstractType.getID());
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
index 96c3ef6..0c60ccb 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
@@ -550,7 +550,7 @@ public class OpenIDConnectService {
             String[] parts = code.split("\\.");
             if (parts.length == 2) {
                 try {
-                    event.detail(Details.CODE_ID, new String(Base64Url.decode(parts[1])));
+                    event.detail(Details.CODE_ID, new String(parts[1]));
                 } catch (Throwable t) {
                 }
             }
@@ -776,6 +776,7 @@ public class OpenIDConnectService {
             clientSession.setAuthMethod(OpenIDConnect.LOGIN_PROTOCOL);
             clientSession.setRedirectUri(redirect);
             clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
+            clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
             clientSession.setNote(OpenIDConnect.STATE_PARAM, state);
             if (scopeParam != null) clientSession.setNote(OpenIDConnect.SCOPE_PARAM, scopeParam);
             if (responseType != null) clientSession.setNote(OpenIDConnect.RESPONSE_TYPE_PARAM, responseType);
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 cf5978f..655b148 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
@@ -49,7 +49,7 @@ public class ApplianceBootstrap {
         realm.setName(adminRealmName);
         realm.setEnabled(true);
         realm.addRequiredCredential(CredentialRepresentation.PASSWORD);
-        realm.setSsoSessionIdleTimeout(300);
+        realm.setSsoSessionIdleTimeout(1800);
         realm.setAccessTokenLifespan(60);
         realm.setSsoSessionMaxLifespan(36000);
         realm.setAccessCodeLifespan(60);
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 9dbfbea..4856c02 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -290,9 +290,6 @@ public class AuthenticationManager {
 
             LoginFormsProvider loginFormsProvider = Flows.forms(session, realm, client, uriInfo).setClientSessionCode(accessCode.getCode()).setUser(user);
             if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL)) {
-                String key = UUID.randomUUID().toString();
-                clientSession.setNote("key", key);
-                loginFormsProvider.setVerifyCode(key);
                 event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
             }
 
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
index 46bba42..5574cbb 100755
--- a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
@@ -1,8 +1,5 @@
 package org.keycloak.services.managers;
 
-import org.keycloak.OAuthErrorException;
-import org.keycloak.jose.jws.Algorithm;
-import org.keycloak.jose.jws.crypto.RSAProvider;
 import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
@@ -11,11 +8,10 @@ import org.keycloak.models.UserModel.RequiredAction;
 import org.keycloak.util.Base64Url;
 import org.keycloak.util.Time;
 
-import java.nio.ByteBuffer;
 import java.security.MessageDigest;
-import java.security.Signature;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.UUID;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -23,6 +19,10 @@ import java.util.Set;
  */
 public class ClientSessionCode {
 
+    public static final String ACTION_KEY = "action_key";
+
+    private static final byte[] HASH_SEPERATOR = "//".getBytes();
+
     private final RealmModel realm;
     private final ClientSessionModel clientSession;
 
@@ -34,14 +34,14 @@ public class ClientSessionCode {
     public static ClientSessionCode parse(String code, KeycloakSession session) {
         try {
             String[] parts = code.split("\\.");
-            String id = new String(Base64Url.decode(parts[1]));
+            String id = parts[1];
 
             ClientSessionModel clientSession = session.sessions().getClientSession(id);
             if (clientSession == null) {
                 return null;
             }
 
-            String hash = createSignatureHash(clientSession.getRealm(), clientSession);
+            String hash = createHash(clientSession.getRealm(), clientSession);
             if (!hash.equals(parts[0])) {
                 return null;
             }
@@ -56,14 +56,14 @@ public class ClientSessionCode {
     public static ClientSessionCode parse(String code, KeycloakSession session, RealmModel realm) {
         try {
             String[] parts = code.split("\\.");
-            String id = new String(Base64Url.decode(parts[1]));
+            String id = parts[1];
 
             ClientSessionModel clientSession = session.sessions().getClientSession(realm, id);
             if (clientSession == null) {
                 return null;
             }
 
-            String hash = createSignatureHash(realm, clientSession);
+            String hash = createHash(realm, clientSession);
             if (!hash.equals(parts[0])) {
                 return null;
             }
@@ -78,10 +78,6 @@ public class ClientSessionCode {
         return clientSession;
     }
 
-    public boolean isValid(RequiredAction requiredAction) {
-        return isValid(convertToAction(requiredAction));
-    }
-
     public boolean isValid(ClientSessionModel.Action requestedAction) {
         ClientSessionModel.Action action = clientSession.getAction();
         if (action == null) {
@@ -111,6 +107,7 @@ public class ClientSessionCode {
 
     public void setAction(ClientSessionModel.Action action) {
         clientSession.setAction(action);
+        clientSession.setNote(ACTION_KEY, UUID.randomUUID().toString());
         clientSession.setTimestamp(Time.currentTime());
     }
 
@@ -138,29 +135,24 @@ public class ClientSessionCode {
     }
 
     private static String generateCode(RealmModel realm, ClientSessionModel clientSession) {
-        String hash = createSignatureHash(realm, clientSession);
+        String hash = createHash(realm, clientSession);
 
         StringBuilder sb = new StringBuilder();
         sb.append(hash);
         sb.append(".");
-        sb.append(Base64Url.encode(clientSession.getId().getBytes()));
+        sb.append(clientSession.getId());
 
         return sb.toString();
     }
 
-    private static String createSignatureHash(RealmModel realm, ClientSessionModel clientSession) {
+    private static String createHash(RealmModel realm, ClientSessionModel clientSession) {
         try {
-            Signature signature = Signature.getInstance(RSAProvider.getJavaAlgorithm(Algorithm.RS256));
-            signature.initSign(realm.getPrivateKey());
-            signature.update(clientSession.getId().getBytes());
-            signature.update(ByteBuffer.allocate(4).putInt(clientSession.getTimestamp()));
-            if (clientSession.getAction() != null) {
-                signature.update(clientSession.getAction().toString().getBytes());
-            }
-            byte[] sign = signature.sign();
-
-            MessageDigest digest = MessageDigest.getInstance("sha-1");
-            digest.update(sign);
+            MessageDigest digest = MessageDigest.getInstance("sha-256");
+            digest.update(realm.getCodeSecret().getBytes());
+            digest.update(HASH_SEPERATOR);
+            digest.update(clientSession.getId().getBytes());
+            digest.update(HASH_SEPERATOR);
+            digest.update(clientSession.getNote(ACTION_KEY).getBytes());
             return Base64Url.encode(digest.digest());
         } catch (Exception e) {
             throw new RuntimeException(e);
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 5eff07b..2d91533 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
@@ -709,9 +709,6 @@ public class UsersResource {
         try {
             UriBuilder builder = Urls.loginPasswordResetBuilder(uriInfo.getBaseUri());
             builder.queryParam("code", accessCode.getCode());
-            String key = UUID.randomUUID().toString();
-            clientSession.setNote("key", key);
-            builder.queryParam("key", key);
 
             String link = builder.build(realm.getName()).toString();
             long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index 7310d74..b970471 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -271,9 +271,9 @@ public class LoginActionsService {
             return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application.");
         }
         ClientSessionModel clientSession = clientCode.getClientSession();
-        if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) {
+        if (!(clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE) || clientCode.isValid(ClientSessionModel.Action.RECOVER_PASSWORD))) {
             clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE);
-            event.client(clientSession.getClient()).error(Errors.INVALID_USER_CREDENTIALS);
+            event.client(clientSession.getClient()).error(Errors.INVALID_CODE);
             return Flows.forms(this.session, realm, clientSession.getClient(), uriInfo).setError(Messages.INVALID_USER)
                     .setClientSessionCode(clientCode.getCode())
                     .createLogin();
@@ -714,22 +714,17 @@ public class LoginActionsService {
 
     @Path("email-verification")
     @GET
-    public Response emailVerification(@QueryParam("code") String code) {
+    public Response emailVerification(@QueryParam("code") String code, @QueryParam("key") String key) {
         event.event(EventType.VERIFY_EMAIL);
-        if (uriInfo.getQueryParameters().containsKey("key")) {
+        if (key != null) {
             Checks checks = new Checks();
-            if (!checks.check(code, ClientSessionModel.Action.VERIFY_EMAIL)) {
+            if (!checks.check(key, ClientSessionModel.Action.VERIFY_EMAIL)) {
                 return checks.response;
             }
             ClientSessionCode accessCode = checks.clientCode;
             ClientSessionModel clientSession = accessCode.getClientSession();
             UserSessionModel userSession = clientSession.getUserSession();
             UserModel user = userSession.getUser();
-            String key = uriInfo.getQueryParameters().getFirst("key");
-            String keyNote = clientSession.getNote("key");
-            if (key == null || !key.equals(keyNote)) {
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Somebody is trying to illegally change your email.");
-            }
             initEvent(clientSession);
             user.setEmailVerified(true);
 
@@ -745,16 +740,11 @@ public class LoginActionsService {
             }
             ClientSessionCode accessCode = checks.clientCode;
             ClientSessionModel clientSession = accessCode.getClientSession();
-            String verifyCode = UUID.randomUUID().toString();
-            clientSession.setNote("key", verifyCode);
             UserSessionModel userSession = clientSession.getUserSession();
-            UserModel user = userSession.getUser();
-
             initEvent(clientSession);
 
             return Flows.forms(session, realm, null, uriInfo)
                     .setClientSessionCode(accessCode.getCode())
-                    .setVerifyCode(verifyCode)
                     .setUser(userSession.getUser())
                     .createResponse(RequiredAction.VERIFY_EMAIL);
         }
@@ -762,22 +752,15 @@ public class LoginActionsService {
 
     @Path("password-reset")
     @GET
-    public Response passwordReset(@QueryParam("code") String code) {
-        event.event(EventType.SEND_RESET_PASSWORD);
-        if (uriInfo.getQueryParameters().containsKey("key")) {
+    public Response passwordReset(@QueryParam("code") String code, @QueryParam("key") String key) {
+        event.event(EventType.RESET_PASSWORD);
+        if (key != null) {
             Checks checks = new Checks();
-            if (!checks.check(code, ClientSessionModel.Action.UPDATE_PASSWORD)) {
+            if (!checks.check(key, ClientSessionModel.Action.RECOVER_PASSWORD)) {
                 return checks.response;
             }
             ClientSessionCode accessCode = checks.clientCode;
-            ClientSessionModel clientSession = accessCode.getClientSession();
-            UserSessionModel userSession = clientSession.getUserSession();
-            UserModel user = userSession.getUser();
-            String key = uriInfo.getQueryParameters().getFirst("key");
-            String keyNote = clientSession.getNote("key");
-            if (key == null || !key.equals(keyNote)) {
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Somebody is trying to illegally change your password.");
-            }
+            accessCode.setRequiredAction(RequiredAction.UPDATE_PASSWORD);
             return Flows.forms(session, realm, null, uriInfo)
                     .setClientSessionCode(accessCode.getCode())
                     .createResponse(RequiredAction.UPDATE_PASSWORD);
@@ -838,14 +821,11 @@ public class LoginActionsService {
             event.session(userSession);
             TokenManager.attachClientSession(userSession, clientSession);
 
-            accessCode.setRequiredAction(RequiredAction.UPDATE_PASSWORD);
+            accessCode.setAction(ClientSessionModel.Action.RECOVER_PASSWORD);
 
             try {
                 UriBuilder builder = Urls.loginPasswordResetBuilder(uriInfo.getBaseUri());
-                builder.queryParam("code", accessCode.getCode());
-                String verifyCode = UUID.randomUUID().toString();
-                clientSession.setNote("key", verifyCode);
-                builder.queryParam("key", verifyCode);
+                builder.queryParam("key", accessCode.getCode());
 
                 String link = builder.build(realm.getName()).toString();
                 long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
@@ -861,7 +841,7 @@ public class LoginActionsService {
             }
         }
 
-        return Flows.forms(session, realm, client,  uriInfo).setSuccess("emailSent").createPasswordReset();
+        return Flows.forms(session, realm, client,  uriInfo).setSuccess("emailSent").setClientSessionCode(accessCode.getCode()).createPasswordReset();
     }
 
     private Response redirectOauth(UserModel user, ClientSessionCode accessCode, ClientSessionModel clientSession, UserSessionModel userSession) {
diff --git a/testsuite/docker-cluster/shared-files/keycloak-base-prepare.sh b/testsuite/docker-cluster/shared-files/keycloak-base-prepare.sh
index 137e5c6..26de114 100644
--- a/testsuite/docker-cluster/shared-files/keycloak-base-prepare.sh
+++ b/testsuite/docker-cluster/shared-files/keycloak-base-prepare.sh
@@ -20,7 +20,9 @@ sed -i -e 's/<\/periodic-rotating-file-handler>/&\n <logger category=\"org.keycl
 
 sed -i -e 's/<subsystem xmlns=\"urn:jboss:domain:infinispan:[0-9]\.[0-9]\">/&\n <cache-container name=\"keycloak\" jndi-name=\"infinispan\/Keycloak\" start=\"EAGER\"> \
 \n  <transport lock-timeout=\"60000\"\/>\n  <distributed-cache name=\"sessions\" mode=\"SYNC\" owners=\"2\" segments=\"60\"\/> \
-\n  <invalidation-cache name=\"realms\" mode=\"SYNC\"\/>\n <\/cache-container>/' $JBOSS_HOME/standalone/configuration/standalone-ha.xml
+\n  <distributed-cache name=\"loginFailures\" mode=\"SYNC\" owners=\"2\" segments=\"60\"\/> \
+\n  <invalidation-cache name=\"realms\" mode=\"SYNC\"\/>\n \
+\n  <invalidation-cache name=\"users\"  mode=\"SYNC\"\/>\n <\/cache-container>/' $JBOSS_HOME/standalone/configuration/standalone-ha.xml
 
 sed -i "s|<mod-cluster-config .*>|<mod-cluster-config advertise-socket=\"modcluster\" proxy-list=\"\$\{httpd.proxyList\}\" proxy-url=\"\/\" balancer=\"mycluster\" advertise=\"false\" connector=\"ajp\" sticky-session=\"true\">|" $JBOSS_HOME/standalone/configuration/standalone-ha.xml
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
index f2b8b26..0f2b13f 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
@@ -122,7 +122,7 @@ public class RequiredActionEmailVerificationTest {
 
         String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
 
-        //Assert.assertEquals(mailCodeId, verificationUrl.split("key=")[1]);
+        Assert.assertEquals(mailCodeId, verificationUrl.split("key=")[1].split("\\.")[1]);
 
         driver.navigate().to(verificationUrl.trim());
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenancyTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenancyTest.java
new file mode 100644
index 0000000..c6df988
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenancyTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.adapter;
+
+import javax.ws.rs.core.UriBuilder;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.oidc.OpenIDConnectService;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.testutils.KeycloakServer;
+import org.openqa.selenium.WebDriver;
+
+/**
+ *
+ * @author Juraci Paixão Kröhling <juraci at kroehling.de>
+ */
+public class MultiTenancyTest {
+    @Rule
+    public WebRule webRule = new WebRule(this);
+
+    @WebResource
+    protected LoginPage loginPage;
+
+    @WebResource
+    protected WebDriver driver;
+    
+    @ClassRule
+    public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() {
+        @Override
+        protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
+            RealmRepresentation tenant1 = KeycloakServer.loadJson(getClass().getResourceAsStream("/adapter-test/tenant1-realm.json"), RealmRepresentation.class);
+            manager.importRealm(tenant1);
+
+            RealmRepresentation tenant2 = KeycloakServer.loadJson(getClass().getResourceAsStream("/adapter-test/tenant2-realm.json"), RealmRepresentation.class);
+            manager.importRealm(tenant2);
+
+            deployApplication("multi-tenant", "/multi-tenant", MultiTenantServlet.class, null, "user", true, MultiTenantResolver.class);
+        }
+    };
+
+    /**
+     * Simplest scenario: one user, one realm. The user is not logged in at
+     * any other realm
+     * @throws Exception
+     */
+    @Test
+    public void testTenantsLoggingOut() throws Exception {
+        doTenantRequests("tenant1", true);
+        doTenantRequests("tenant2", true);
+    }
+
+    /**
+     * This tests the adapter's ability to deal with multiple sessions
+     * from the same user, one for each realm. It should not mixup and return
+     * a session from tenant1 to tenant2
+     * @throws Exception
+     */
+    @Test
+    public void testTenantsWithoutLoggingOut() throws Exception {
+        doTenantRequests("tenant1", true);
+        doTenantRequests("tenant2", true);
+
+        doTenantRequests("tenant1", false);
+        doTenantRequests("tenant2", true);
+    }
+
+    /**
+     * This test simulates an user that is not logged in yet, and tris to login
+     * into tenant1 using an account from tenant2.
+     * On this scenario, the user should be shown the login page again.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testUnauthorizedAccessNotLoggedIn() throws Exception {
+        String keycloakServerBaseUrl = "http://localhost:8081/auth";
+
+        driver.navigate().to("http://localhost:8081/multi-tenant?realm=tenant1");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(keycloakServerBaseUrl));
+
+        loginPage.login("user-tenant2", "user-tenant2");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(keycloakServerBaseUrl));
+    }
+
+    /**
+     * This test simulates an user which is already logged in into tenant1
+     * and tries to access a resource on tenant2.
+     * On this scenario, the user should be shown the login page again.
+     * 
+     * @throws Exception
+     */
+    @Test
+    public void testUnauthorizedAccessLoggedIn() throws Exception {
+        String keycloakServerBaseUrl = "http://localhost:8081/auth";
+        doTenantRequests("tenant1", false);
+
+        driver.navigate().to("http://localhost:8081/multi-tenant?realm=tenant2");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(keycloakServerBaseUrl));
+    }
+
+    private void doTenantRequests(String tenant, boolean logout) {
+        String tenantLoginUrl = OpenIDConnectService.loginPageUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build(tenant).toString();
+
+        driver.navigate().to("http://localhost:8081/multi-tenant?realm="+tenant);
+        System.out.println("Current url: " + driver.getCurrentUrl());
+
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(tenantLoginUrl));
+        loginPage.login("bburke@redhat.com", "password");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+
+        Assert.assertEquals("http://localhost:8081/multi-tenant?realm="+tenant, driver.getCurrentUrl());
+        String pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+
+        Assert.assertTrue(pageSource.contains("Username: bburke@redhat.com"));
+        Assert.assertTrue(pageSource.contains("Realm: "+tenant));
+
+        if (logout) {
+            driver.manage().deleteAllCookies();
+        }
+    }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenantResolver.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenantResolver.java
new file mode 100644
index 0000000..1acf95d
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenantResolver.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.adapter;
+
+import java.io.InputStream;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.KeycloakConfigResolver;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+
+/**
+ *
+ * @author Juraci Paixão Kröhling <juraci at kroehling.de>
+ */
+public class MultiTenantResolver implements KeycloakConfigResolver {
+
+    @Override
+    public KeycloakDeployment resolve(HttpFacade.Request request) {
+        String realm = request.getQueryParamValue("realm");
+        InputStream is = getClass().getResourceAsStream("/adapter-test/"+realm+"-keycloak.json");
+
+        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(is);
+        return deployment;
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenantServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenantServlet.java
new file mode 100644
index 0000000..5e04cf2
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenantServlet.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.adapter;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.keycloak.KeycloakSecurityContext;
+
+/**
+ *
+ * @author Juraci Paixão Kröhling <juraci at kroehling.de>
+ */
+public class MultiTenantServlet extends HttpServlet {
+
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        resp.setContentType("text/html");
+        PrintWriter pw = resp.getWriter();
+        KeycloakSecurityContext context = (KeycloakSecurityContext)req.getAttribute(KeycloakSecurityContext.class.getName());
+
+        pw.print("Username: ");
+        pw.println(context.getIdToken().getPreferredUsername());
+
+        pw.print("<br/>Realm: ");
+        pw.println(context.getRealm());
+
+        pw.flush();
+    }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
index 8f94592..d0a8db9 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
@@ -148,6 +148,15 @@ public class FederationProvidersIntegrationTest {
     }
 
     @Test
+    public void loginLdapWithEmail() {
+        loginPage.open();
+        loginPage.login("john@email.org", "Password1");
+
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+    }
+
+    @Test
     public void XdeleteLink() {
         loginLdap();
         {
@@ -200,6 +209,11 @@ public class FederationProvidersIntegrationTest {
         loginPage.open();
         loginPage.login("johnkeycloak", "New-password1");
         Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        // Change password back to previous value
+        changePasswordPage.open();
+        changePasswordPage.changePassword("New-password1", "Password1", "Password1");
+        Assert.assertEquals("Your password has been updated", profilePage.getSuccess());
     }
 
     @Test
@@ -370,7 +384,7 @@ public class FederationProvidersIntegrationTest {
             Assert.assertTrue(session.users().validCredentials(appRealm, user, cred));
 
             // LDAP password is still unchanged
-            Assert.assertTrue(LDAPUtils.validatePassword(getPartitionManager(session, model), "johnkeycloak", "New-password1"));
+            Assert.assertTrue(LDAPUtils.validatePassword(getPartitionManager(session, model), "johnkeycloak", "Password1"));
 
             // ATM it's not permitted to delete user in unsynced mode. Should be user deleted just locally instead?
             Assert.assertFalse(session.users().removeUser(appRealm, user));
@@ -387,6 +401,18 @@ public class FederationProvidersIntegrationTest {
         }
     }
 
+    @Test
+    public void testCaseSensitiveSearch() {
+        loginPage.open();
+
+        // This should fail for now due to case-sensitivity
+        loginPage.login("johnKeycloak", "Password1");
+        Assert.assertEquals("Invalid username or password.", loginPage.getError());
+
+        loginPage.login("John@email.org", "Password1");
+        Assert.assertEquals("Invalid username or password.", loginPage.getError());
+    }
+
     static PartitionManager getPartitionManager(KeycloakSession keycloakSession, UserFederationProviderModel ldapFedModel) {
         PartitionManagerProvider partitionManagerProvider = keycloakSession.getProvider(PartitionManagerProvider.class);
         return partitionManagerProvider.getPartitionManager(ldapFedModel);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
index 8a922af..4673165 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
@@ -166,7 +166,7 @@ public class LoginTotpTest {
             loginPage.assertCurrent();
             Assert.assertEquals("Invalid username or password.", loginPage.getError());
 
-            AssertEvents.ExpectedEvent expectedEvent = events.expectLogin().error("invalid_user_credentials")
+            AssertEvents.ExpectedEvent expectedEvent = events.expectLogin().error("invalid_code")
                     .user((String)null)
                     .clearDetails()
                     .session((String) null);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 22ec3c1..774d683 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -38,6 +38,7 @@ import org.keycloak.testsuite.MailUtil;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
+import org.keycloak.testsuite.pages.ErrorPage;
 import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.pages.LoginPasswordResetPage;
 import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
@@ -97,6 +98,9 @@ public class ResetPasswordTest {
     protected LoginPage loginPage;
 
     @WebResource
+    protected ErrorPage errorPage;
+
+    @WebResource
     protected LoginPasswordResetPage resetPasswordPage;
 
     @WebResource
@@ -111,6 +115,42 @@ public class ResetPasswordTest {
     }
 
     @Test
+    public void resetPasswordCancel() throws IOException, MessagingException {
+        loginPage.open();
+        loginPage.resetPassword();
+
+        resetPasswordPage.assertCurrent();
+
+        resetPasswordPage.changePassword("login-test");
+
+        resetPasswordPage.assertCurrent();
+
+        events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
+
+        resetPasswordPage.backToLogin();
+
+        Assert.assertTrue(loginPage.isCurrent());
+
+        loginPage.login("login-test", "password");
+
+        events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
+
+        Assert.assertEquals(1, greenMail.getReceivedMessages().length);
+
+        MimeMessage message = greenMail.getReceivedMessages()[0];
+
+        String body = (String) message.getContent();
+        String changePasswordUrl = MailUtil.getLink(body);
+
+        driver.navigate().to(changePasswordUrl.trim());
+
+        events.expect(EventType.RESET_PASSWORD_ERROR).client((String) null).user((String) null).error("invalid_code").clearDetails().assertEvent();
+
+        Assert.assertTrue(errorPage.isCurrent());
+        Assert.assertEquals("Unknown code, please login again through your application.", errorPage.getError());
+    }
+
+    @Test
     public void resetPasswordByEmail() throws IOException, MessagingException {
         resetPassword("login@test.com");
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
index c68ae7d..420d66c 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
@@ -244,12 +244,15 @@ public class UserSessionProviderTest {
     @Test
     public void testRemoveUserSessionsByExpired() {
         session.sessions().getUserSessions(realm, session.users().getUserByUsername("user1", realm));
+        ClientModel client = realm.findClient("test-app");
 
         try {
             Set<String> expired = new HashSet<String>();
+            Set<String> expiredClientSessions = new HashSet<String>();
 
             Time.setOffset(-(realm.getSsoSessionMaxLifespan() + 1));
             expired.add(session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true).getId());
+            expiredClientSessions.add(session.sessions().createClientSession(realm, client).getId());
 
             Time.setOffset(0);
             UserSessionModel s = session.sessions().createUserSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.1", "form", true);
@@ -257,9 +260,15 @@ public class UserSessionProviderTest {
             s.setLastSessionRefresh(0);
             expired.add(s.getId());
 
+            ClientSessionModel clSession = session.sessions().createClientSession(realm, client);
+            clSession.setUserSession(s);
+            expiredClientSessions.add(clSession.getId());
+
             Set<String> valid = new HashSet<String>();
+            Set<String> validClientSessions = new HashSet<String>();
 
             valid.add(session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true).getId());
+            validClientSessions.add(session.sessions().createClientSession(realm, client).getId());
 
             resetSession();
 
@@ -269,10 +278,16 @@ public class UserSessionProviderTest {
             for (String e : expired) {
                 assertNull(session.sessions().getUserSession(realm, e));
             }
+            for (String e : expiredClientSessions) {
+                assertNull(session.sessions().getClientSession(realm, e));
+            }
 
             for (String v : valid) {
                 assertNotNull(session.sessions().getUserSession(realm, v));
             }
+            for (String e : validClientSessions) {
+                assertNotNull(session.sessions().getClientSession(realm, e));
+            }
         } finally {
             Time.setOffset(0);
         }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
index fcd51b3..ca4b7ee 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
@@ -139,7 +139,7 @@ public class RefreshTokenTest {
 
         Assert.assertThat(token.getExpiration() - Time.currentTime(), allOf(greaterThanOrEqualTo(250), lessThanOrEqualTo(300)));
         int actual = refreshToken.getExpiration() - Time.currentTime();
-        Assert.assertThat(actual, allOf(greaterThanOrEqualTo(559), lessThanOrEqualTo(600)));
+        Assert.assertThat(actual, allOf(greaterThanOrEqualTo(1799), lessThanOrEqualTo(1800)));
 
         Assert.assertEquals(sessionId, refreshToken.getSessionState());
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java
index 8817c9c..a244c32 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java
@@ -41,6 +41,9 @@ public class LoginPasswordResetPage extends AbstractPage {
     @FindBy(className = "feedback-error")
     private WebElement emailErrorMessage;
 
+    @FindBy(partialLinkText = "Back to Login")
+    private WebElement backToLogin;
+
     public void changePassword(String username) {
         usernameInput.sendKeys(username);
 
@@ -63,4 +66,8 @@ public class LoginPasswordResetPage extends AbstractPage {
         return emailErrorMessage != null ? emailErrorMessage.getText() : null;
     }
 
+    public void backToLogin() {
+        backToLogin.click();
+    }
+
 }
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 06e793f..0808dcb 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
@@ -24,6 +24,7 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.Socket;
+import org.keycloak.adapters.KeycloakConfigResolver;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -133,9 +134,17 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
     }
 
     public void deployApplication(String name, String contextPath, Class<? extends Servlet> servletClass, String adapterConfigPath, String role, boolean isConstrained) {
+        deployApplication(name, contextPath, servletClass, adapterConfigPath, role, isConstrained, null);
+    }
+
+    public void deployApplication(String name, String contextPath, Class<? extends Servlet> servletClass, String adapterConfigPath, String role, boolean isConstrained, Class<? extends KeycloakConfigResolver> keycloakConfigResolver) {
         String constraintUrl = "/*";
         DeploymentInfo di = createDeploymentInfo(name, contextPath, servletClass);
-        di.addInitParameter("keycloak.config.file", adapterConfigPath);
+        if (null == keycloakConfigResolver) {
+            di.addInitParameter("keycloak.config.file", adapterConfigPath);
+        } else {
+            di.addInitParameter("keycloak.config.resolver", keycloakConfigResolver.getCanonicalName());
+        }
         if (isConstrained) {
             SecurityConstraint constraint = new SecurityConstraint();
             WebResourceCollection collection = new WebResourceCollection();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java
index 302fc91..6c104fb 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java
@@ -71,8 +71,7 @@ public class SamlBindingTest {
     @WebResource
     protected LoginPage loginPage;
 
-    @Test
-    @Ignore
+    //@Test
     public void runit() throws Exception {
         Thread.sleep(10000000);
     }
diff --git a/testsuite/integration/src/test/resources/adapter-test/tenant1-keycloak.json b/testsuite/integration/src/test/resources/adapter-test/tenant1-keycloak.json
new file mode 100644
index 0000000..80bff8e
--- /dev/null
+++ b/testsuite/integration/src/test/resources/adapter-test/tenant1-keycloak.json
@@ -0,0 +1,10 @@
+{
+  "realm" : "tenant1",
+  "resource" : "multi-tenant",
+  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+  "auth-server-url" : "http://localhost:8081/auth",
+  "ssl-required" : "external",
+  "credentials" : {
+      "secret": "password"
+   }
+}
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/adapter-test/tenant1-realm.json b/testsuite/integration/src/test/resources/adapter-test/tenant1-realm.json
new file mode 100644
index 0000000..783776f
--- /dev/null
+++ b/testsuite/integration/src/test/resources/adapter-test/tenant1-realm.json
@@ -0,0 +1,75 @@
+{
+    "id": "tenant1",
+    "realm": "tenant1",
+    "enabled": true,
+    "accessTokenLifespan": 3000,
+    "accessCodeLifespan": 10,
+    "accessCodeLifespanUserAction": 6000,
+    "sslRequired": "external",
+    "registrationAllowed": false,
+    "social": false,
+    "passwordCredentialGrantAllowed": true,
+    "updateProfileOnInitialSocialLogin": false,
+    "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
+    "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+    "requiredCredentials": [ "password" ],
+    "users" : [
+        {
+            "username" : "bburke@redhat.com",
+            "enabled": true,
+            "email" : "bburke@redhat.com",
+            "firstName": "Bill",
+            "lastName": "Burke",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "realmRoles": [ "user" ],
+            "applicationRoles": {
+                "multi-tenant": [ "user" ]
+            }
+        },
+        {
+            "username" : "user-tenant1",
+            "enabled": true,
+            "email" : "user-tenant1@redhat.com",
+            "firstName": "Bill",
+            "lastName": "Burke",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "user-tenant1" }
+            ],
+            "realmRoles": [ "user" ],
+            "applicationRoles": {
+                "multi-tenant": [ "user" ]
+            }
+        }
+    ],
+    "roles" : {
+        "realm" : [
+            {
+                "name": "user",
+                "description": "User privileges"
+            }
+        ]
+    },
+    "scopeMappings": [
+        {
+            "client": "multi-tenant",
+            "roles": ["user"]
+        }
+
+    ],
+    "applications": [
+        {
+            "name": "multi-tenant",
+            "enabled": true,
+            "adminUrl": "http://localhost:8081/multi-tenant",
+            "baseUrl": "http://localhost:8081/multi-tenant",
+            "redirectUris": [
+                "http://localhost:8081/multi-tenant/*"
+            ],
+            "secret": "password"
+        }
+    ]
+}
diff --git a/testsuite/integration/src/test/resources/adapter-test/tenant2-keycloak.json b/testsuite/integration/src/test/resources/adapter-test/tenant2-keycloak.json
new file mode 100644
index 0000000..deb538d
--- /dev/null
+++ b/testsuite/integration/src/test/resources/adapter-test/tenant2-keycloak.json
@@ -0,0 +1,10 @@
+{
+  "realm" : "tenant2",
+  "resource" : "multi-tenant",
+  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+  "auth-server-url" : "http://localhost:8081/auth",
+  "ssl-required" : "external",
+  "credentials" : {
+      "secret": "password"
+   }
+}
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/adapter-test/tenant2-realm.json b/testsuite/integration/src/test/resources/adapter-test/tenant2-realm.json
new file mode 100644
index 0000000..1c17f11
--- /dev/null
+++ b/testsuite/integration/src/test/resources/adapter-test/tenant2-realm.json
@@ -0,0 +1,75 @@
+{
+    "id": "tenant2",
+    "realm": "tenant2",
+    "enabled": true,
+    "accessTokenLifespan": 3000,
+    "accessCodeLifespan": 10,
+    "accessCodeLifespanUserAction": 6000,
+    "sslRequired": "external",
+    "registrationAllowed": false,
+    "social": false,
+    "passwordCredentialGrantAllowed": true,
+    "updateProfileOnInitialSocialLogin": false,
+    "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
+    "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+    "requiredCredentials": [ "password" ],
+    "users" : [
+        {
+            "username" : "bburke@redhat.com",
+            "enabled": true,
+            "email" : "bburke@redhat.com",
+            "firstName": "Bill",
+            "lastName": "Burke",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "realmRoles": [ "user" ],
+            "applicationRoles": {
+                "multi-tenant": [ "user" ]
+            }
+        },
+        {
+            "username" : "user-tenant2",
+            "enabled": true,
+            "email" : "user-tenant2@redhat.com",
+            "firstName": "Bill",
+            "lastName": "Burke",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "user-tenant2" }
+            ],
+            "realmRoles": [ "user" ],
+            "applicationRoles": {
+                "multi-tenant": [ "user" ]
+            }
+        }
+    ],
+    "roles" : {
+        "realm" : [
+            {
+                "name": "user",
+                "description": "User privileges"
+            }
+        ]
+    },
+    "scopeMappings": [
+        {
+            "client": "multi-tenant",
+            "roles": ["user"]
+        }
+
+    ],
+    "applications": [
+        {
+            "name": "multi-tenant",
+            "enabled": true,
+            "adminUrl": "http://localhost:8081/multi-tenant",
+            "baseUrl": "http://localhost:8081/multi-tenant",
+            "redirectUris": [
+                "http://localhost:8081/multi-tenant/*"
+            ],
+            "secret": "password"
+        }
+    ]
+}
diff --git a/testsuite/tomcat7/pom.xml b/testsuite/tomcat7/pom.xml
index f667d87..f53a489 100755
--- a/testsuite/tomcat7/pom.xml
+++ b/testsuite/tomcat7/pom.xml
@@ -10,7 +10,7 @@
     <modelVersion>4.0.0</modelVersion>
 
     <artifactId>keycloak-testsuite-tomcat7</artifactId>
-    <name>Keycloak Tomcat 7Integration TestSuite</name>
+    <name>Keycloak Tomcat 7 Integration TestSuite</name>
     <description />
 
    <dependencies>
diff --git a/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/Tomcat7Test.java b/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/Tomcat7Test.java
index e0603fc..791e0d3 100755
--- a/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/Tomcat7Test.java
+++ b/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/Tomcat7Test.java
@@ -60,6 +60,7 @@ import java.io.OutputStream;
 import java.net.URL;
 import java.security.Principal;
 import java.util.Map;
+import java.util.regex.Matcher;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -106,12 +107,11 @@ public class Tomcat7Test {
 
     @BeforeClass
     public static void initTomcat() throws Exception {
-
-        String webappDirLocation = "src/test/resources/tomcat-test/webapp/";
         URL dir = Tomcat7Test.class.getResource("/tomcat-test/webapp/META-INF/context.xml");
         File webappDir = new File(dir.getFile()).getParentFile().getParentFile();
         tomcat = new Tomcat();
-
+        String baseDir = getBaseDirectory();
+        tomcat.setBaseDir(baseDir);
         tomcat.setPort(8080);
 
         tomcat.addWebapp("/customer-portal", webappDir.toString());
@@ -167,7 +167,24 @@ public class Tomcat7Test {
     }
 
 
+    private static String getBaseDirectory() {
+        String dirPath = null;
+        String relativeDirPath = "testsuite" + File.separator + "tomcat7" + File.separator + "target";
+
+        if (System.getProperties().containsKey("maven.home")) {
+            dirPath = System.getProperty("user.dir").replaceFirst("testsuite.tomcat7.*", Matcher.quoteReplacement(relativeDirPath));
+        } else {
+            for (String c : System.getProperty("java.class.path").split(File.pathSeparator)) {
+                if (c.contains(File.separator + "testsuite" + File.separator + "tomcat7")) {
+                    dirPath = c.replaceFirst("testsuite.tomcat7.*", Matcher.quoteReplacement(relativeDirPath));
+                    break;
+                }
+            }
+        }
 
+        String absolutePath = new File(dirPath).getAbsolutePath();
+        return absolutePath;
+    }