keycloak-uncached

Changes

distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-prepare.cli 7(+0 -7)

distribution/server-overlay/wf9-server-overlay/cli/keycloak-prepare.cli 6(+0 -6)

Details

diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index e28aa97..248c4de 100755
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -228,7 +228,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
 
 
         String descriptor =
-                "<EntityDescriptor entityID=\"" + getEntityId(uriInfo, realm) + "\">\n" +
+                "<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"" + getEntityId(uriInfo, realm) + "\">\n" +
                 "    <SPSSODescriptor AuthnRequestsSigned=\"" + getConfig().isWantAuthnRequestsSigned() + "\"\n" +
                 "            protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext\">\n" +
                 "        <NameIDFormat>" + getConfig().getNameIDPolicyFormat() + "\n" +
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml
index 9e48a6a..3d5b99f 100644
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml
@@ -2,6 +2,13 @@
 <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
     <changeSet author="mposolda@redhat.com" id="1.6.0">
 
+        <addColumn tableName="REALM">
+            <column name="OFFLINE_SESSION_IDLE_TIMEOUT" type="INT"/>
+            <column name="REVOKE_REFRESH_TOKEN" type="BOOLEAN" defaultValueBoolean="false">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+
         <addColumn tableName="KEYCLOAK_ROLE">
             <column name="SCOPE_PARAM_REQUIRED" type="BOOLEAN" defaultValueBoolean="false">
                 <constraints nullable="false"/>
@@ -43,16 +50,11 @@
             <column name="OFFLINE" type="BOOLEAN" defaultValueBoolean="false">
                 <constraints nullable="false"/>
             </column>
+            <column name="TIMESTAMP" type="INT"/>
             <column name="DATA" type="CLOB"/>
         </createTable>
 
         <addPrimaryKey columnNames="USER_SESSION_ID, OFFLINE" constraintName="CONSTRAINT_OFFLINE_US_SES_PK" tableName="OFFLINE_USER_SESSION"/>
         <addPrimaryKey columnNames="CLIENT_SESSION_ID, OFFLINE" constraintName="CONSTRAINT_OFFLINE_CL_SES_PK" tableName="OFFLINE_CLIENT_SESSION"/>
-
-        <addColumn tableName="REALM">
-            <column name="REVOKE_REFRESH_TOKEN" type="BOOLEAN" defaultValueBoolean="false">
-                <constraints nullable="false"/>
-            </column>
-        </addColumn>
     </changeSet>
 </databaseChangeLog>
\ No newline at end of file
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 1bec10b..387ab7e 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -14,6 +14,7 @@ public class RealmRepresentation {
     protected Integer accessTokenLifespan;
     protected Integer ssoSessionIdleTimeout;
     protected Integer ssoSessionMaxLifespan;
+    protected Integer offlineSessionIdleTimeout;
     protected Integer accessCodeLifespan;
     protected Integer accessCodeLifespanUserAction;
     protected Integer accessCodeLifespanLogin;
@@ -199,6 +200,14 @@ public class RealmRepresentation {
         this.ssoSessionMaxLifespan = ssoSessionMaxLifespan;
     }
 
+    public Integer getOfflineSessionIdleTimeout() {
+        return offlineSessionIdleTimeout;
+    }
+
+    public void setOfflineSessionIdleTimeout(Integer offlineSessionIdleTimeout) {
+        this.offlineSessionIdleTimeout = offlineSessionIdleTimeout;
+    }
+
     public List<ScopeMappingRepresentation> getScopeMappings() {
         return scopeMappings;
     }
@@ -620,10 +629,14 @@ public class RealmRepresentation {
     }
 
     public Set<String> getSupportedLocales() {
+        return supportedLocales;
+    }
+
+    public void addSupportedLocales(String locale) {
         if(supportedLocales == null){
-            supportedLocales = new HashSet<String>();
+            supportedLocales = new HashSet<>();
         }
-        return supportedLocales;
+        supportedLocales.add(locale);
     }
 
     public void setSupportedLocales(Set<String> supportedLocales) {
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
index 669cf41..3e4315c 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
@@ -59,7 +59,7 @@
 
     "connectionsInfinispan": {
         "default" : {
-            "cacheContainer" : "java:jboss/infinispan/Keycloak"
+            "cacheContainer" : "java:comp/env/infinispan/Keycloak"
         }
     }
 }
\ No newline at end of file
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/infinispan/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/infinispan/main/module.xml
new file mode 100755
index 0000000..65e3dc2
--- /dev/null
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/infinispan/main/module.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014, Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags. See the copyright.txt file in the
+  ~ distribution for a full listing of individual contributors.
+  ~
+  ~ This is free software; you can redistribute it and/or modify it
+  ~ under the terms of the GNU Lesser General Public License as
+  ~ published by the Free Software Foundation; either version 2.1 of
+  ~ the License, or (at your option) any later version.
+  ~
+  ~ This software is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  ~ Lesser General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU Lesser General Public
+  ~ License along with this software; if not, write to the Free
+  ~ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  ~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  -->
+
+<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-server-subsystem.infinispan">
+
+    <resources>
+        <!-- Insert resources here -->
+    </resources>
+
+    <dependencies>
+        <module name="org.infinispan" export="true"/>
+    </dependencies>
+</module>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
index 11f8141..c339f44 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
@@ -54,6 +54,9 @@
             <module name="org.jboss.resteasy.resteasy-jaxrs"/>
 
             <module name="org.jboss.msc"/>
+
+            <!-- suppress unsupported dependency 'org.infinispan:main' warning -->
+            <module name="org.keycloak.keycloak-server-subsystem.infinispan"/>
         </dependencies>
         <exclusions>
             <module name="org.jboss.resteasy.resteasy-jackson2-provider"/>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/web.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/web.xml
index 164f6be..f59d1d3 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/web.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/web.xml
@@ -39,4 +39,10 @@
         <servlet-name>Keycloak REST Interface</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
+
+    <resource-env-ref>
+        <resource-env-ref-name>infinispan/Keycloak</resource-env-ref-name>
+        <resource-env-ref-type>org.infinispan.manager.EmbeddedCacheManager</resource-env-ref-type>
+        <lookup-name>java:jboss/infinispan/Keycloak</lookup-name>
+    </resource-env-ref>
 </web-app>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/build.xml b/distribution/server-overlay/eap6/eap6-server-modules/build.xml
index 2276471..60ccb65 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/build.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/build.xml
@@ -290,6 +290,8 @@
 
         <module-def name="org.keycloak.keycloak-server-subsystem"/>
 
+        <module-def name="org.keycloak.keycloak-server-subsystem.infinispan"/>
+
         <module-def name="org.keycloak.keycloak-wildfly-extensions">
             <maven-resource group="org.keycloak" artifact="keycloak-wildfly-extensions"/>
         </module-def>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
index 11f8141..c339f44 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
@@ -54,6 +54,9 @@
             <module name="org.jboss.resteasy.resteasy-jaxrs"/>
 
             <module name="org.jboss.msc"/>
+
+            <!-- suppress unsupported dependency 'org.infinispan:main' warning -->
+            <module name="org.keycloak.keycloak-server-subsystem.infinispan"/>
         </dependencies>
         <exclusions>
             <module name="org.jboss.resteasy.resteasy-jackson2-provider"/>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/web.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/web.xml
index 164f6be..f59d1d3 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/web.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/web.xml
@@ -39,4 +39,10 @@
         <servlet-name>Keycloak REST Interface</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
+
+    <resource-env-ref>
+        <resource-env-ref-name>infinispan/Keycloak</resource-env-ref-name>
+        <resource-env-ref-type>org.infinispan.manager.EmbeddedCacheManager</resource-env-ref-type>
+        <lookup-name>java:jboss/infinispan/Keycloak</lookup-name>
+    </resource-env-ref>
 </web-app>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-server-subsystem/infinispan/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-server-subsystem/infinispan/main/module.xml
new file mode 100755
index 0000000..65e3dc2
--- /dev/null
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-server-subsystem/infinispan/main/module.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014, Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags. See the copyright.txt file in the
+  ~ distribution for a full listing of individual contributors.
+  ~
+  ~ This is free software; you can redistribute it and/or modify it
+  ~ under the terms of the GNU Lesser General Public License as
+  ~ published by the Free Software Foundation; either version 2.1 of
+  ~ the License, or (at your option) any later version.
+  ~
+  ~ This software is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  ~ Lesser General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU Lesser General Public
+  ~ License along with this software; if not, write to the Free
+  ~ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  ~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  -->
+
+<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-server-subsystem.infinispan">
+
+    <resources>
+        <!-- Insert resources here -->
+    </resources>
+
+    <dependencies>
+        <module name="org.infinispan" export="true"/>
+    </dependencies>
+</module>
diff --git a/distribution/server-overlay/eap6/eap6-server-overlay/assembly.xml b/distribution/server-overlay/eap6/eap6-server-overlay/assembly.xml
index 580b1d7..b3324df 100755
--- a/distribution/server-overlay/eap6/eap6-server-overlay/assembly.xml
+++ b/distribution/server-overlay/eap6/eap6-server-overlay/assembly.xml
@@ -20,13 +20,13 @@
                 <include>**/**</include>
             </includes>
         </fileSet>
-        <!--<fileSet>
+        <fileSet>
             <directory>cli</directory>
             <includes>
                 <include>*.cli</include>
             </includes>
             <outputDirectory>bin</outputDirectory>
-        </fileSet>-->
+        </fileSet>
     </fileSets>
 
     <files>
diff --git a/distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-install.cli b/distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-install.cli
index cc59431..156197a 100644
--- a/distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-install.cli
+++ b/distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-install.cli
@@ -1,2 +1,10 @@
+/subsystem=datasources/data-source=KeycloakDS/:add(connection-url="jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE",driver-name=h2,jndi-name=java:jboss/datasources/KeycloakDS,password=sa,user-name=sa,use-java-context=true,enabled=true)
+/subsystem=logging/logger=org.jboss.resteasy.resteasy_jaxrs.i18n/:add(level=ERROR)
+/subsystem=infinispan/cache-container=keycloak:add(jndi-name="infinispan/Keycloak",start="EAGER")
+/subsystem=infinispan/cache-container=keycloak/local-cache=realms:add()
+/subsystem=infinispan/cache-container=keycloak/local-cache=users:add()
+/subsystem=infinispan/cache-container=keycloak/local-cache=sessions:add()
+/subsystem=infinispan/cache-container=keycloak/local-cache=loginFailures:add()
 /extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem)
-/subsystem=keycloak-server:add(web-context=auth)
\ No newline at end of file
+/subsystem=keycloak-server:add(web-context=auth)
+:shutdown(restart=true)
\ No newline at end of file
diff --git a/distribution/server-overlay/wf9-server-overlay/assembly.xml b/distribution/server-overlay/wf9-server-overlay/assembly.xml
index 0427f80..8f0daa2 100755
--- a/distribution/server-overlay/wf9-server-overlay/assembly.xml
+++ b/distribution/server-overlay/wf9-server-overlay/assembly.xml
@@ -47,13 +47,13 @@
             </includes>
             <outputDirectory></outputDirectory>
         </fileSet>
-        <!--<fileSet>
+        <fileSet>
             <directory>cli</directory>
             <includes>
                 <include>*.cli</include>
             </includes>
             <outputDirectory>bin</outputDirectory>
-        </fileSet>-->
+        </fileSet>
     </fileSets>
 
     <files>
diff --git a/distribution/server-overlay/wf9-server-overlay/cli/keycloak-install.cli b/distribution/server-overlay/wf9-server-overlay/cli/keycloak-install.cli
index cc59431..d6c3988 100644
--- a/distribution/server-overlay/wf9-server-overlay/cli/keycloak-install.cli
+++ b/distribution/server-overlay/wf9-server-overlay/cli/keycloak-install.cli
@@ -1,2 +1,8 @@
+/subsystem=datasources/data-source=KeycloakDS/:add(connection-url="jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE",driver-name=h2,jndi-name=java:jboss/datasources/KeycloakDS,password=sa,user-name=sa,use-java-context=true)
+/subsystem=infinispan/cache-container=keycloak:add(jndi-name="infinispan/Keycloak")
+/subsystem=infinispan/cache-container=keycloak/local-cache=realms:add()
+/subsystem=infinispan/cache-container=keycloak/local-cache=users:add()
+/subsystem=infinispan/cache-container=keycloak/local-cache=sessions:add()
+/subsystem=infinispan/cache-container=keycloak/local-cache=loginFailures:add()
 /extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem)
 /subsystem=keycloak-server:add(web-context=auth)
\ No newline at end of file
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
index 8de75d7..558f943 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
@@ -43,7 +43,7 @@
 
 
         <section id="overlay_install">
-            <title>Install on existing WildFly 9.0.0.Final</title>
+            <title>Install on existing WildFly 9.0.1.Final</title>
             <para>
                 Keycloak can be installed into an existing WildFly 9.0.0.Final server. To do this download
                 <literal>keycloak-overlay-&project.version;.zip</literal> or <literal>keycloak-overlay-&project.version;.tar.gz</literal>.
@@ -59,22 +59,23 @@
                 (username: <emphasis>admin</emphasis> and password: <emphasis>admin</emphasis>). Keycloak will then prompt you to
                 enter in a new password.
             </para>
-            <!--<para>-->
-                <!--To add Keycloak to other sever configurations (standalone.xml, standalone-ha.xml, etc.) start the server with-->
-                <!--the desired server-config. If you are running the server in standalone mode run:-->
-                <!--<programlisting>&lt;WILDFLY_HOME&gt;/bin/jboss-cli.sh -c &#45;&#45;file=keycloak-prepare.cli</programlisting>-->
-                <!--Or if you are running in clustering (HA) mode run:-->
-                <!--<programlisting>&lt;WILDFLY_HOME&gt;/bin/jboss-cli.sh -c &#45;&#45;file=keycloak-prepare-ha.cli</programlisting>-->
-                <!--After that you need to restart the server, you can do this with:-->
-                <!--<programlisting>&lt;WILDFLY_HOME&gt;/bin/jboss-cli.sh -c :reload</programlisting>-->
-                <!--Finally you need to run:-->
-                <!--<programlisting>&lt;WILDFLY_HOME&gt;/bin/jboss-cli.sh -c &#45;&#45;file=keycloak-install.cli</programlisting>-->
-            <!--</para>-->
+            <para>
+                To add Keycloak to other sever configurations (standalone.xml, standalone-ha.xml, etc.) start the server with
+                the desired server-config. If you are running the server in standalone mode run:
+                <programlisting>cd &lt;WILDFLY_HOME&gt;/bin
+                    ./jboss-cli.sh -c --file=keycloak-install.cli</programlisting>
+                Or if you are running in clustering (HA) mode (by having used -c standalone-ha.xml) then run:
+                <programlisting>cd &lt;WILDFLY_HOME&gt;/bin
+                    ./jboss-cli.sh -c --file=keycloak-install-ha.cli</programlisting>
+                You may see exceptions in the server log, but after restarting the server they should be gone.
+                You can restart the server with:
+                <programlisting>&lt;WILDFLY_HOME&gt;/bin/jboss-cli.sh -c :reload</programlisting>
+            </para>
         </section>
         <section>
             <title>Install on existing JBoss EAP 6.4.0.GA</title>
             <para>
-                Same procedure as WildFly 9.0.0.Final, but download <literal>keycloak-overlay-eap6-&project.version;.zip</literal> or <literal>keycloak-overlay-eap6-&project.version;.tar.gz</literal>.
+                Same procedure as WildFly 9.0.1.Final, but download <literal>keycloak-overlay-eap6-&project.version;.zip</literal> or <literal>keycloak-overlay-eap6-&project.version;.tar.gz</literal>.
             </para>
         </section>
         <section>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 802645e..f13d6e8 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -76,6 +76,8 @@ days=Days
 sso-session-max=SSO Session Max
 sso-session-idle.tooltip=Time a session is allowed to be idle before it expires. Tokens and browser sessions are invalidated when a session is expired.
 sso-session-max.tooltip=Max time before a session is expired. Tokens and browser sessions are invalidated when a session is expired.
+offline-session-idle=Offline Session Idle
+offline-session-idle.tooltip=Time an offline session is allowed to be idle before it expires. You need to use offline token to refresh at least once within this period, otherwise offline session will expire.
 access-token-lifespan=Access Token Lifespan
 access-token-lifespan.tooltip=Max time before an access token is expired. This value is recommended to be short relative to the SSO timeout.
 client-login-timeout=Client login timeout
@@ -336,6 +338,8 @@ offline-tokens.tooltip=Total number of offline tokens for this client.
 show-offline-tokens=Show Offline Tokens
 show-offline-tokens.tooltip=Warning, this is a potentially expensive operation depending on number of offline tokens.
 token-issued=Token Issued
+last-access=Last Access
+last-refresh=Last Refresh
 key-export=Key Export
 key-import=Key Import
 export-saml-key=Export SAML Key
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
index 4a1d5a3..f1d922b 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -498,6 +498,24 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'UserConsentsCtrl'
         })
+        .when('/realms/:realm/users/:user/offline-sessions/:client', {
+            templateUrl : resourceUrl + '/partials/user-offline-sessions.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                user : function(UserLoader) {
+                    return UserLoader();
+                },
+                client : function(ClientLoader) {
+                    return ClientLoader();
+                },
+                offlineSessions : function(UserOfflineSessionsLoader) {
+                    return UserOfflineSessionsLoader();
+                }
+            },
+            controller : 'UserOfflineSessionsCtrl'
+        })
         .when('/realms/:realm/users', {
             templateUrl : resourceUrl + '/partials/user-list.html',
             resolve : {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index 27a9794..e35e1c6 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -779,7 +779,7 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
         } else if ($scope.client.attributes['saml.signature.algorithm'] == 'DSA_SHA1') {
             $scope.signatureAlgorithm = $scope.signatureAlgorithms[3];
         }
-        if ($scope.client.attributes['saml_name_id_format'] == 'unspecified') {
+        if ($scope.client.attributes['saml_name_id_format'] == 'username') {
             $scope.nameIdFormat = $scope.nameIdFormats[0];
         } else if ($scope.client.attributes['saml_name_id_format'] == 'email') {
             $scope.nameIdFormat = $scope.nameIdFormats[1];
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index af93ac8..023cdf1 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -912,6 +912,12 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http, 
         $scope.realm.ssoSessionMaxLifespan = TimeUnit.convert($scope.realm.ssoSessionMaxLifespan, from, to);
     });
 
+    $scope.realm.offlineSessionIdleTimeoutUnit = TimeUnit.autoUnit(realm.offlineSessionIdleTimeout);
+    $scope.realm.offlineSessionIdleTimeout = TimeUnit.toUnit(realm.offlineSessionIdleTimeout, $scope.realm.offlineSessionIdleTimeoutUnit);
+    $scope.$watch('realm.offlineSessionIdleTimeoutUnit', function(to, from) {
+        $scope.realm.offlineSessionIdleTimeout = TimeUnit.convert($scope.realm.offlineSessionIdleTimeout, from, to);
+    });
+
     $scope.realm.accessCodeLifespanUnit = TimeUnit.autoUnit(realm.accessCodeLifespan);
     $scope.realm.accessCodeLifespan = TimeUnit.toUnit(realm.accessCodeLifespan, $scope.realm.accessCodeLifespanUnit);
     $scope.$watch('realm.accessCodeLifespanUnit', function(to, from) {
@@ -943,6 +949,7 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http, 
         var realmCopy = angular.copy($scope.realm);
         delete realmCopy["accessTokenLifespanUnit"];
         delete realmCopy["ssoSessionMaxLifespanUnit"];
+        delete realmCopy["offlineSessionIdleTimeoutUnit"];
         delete realmCopy["accessCodeLifespanUnit"];
         delete realmCopy["ssoSessionIdleTimeoutUnit"];
         delete realmCopy["accessCodeLifespanUserActionUnit"];
@@ -951,6 +958,7 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http, 
         realmCopy.accessTokenLifespan = TimeUnit.toSeconds($scope.realm.accessTokenLifespan, $scope.realm.accessTokenLifespanUnit)
         realmCopy.ssoSessionIdleTimeout = TimeUnit.toSeconds($scope.realm.ssoSessionIdleTimeout, $scope.realm.ssoSessionIdleTimeoutUnit)
         realmCopy.ssoSessionMaxLifespan = TimeUnit.toSeconds($scope.realm.ssoSessionMaxLifespan, $scope.realm.ssoSessionMaxLifespanUnit)
+        realmCopy.offlineSessionIdleTimeout = TimeUnit.toSeconds($scope.realm.offlineSessionIdleTimeout, $scope.realm.offlineSessionIdleTimeoutUnit)
         realmCopy.accessCodeLifespan = TimeUnit.toSeconds($scope.realm.accessCodeLifespan, $scope.realm.accessCodeLifespanUnit)
         realmCopy.accessCodeLifespanUserAction = TimeUnit.toSeconds($scope.realm.accessCodeLifespanUserAction, $scope.realm.accessCodeLifespanUserActionUnit)
         realmCopy.accessCodeLifespanLogin = TimeUnit.toSeconds($scope.realm.accessCodeLifespanLogin, $scope.realm.accessCodeLifespanLoginUnit)
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
index 3746740..4f45d63 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
@@ -216,6 +216,17 @@ module.controller('UserConsentsCtrl', function($scope, realm, user, userConsents
     }
 });
 
+module.controller('UserOfflineSessionsCtrl', function($scope, $location, realm, user, client, offlineSessions) {
+    $scope.realm = realm;
+    $scope.user = user;
+    $scope.client = client;
+    $scope.offlineSessions = offlineSessions;
+
+    $scope.cancel = function() {
+         $location.url("/realms/" + realm.realm + '/users/' + user.id + '/consents');
+    };
+});
+
 
 module.controller('UserListCtrl', function($scope, realm, User, UserImpersonation, BruteForce, Notifications, $route, Dialog) {
     $scope.realm = realm;
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
index 9c05940..7706f0f 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
@@ -181,6 +181,16 @@ module.factory('UserSessionsLoader', function(Loader, UserSessions, $route, $q) 
     });
 });
 
+module.factory('UserOfflineSessionsLoader', function(Loader, UserOfflineSessions, $route, $q) {
+    return Loader.query(UserOfflineSessions, function() {
+        return {
+            realm : $route.current.params.realm,
+            user : $route.current.params.user,
+            client : $route.current.params.client
+        }
+    });
+});
+
 module.factory('UserFederatedIdentityLoader', function(Loader, UserFederatedIdentities, $route, $q) {
     return Loader.query(UserFederatedIdentities, function() {
         return {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
index b92fd83..ec0d475 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
@@ -369,6 +369,13 @@ module.factory('UserSessions', function($resource) {
         user : '@user'
     });
 });
+module.factory('UserOfflineSessions', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/users/:user/offline-sessions/:client', {
+        realm : '@realm',
+        user : '@user',
+        client : '@client'
+    });
+});
 
 module.factory('UserSessionLogout', function($resource) {
     return $resource(authUrl + '/admin/realms/:realm/sessions/:session', {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-offline-sessions.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-offline-sessions.html
index 86f574b..3d2aaf7 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-offline-sessions.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-offline-sessions.html
@@ -21,7 +21,7 @@
     <table class="table table-striped table-bordered" data-ng-show="count > 0">
         <thead>
         <tr>
-            <th class="kc-table-actions" colspan="3">
+            <th class="kc-table-actions" colspan="4">
                 <div class="pull-right">
                     <a class="btn btn-default" ng-click="loadUsers()" tooltip-placement="left" tooltip-trigger="mouseover mouseout" tooltip="{{:: 'show-offline-tokens.tooltip' | translate}}">{{:: 'show-offline-tokens' | translate}}</a>
                 </div>
@@ -31,6 +31,7 @@
             <th>{{:: 'user' | translate}}</th>
             <th>{{:: 'from-ip' | translate}}</th>
             <th>{{:: 'token-issued' | translate}}</th>
+            <th>{{:: 'last-refresh' | translate}}</th>
         </tr>
         </thead>
         <tfoot data-ng-show="sessions && (sessions.length >= 5 || query.first != 0)">
@@ -49,6 +50,7 @@
             <td><a href="#/realms/{{realm.realm}}/users/{{session.userId}}">{{session.username}}</a></td>
             <td>{{session.ipAddress}}</td>
             <td>{{session.start | date:'medium'}}</td>
+            <td>{{session.lastAccess | date:'medium'}}</td>
         </tr>
         </tbody>
     </table>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html
index bb55022..dee13f6 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html
@@ -49,6 +49,23 @@
         </div>
 
         <div class="form-group">
+            <label class="col-md-2 control-label" for="offlineSessionIdleTimeout">{{:: 'offline-session-idle' | translate}}</label>
+
+            <div class="col-md-6 time-selector">
+                <input class="form-control" type="number" required min="1"
+                       max="31536000" data-ng-model="realm.offlineSessionIdleTimeout"
+                       id="offlineSessionIdleTimeout" name="offlineSessionIdleTimeout"/>
+                <select class="form-control" name="offlineSessionIdleTimeoutUnit" data-ng-model="realm.offlineSessionIdleTimeoutUnit">
+                    <option data-ng-selected="!realm.offlineSessionIdleTimeoutUnit" value="Seconds">{{:: 'seconds' | translate}}</option>
+                    <option value="Minutes">{{:: 'minutes' | translate}}</option>
+                    <option value="Hours">{{:: 'hours' | translate}}</option>
+                    <option value="Days">{{:: 'days' | translate}}</option>
+                </select>
+            </div>
+            <kc-tooltip>{{:: 'offline-session-idle.tooltip' | translate}}</kc-tooltip>
+        </div>
+
+        <div class="form-group">
             <label class="col-md-2 control-label" for="accessTokenLifespan">{{:: 'access-token-lifespan' | translate}}</label>
 
             <div class="col-md-6 time-selector">
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-consents.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-consents.html
index cf6db99..0d0a92c 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-consents.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-consents.html
@@ -38,7 +38,7 @@
             </td>
             <td>
                 <span data-ng-repeat="additionalGrant in consent.additionalGrants">
-                    <span ng-if="!$first">, </span>{{additionalGrant}}
+                    <span ng-if="!$first">, </span><a href="#/realms/{{realm.realm}}/users/{{user.id}}/offline-sessions/{{additionalGrant.client}}">{{additionalGrant.key}}</a>
                 </span>
             </td>
             <td class="kc-action-cell">
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-offline-sessions.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-offline-sessions.html
new file mode 100644
index 0000000..bc7ad50
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-offline-sessions.html
@@ -0,0 +1,35 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+    <ol class="breadcrumb">
+        <li><a href="#/realms/{{realm.realm}}/users">Users</a></li>
+        <li><a href="#/realms/{{realm.realm}}/users/{{user.id}}">{{user.username}}</a></li>
+        <li><a href="#/realms/{{realm.realm}}/users/{{user.id}}/consents">consents</a></li>
+        <li>{{client.clientId}}</li>
+    </ol>
+
+    <kc-tabs-user></kc-tabs-user>
+
+    <table class="table table-striped table-bordered">
+        <thead>
+        <tr>
+            <th>IP Address</th>
+            <th>Started</th>
+            <th>Last Refresh</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr data-ng-repeat="session in offlineSessions">
+            <td>{{session.ipAddress}}</td>
+            <td>{{session.start | date:'medium'}}</td>
+            <td>{{session.lastAccess | date:'medium'}}</td>
+        </tr>
+        </tbody>
+    </table>
+
+    <div class="form-group">
+        <div class="col-md-10 col-md-offset-2">
+            <button kc-cancel data-ng-click="cancel()">Back</button>
+        </div>
+    </div>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-totp.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-totp.ftl
index c5d28a1..e472fff 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/login-totp.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/login/login-totp.ftl
@@ -25,6 +25,7 @@
                 <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
                     <div class="${properties.kcFormButtonsWrapperClass!}">
                         <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="login" id="kc-login" type="submit" value="${msg("doLogIn")}"/>
+                        <input class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="cancel" id="kc-cancel" type="submit" value="${msg("doCancel")}"/>
                     </div>
                 </div>
             </div>
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java
index e21ee79..a2490b6 100755
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java
@@ -46,6 +46,24 @@ public interface UserResource {
     @Path("reset-password")
     public void resetPassword(CredentialRepresentation credentialRepresentation);
 
+    /**
+     * Use executeActionsEmail and pass in the UPDATE_PASSWORD required action
+     *
+     */
+    @PUT
+    @Path("reset-password-email")
+    @Deprecated
+    public void resetPasswordEmail();
+
+    /**
+     * Use executeActionsEmail and pass in the UPDATE_PASSWORD required action
+     *
+     */
+    @PUT
+    @Path("reset-password-email")
+    @Deprecated
+    public void resetPasswordEmail(@QueryParam("client_id") String clientId);
+
     @PUT
     @Path("execute-actions-email")
     public void executeActionsEmail(List<String> actions);
diff --git a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
index ca47f3e..730810b 100644
--- a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
+++ b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
@@ -47,6 +47,8 @@ public class MigrateTo1_6_0 {
 
         List<RealmModel> realms = session.realms().getRealms();
         for (RealmModel realm : realms) {
+            realm.setOfflineSessionIdleTimeout(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT);
+
             if (realm.getRole(Constants.OFFLINE_ACCESS_ROLE) == null) {
                 for (RoleModel realmRole : realm.getRoles()) {
                     realmRole.setScopeParamRequired(false);
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 b4f638d..293f38b 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
@@ -98,7 +98,8 @@ public interface ClientSessionModel {
         SOCIAL_CALLBACK,
         LOGGED_OUT,
         RESET_CREDENTIALS,
-        EXECUTE_ACTIONS
+        EXECUTE_ACTIONS,
+        REQUIRED_ACTIONS
     }
 
     public enum ExecutionStatus {
diff --git a/model/api/src/main/java/org/keycloak/models/Constants.java b/model/api/src/main/java/org/keycloak/models/Constants.java
index 5fe3189..43bdc7d 100755
--- a/model/api/src/main/java/org/keycloak/models/Constants.java
+++ b/model/api/src/main/java/org/keycloak/models/Constants.java
@@ -19,4 +19,7 @@ public interface Constants {
     String READ_TOKEN_ROLE = "read-token";
     String[] BROKER_SERVICE_ROLES = {READ_TOKEN_ROLE};
     String OFFLINE_ACCESS_ROLE = OAuth2Constants.OFFLINE_ACCESS;
+
+    // 30 days
+    int DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT = 2592000;
 }
diff --git a/model/api/src/main/java/org/keycloak/models/entities/PersistentClientSessionEntity.java b/model/api/src/main/java/org/keycloak/models/entities/PersistentClientSessionEntity.java
index a03edd3..1c1802e 100644
--- a/model/api/src/main/java/org/keycloak/models/entities/PersistentClientSessionEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/PersistentClientSessionEntity.java
@@ -7,6 +7,7 @@ public class PersistentClientSessionEntity {
 
     private String clientSessionId;
     private String clientId;
+    private int timestamp;
     private String data;
 
     public String getClientSessionId() {
@@ -25,6 +26,14 @@ public class PersistentClientSessionEntity {
         this.clientId = clientId;
     }
 
+    public int getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(int timestamp) {
+        this.timestamp = timestamp;
+    }
+
     public String getData() {
         return data;
     }
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 e5fe6d2..389ec0a 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
@@ -42,6 +42,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
     private boolean revokeRefreshToken;
     private int ssoSessionIdleTimeout;
     private int ssoSessionMaxLifespan;
+    private int offlineSessionIdleTimeout;
     private int accessTokenLifespan;
     private int accessCodeLifespan;
     private int accessCodeLifespanUserAction;
@@ -254,6 +255,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
         this.ssoSessionMaxLifespan = ssoSessionMaxLifespan;
     }
 
+    public int getOfflineSessionIdleTimeout() {
+        return offlineSessionIdleTimeout;
+    }
+
+    public void setOfflineSessionIdleTimeout(int offlineSessionIdleTimeout) {
+        this.offlineSessionIdleTimeout = offlineSessionIdleTimeout;
+    }
+
     public int getAccessTokenLifespan() {
         return accessTokenLifespan;
     }
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 7471a4c..8eb13ee 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -100,8 +100,8 @@ public interface RealmModel extends RoleContainerModel {
     int getSsoSessionMaxLifespan();
     void setSsoSessionMaxLifespan(int seconds);
 
-//    int getOfflineSessionIdleTimeout();
-//    void setOfflineSessionIdleTimeout(int seconds);
+    int getOfflineSessionIdleTimeout();
+    void setOfflineSessionIdleTimeout(int seconds);
 
     int getAccessTokenLifespan();
 
diff --git a/model/api/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java b/model/api/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java
index 6daf7c7..809fbd2 100644
--- a/model/api/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java
@@ -93,6 +93,11 @@ public class DisabledUserSessionPersisterProvider implements UserSessionPersiste
     }
 
     @Override
+    public void updateAllTimestamps(int time) {
+
+    }
+
+    @Override
     public List<UserSessionModel> loadUserSessions(int firstResult, int maxResults, boolean offline) {
         return Collections.emptyList();
     }
diff --git a/model/api/src/main/java/org/keycloak/models/session/PersistentClientSessionAdapter.java b/model/api/src/main/java/org/keycloak/models/session/PersistentClientSessionAdapter.java
index 1fced88..8465269 100644
--- a/model/api/src/main/java/org/keycloak/models/session/PersistentClientSessionAdapter.java
+++ b/model/api/src/main/java/org/keycloak/models/session/PersistentClientSessionAdapter.java
@@ -37,7 +37,6 @@ public class PersistentClientSessionAdapter implements ClientSessionModel {
         data.setProtocolMappers(clientSession.getProtocolMappers());
         data.setRedirectUri(clientSession.getRedirectUri());
         data.setRoles(clientSession.getRoles());
-        data.setTimestamp(clientSession.getTimestamp());
         data.setUserSessionNotes(clientSession.getUserSessionNotes());
 
         model = new PersistentClientSessionModel();
@@ -47,6 +46,7 @@ public class PersistentClientSessionAdapter implements ClientSessionModel {
             model.setUserId(clientSession.getAuthenticatedUser().getId());
         }
         model.setUserSessionId(clientSession.getUserSession().getId());
+        model.setTimestamp(clientSession.getTimestamp());
 
         realm = clientSession.getRealm();
         client = clientSession.getClient();
@@ -122,12 +122,12 @@ public class PersistentClientSessionAdapter implements ClientSessionModel {
 
     @Override
     public int getTimestamp() {
-        return getData().getTimestamp();
+        return model.getTimestamp();
     }
 
     @Override
     public void setTimestamp(int timestamp) {
-        getData().setTimestamp(timestamp);
+        model.setTimestamp(timestamp);
     }
 
     @Override
@@ -309,9 +309,6 @@ public class PersistentClientSessionAdapter implements ClientSessionModel {
         @JsonProperty("executionStatus")
         private Map<String, ClientSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
 
-        @JsonProperty("timestamp")
-        private int timestamp;
-
         @JsonProperty("action")
         private String action;
 
@@ -374,14 +371,6 @@ public class PersistentClientSessionAdapter implements ClientSessionModel {
             this.executionStatus = executionStatus;
         }
 
-        public int getTimestamp() {
-            return timestamp;
-        }
-
-        public void setTimestamp(int timestamp) {
-            this.timestamp = timestamp;
-        }
-
         public String getAction() {
             return action;
         }
diff --git a/model/api/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java b/model/api/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java
index 96e900f..b1a388b 100644
--- a/model/api/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java
@@ -9,6 +9,7 @@ public class PersistentClientSessionModel {
     private String userSessionId;
     private String clientId;
     private String userId;
+    private int timestamp;
     private String data;
 
     public String getClientSessionId() {
@@ -43,6 +44,14 @@ public class PersistentClientSessionModel {
         this.userId = userId;
     }
 
+    public int getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(int timestamp) {
+        this.timestamp = timestamp;
+    }
+
     public String getData() {
         return data;
     }
diff --git a/model/api/src/main/java/org/keycloak/models/session/UserSessionPersisterProvider.java b/model/api/src/main/java/org/keycloak/models/session/UserSessionPersisterProvider.java
index 4b3355e..5863fdb 100644
--- a/model/api/src/main/java/org/keycloak/models/session/UserSessionPersisterProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/session/UserSessionPersisterProvider.java
@@ -35,6 +35,9 @@ public interface UserSessionPersisterProvider extends Provider {
     // Called at startup to remove userSessions without any clientSession
     void clearDetachedUserSessions();
 
+    // Update "lastSessionRefresh" of all userSessions and "timestamp" of all clientSessions to specified time
+    void updateAllTimestamps(int time);
+
     // Called during startup. For each userSession, it loads also clientSessions
     List<UserSessionModel> loadUserSessions(int firstResult, int maxResults, boolean offline);
 
diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java b/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
index 836cc75..1a59f4f 100755
--- a/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
@@ -59,6 +59,10 @@ public interface UserSessionProvider extends Provider {
     int getOfflineSessionsCount(RealmModel realm, ClientModel client);
     List<UserSessionModel> getOfflineUserSessions(RealmModel realm, ClientModel client, int first, int max);
 
+    // Triggered by persister during pre-load
+    UserSessionModel importUserSession(UserSessionModel persistentUserSession, boolean offline);
+    ClientSessionModel importClientSession(ClientSessionModel persistentClientSession, boolean offline);
+
     void close();
 
 }
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 6b4a6be..084d012 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
@@ -148,6 +148,7 @@ public class ModelToRepresentation {
         rep.setAccessTokenLifespan(realm.getAccessTokenLifespan());
         rep.setSsoSessionIdleTimeout(realm.getSsoSessionIdleTimeout());
         rep.setSsoSessionMaxLifespan(realm.getSsoSessionMaxLifespan());
+        rep.setOfflineSessionIdleTimeout(realm.getOfflineSessionIdleTimeout());
         rep.setAccessCodeLifespan(realm.getAccessCodeLifespan());
         rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
         rep.setAccessCodeLifespanLogin(realm.getAccessCodeLifespanLogin());
@@ -211,7 +212,10 @@ public class ModelToRepresentation {
         }
 
         rep.setInternationalizationEnabled(realm.isInternationalizationEnabled());
-        rep.getSupportedLocales().addAll(realm.getSupportedLocales());
+        if(realm.getSupportedLocales() != null){
+            rep.setSupportedLocales(new HashSet<String>());
+            rep.getSupportedLocales().addAll(realm.getSupportedLocales());
+        }
         rep.setDefaultLocale(realm.getDefaultLocale());
         if (internal) {
             exportAuthenticationFlows(realm, rep);
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 6c61e66..0e1e40e 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
@@ -1,5 +1,6 @@
 package org.keycloak.models.utils;
 
+import org.keycloak.models.Constants;
 import org.keycloak.util.Base64;
 import org.jboss.logging.Logger;
 import org.keycloak.enums.SslRequired;
@@ -106,6 +107,8 @@ public class RepresentationToModel {
         else newRealm.setSsoSessionIdleTimeout(1800);
         if (rep.getSsoSessionMaxLifespan() != null) newRealm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan());
         else newRealm.setSsoSessionMaxLifespan(36000);
+        if (rep.getOfflineSessionIdleTimeout() != null) newRealm.setOfflineSessionIdleTimeout(rep.getOfflineSessionIdleTimeout());
+        else newRealm.setOfflineSessionIdleTimeout(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT);
 
         if (rep.getAccessCodeLifespan() != null) newRealm.setAccessCodeLifespan(rep.getAccessCodeLifespan());
         else newRealm.setAccessCodeLifespan(60);
@@ -535,6 +538,7 @@ public class RepresentationToModel {
         if (rep.getAccessTokenLifespan() != null) realm.setAccessTokenLifespan(rep.getAccessTokenLifespan());
         if (rep.getSsoSessionIdleTimeout() != null) realm.setSsoSessionIdleTimeout(rep.getSsoSessionIdleTimeout());
         if (rep.getSsoSessionMaxLifespan() != null) realm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan());
+        if (rep.getOfflineSessionIdleTimeout() != null) realm.setOfflineSessionIdleTimeout(rep.getOfflineSessionIdleTimeout());
         if (rep.getRequiredCredentials() != null) {
             realm.updateRequiredCredentials(rep.getRequiredCredentials());
         }
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
index 26227b1..381c172 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
@@ -356,6 +356,16 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public int getOfflineSessionIdleTimeout() {
+        return realm.getOfflineSessionIdleTimeout();
+    }
+
+    @Override
+    public void setOfflineSessionIdleTimeout(int seconds) {
+        realm.setOfflineSessionIdleTimeout(seconds);
+    }
+
+    @Override
     public int getAccessTokenLifespan() {
         return realm.getAccessTokenLifespan();
     }
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
index 51d445c..e9b92a6 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
@@ -276,6 +276,19 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public int getOfflineSessionIdleTimeout() {
+        if (updated != null) return updated.getOfflineSessionIdleTimeout();
+        return cached.getOfflineSessionIdleTimeout();
+    }
+
+
+    @Override
+    public void setOfflineSessionIdleTimeout(int seconds) {
+        getDelegateForUpdate();
+        updated.setOfflineSessionIdleTimeout(seconds);
+    }
+
+    @Override
     public int getAccessTokenLifespan() {
         if (updated != null) return updated.getAccessTokenLifespan();
         return cached.getAccessTokenLifespan();
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 5193588..3aa7d38 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
@@ -58,6 +58,7 @@ public class CachedRealm implements Serializable {
     private boolean revokeRefreshToken;
     private int ssoSessionIdleTimeout;
     private int ssoSessionMaxLifespan;
+    private int offlineSessionIdleTimeout;
     private int accessTokenLifespan;
     private int accessCodeLifespan;
     private int accessCodeLifespanUserAction;
@@ -140,6 +141,7 @@ public class CachedRealm implements Serializable {
         revokeRefreshToken = model.isRevokeRefreshToken();
         ssoSessionIdleTimeout = model.getSsoSessionIdleTimeout();
         ssoSessionMaxLifespan = model.getSsoSessionMaxLifespan();
+        offlineSessionIdleTimeout = model.getOfflineSessionIdleTimeout();
         accessTokenLifespan = model.getAccessTokenLifespan();
         accessCodeLifespan = model.getAccessCodeLifespan();
         accessCodeLifespanUserAction = model.getAccessCodeLifespanUserAction();
@@ -327,6 +329,10 @@ public class CachedRealm implements Serializable {
         return ssoSessionMaxLifespan;
     }
 
+    public int getOfflineSessionIdleTimeout() {
+        return offlineSessionIdleTimeout;
+    }
+
     public int getAccessTokenLifespan() {
         return accessTokenLifespan;
     }
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 27c4824..bf5b339 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 {
     private int ssoSessionIdleTimeout;
     @Column(name="SSO_MAX_LIFESPAN")
     private int ssoSessionMaxLifespan;
+    @Column(name="OFFLINE_SESSION_IDLE_TIMEOUT")
+    private int offlineSessionIdleTimeout;
     @Column(name="ACCESS_TOKEN_LIFESPAN")
     protected int accessTokenLifespan;
     @Column(name="ACCESS_CODE_LIFESPAN")
@@ -314,6 +316,14 @@ public class RealmEntity {
         this.ssoSessionMaxLifespan = ssoSessionMaxLifespan;
     }
 
+    public int getOfflineSessionIdleTimeout() {
+        return offlineSessionIdleTimeout;
+    }
+
+    public void setOfflineSessionIdleTimeout(int offlineSessionIdleTimeout) {
+        this.offlineSessionIdleTimeout = offlineSessionIdleTimeout;
+    }
+
     public int getAccessTokenLifespan() {
         return accessTokenLifespan;
     }
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 2c8e2ad..9290013 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
@@ -378,6 +378,16 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public int getOfflineSessionIdleTimeout() {
+        return realm.getOfflineSessionIdleTimeout();
+    }
+
+    @Override
+    public void setOfflineSessionIdleTimeout(int seconds) {
+        realm.setOfflineSessionIdleTimeout(seconds);
+    }
+
+    @Override
     public int getAccessCodeLifespan() {
         return realm.getAccessCodeLifespan();
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java
index ffc0455..bf19d96 100644
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java
@@ -58,6 +58,7 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
         PersistentClientSessionEntity entity = new PersistentClientSessionEntity();
         entity.setClientSessionId(clientSession.getId());
         entity.setClientId(clientSession.getClient().getId());
+        entity.setTimestamp(clientSession.getTimestamp());
         entity.setOffline(offline);
         entity.setUserSessionId(clientSession.getUserSession().getId());
         entity.setData(model.getData());
@@ -128,26 +129,32 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
 
     @Override
     public void onRealmRemoved(RealmModel realm) {
-        em.createNamedQuery("deleteClientSessionsByRealm").setParameter("realmId", realm.getId()).executeUpdate();
-        em.createNamedQuery("deleteUserSessionsByRealm").setParameter("realmId", realm.getId()).executeUpdate();
+        int num = em.createNamedQuery("deleteClientSessionsByRealm").setParameter("realmId", realm.getId()).executeUpdate();
+        num = em.createNamedQuery("deleteUserSessionsByRealm").setParameter("realmId", realm.getId()).executeUpdate();
     }
 
     @Override
     public void onClientRemoved(RealmModel realm, ClientModel client) {
-        em.createNamedQuery("deleteClientSessionsByClient").setParameter("clientId", client.getId()).executeUpdate();
-        em.createNamedQuery("deleteDetachedUserSessions").executeUpdate();
+        int num = em.createNamedQuery("deleteClientSessionsByClient").setParameter("clientId", client.getId()).executeUpdate();
+        num = em.createNamedQuery("deleteDetachedUserSessions").executeUpdate();
     }
 
     @Override
     public void onUserRemoved(RealmModel realm, UserModel user) {
-        em.createNamedQuery("deleteClientSessionsByUser").setParameter("userId", user.getId()).executeUpdate();
-        em.createNamedQuery("deleteUserSessionsByUser").setParameter("userId", user.getId()).executeUpdate();
+        int num = em.createNamedQuery("deleteClientSessionsByUser").setParameter("userId", user.getId()).executeUpdate();
+        num = em.createNamedQuery("deleteUserSessionsByUser").setParameter("userId", user.getId()).executeUpdate();
     }
 
     @Override
     public void clearDetachedUserSessions() {
-        em.createNamedQuery("deleteDetachedClientSessions").executeUpdate();
-        em.createNamedQuery("deleteDetachedUserSessions").executeUpdate();
+        int num = em.createNamedQuery("deleteDetachedClientSessions").executeUpdate();
+        num = em.createNamedQuery("deleteDetachedUserSessions").executeUpdate();
+    }
+
+    @Override
+    public void updateAllTimestamps(int time) {
+        int num = em.createNamedQuery("updateClientSessionsTimestamps").setParameter("timestamp", time).executeUpdate();
+        num = em.createNamedQuery("updateUserSessionsTimestamps").setParameter("lastSessionRefresh", time).executeUpdate();
     }
 
     @Override
@@ -220,6 +227,7 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
         model.setClientId(entity.getClientId());
         model.setUserSessionId(userSession.getId());
         model.setUserId(userSession.getUser().getId());
+        model.setTimestamp(entity.getTimestamp());
         model.setData(entity.getData());
         return new PersistentClientSessionAdapter(model, realm, client, userSession);
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java
index a11b875..faf3f80 100644
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java
@@ -17,13 +17,14 @@ import javax.persistence.Table;
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
 @NamedQueries({
-        @NamedQuery(name="deleteClientSessionsByRealm", query="delete from PersistentClientSessionEntity sess where sess.userSessionId in (select u from PersistentUserSessionEntity u where u.realmId=:realmId)"),
+        @NamedQuery(name="deleteClientSessionsByRealm", query="delete from PersistentClientSessionEntity sess where sess.userSessionId IN (select u.userSessionId from PersistentUserSessionEntity u where u.realmId=:realmId)"),
         @NamedQuery(name="deleteClientSessionsByClient", query="delete from PersistentClientSessionEntity sess where sess.clientId=:clientId"),
-        @NamedQuery(name="deleteClientSessionsByUser", query="delete from PersistentClientSessionEntity sess where sess.userSessionId in (select u from PersistentUserSessionEntity u where u.userId=:userId)"),
+        @NamedQuery(name="deleteClientSessionsByUser", query="delete from PersistentClientSessionEntity sess where sess.userSessionId IN (select u.userSessionId from PersistentUserSessionEntity u where u.userId=:userId)"),
         @NamedQuery(name="deleteClientSessionsByUserSession", query="delete from PersistentClientSessionEntity sess where sess.userSessionId=:userSessionId and offline=:offline"),
         @NamedQuery(name="deleteDetachedClientSessions", query="delete from PersistentClientSessionEntity sess where sess.userSessionId NOT IN (select u.userSessionId from PersistentUserSessionEntity u)"),
         @NamedQuery(name="findClientSessionsByUserSession", query="select sess from PersistentClientSessionEntity sess where sess.userSessionId=:userSessionId and offline=:offline"),
         @NamedQuery(name="findClientSessionsByUserSessions", query="select sess from PersistentClientSessionEntity sess where offline=:offline and sess.userSessionId IN (:userSessionIds) order by sess.userSessionId"),
+        @NamedQuery(name="updateClientSessionsTimestamps", query="update PersistentClientSessionEntity c set timestamp=:timestamp"),
 })
 @Table(name="OFFLINE_CLIENT_SESSION")
 @Entity
@@ -40,6 +41,9 @@ public class PersistentClientSessionEntity {
     @Column(name="CLIENT_ID", length = 36)
     protected String clientId;
 
+    @Column(name="TIMESTAMP")
+    protected int timestamp;
+
     @Id
     @Column(name = "OFFLINE")
     protected boolean offline;
@@ -71,6 +75,14 @@ public class PersistentClientSessionEntity {
         this.clientId = clientId;
     }
 
+    public int getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(int timestamp) {
+        this.timestamp = timestamp;
+    }
+
     public boolean isOffline() {
         return offline;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentUserSessionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentUserSessionEntity.java
index f739091..95745a8 100644
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentUserSessionEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentUserSessionEntity.java
@@ -26,7 +26,8 @@ import org.keycloak.models.jpa.entities.UserEntity;
         @NamedQuery(name="deleteUserSessionsByUser", query="delete from PersistentUserSessionEntity sess where sess.userId=:userId"),
         @NamedQuery(name="deleteDetachedUserSessions", query="delete from PersistentUserSessionEntity sess where sess.userSessionId NOT IN (select c.userSessionId from PersistentClientSessionEntity c)"),
         @NamedQuery(name="findUserSessionsCount", query="select count(sess) from PersistentUserSessionEntity sess where offline=:offline"),
-        @NamedQuery(name="findUserSessions", query="select sess from PersistentUserSessionEntity sess where offline=:offline order by sess.userSessionId")
+        @NamedQuery(name="findUserSessions", query="select sess from PersistentUserSessionEntity sess where offline=:offline order by sess.userSessionId"),
+        @NamedQuery(name="updateUserSessionsTimestamps", query="update PersistentUserSessionEntity c set lastSessionRefresh=:lastSessionRefresh"),
 
 })
 @Table(name="OFFLINE_USER_SESSION")
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserSessionPersisterProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserSessionPersisterProvider.java
index 53917c2..f23e7fb 100644
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserSessionPersisterProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserSessionPersisterProvider.java
@@ -45,10 +45,6 @@ public class MongoUserSessionPersisterProvider implements UserSessionPersisterPr
         return invocationContext.getMongoStore();
     }
 
-    private Class<? extends PersistentUserSessionEntity> getClazz(boolean offline) {
-        return offline ? MongoOfflineUserSessionEntity.class : MongoOnlineUserSessionEntity.class;
-    }
-
     private MongoUserSessionEntity loadUserSession(String userSessionId, boolean offline) {
         Class<? extends MongoUserSessionEntity> clazz = offline ? MongoOfflineUserSessionEntity.class : MongoOnlineUserSessionEntity.class;
         return getMongoStore().loadEntity(clazz, userSessionId, invocationContext);
@@ -221,6 +217,41 @@ public class MongoUserSessionPersisterProvider implements UserSessionPersisterPr
     }
 
     @Override
+    public void updateAllTimestamps(int time) {
+        // 1) Update timestamp of clientSessions
+
+        DBObject timestampSubquery = new QueryBuilder()
+                .and("timestamp").notEquals(time).get();
+
+        DBObject query = new QueryBuilder()
+                .and("clientSessions").elemMatch(timestampSubquery).get();
+
+
+        DBObject update = new QueryBuilder()
+                .and("$set").is(new BasicDBObject("clientSessions.$.timestamp", time)).get();
+
+        // Not sure how to do in single query :/
+        int countModified = 1;
+        while (countModified > 0) {
+            countModified = getMongoStore().updateEntities(MongoOfflineUserSessionEntity.class, query, update, invocationContext);
+        }
+
+        countModified = 1;
+        while (countModified > 0) {
+            countModified = getMongoStore().updateEntities(MongoOnlineUserSessionEntity.class, query, update, invocationContext);
+        }
+
+        // 2) update lastSessionRefresh of userSessions
+        query = new QueryBuilder().get();
+
+        update = new QueryBuilder()
+                .and("$set").is(new BasicDBObject("lastSessionRefresh", time)).get();
+
+        getMongoStore().updateEntities(MongoOfflineUserSessionEntity.class, query, update, invocationContext);
+        getMongoStore().updateEntities(MongoOnlineUserSessionEntity.class, query, update, invocationContext);
+    }
+
+    @Override
     public List<UserSessionModel> loadUserSessions(int firstResult, int maxResults, boolean offline) {
         DBObject query = new QueryBuilder()
                 .get();
@@ -232,13 +263,13 @@ public class MongoUserSessionPersisterProvider implements UserSessionPersisterPr
 
         List<UserSessionModel> results = new LinkedList<>();
         for (MongoUserSessionEntity entity : entities) {
-            PersistentUserSessionAdapter userSession = toAdapter(entity, offline);
+            PersistentUserSessionAdapter userSession = toAdapter(entity);
             results.add(userSession);
         }
         return results;
     }
 
-    private PersistentUserSessionAdapter toAdapter(PersistentUserSessionEntity entity, boolean offline) {
+    private PersistentUserSessionAdapter toAdapter(PersistentUserSessionEntity entity) {
         RealmModel realm = session.realms().getRealm(entity.getRealmId());
         UserModel user = session.users().getUserById(entity.getUserId(), realm);
 
@@ -250,14 +281,14 @@ public class MongoUserSessionPersisterProvider implements UserSessionPersisterPr
         List<ClientSessionModel> clientSessions = new LinkedList<>();
         PersistentUserSessionAdapter userSessionAdapter = new PersistentUserSessionAdapter(model, realm, user, clientSessions);
         for (PersistentClientSessionEntity clientSessEntity : entity.getClientSessions()) {
-            PersistentClientSessionAdapter clientSessAdapter = toAdapter(realm, userSessionAdapter, offline, clientSessEntity);
+            PersistentClientSessionAdapter clientSessAdapter = toAdapter(realm, userSessionAdapter, clientSessEntity);
             clientSessions.add(clientSessAdapter);
         }
 
         return userSessionAdapter;
     }
 
-    private PersistentClientSessionAdapter toAdapter(RealmModel realm, PersistentUserSessionAdapter userSession, boolean offline, PersistentClientSessionEntity entity) {
+    private PersistentClientSessionAdapter toAdapter(RealmModel realm, PersistentUserSessionAdapter userSession, PersistentClientSessionEntity entity) {
         ClientModel client = realm.getClientById(entity.getClientId());
 
         PersistentClientSessionModel model = new PersistentClientSessionModel();
@@ -265,6 +296,7 @@ public class MongoUserSessionPersisterProvider implements UserSessionPersisterPr
         model.setClientId(entity.getClientId());
         model.setUserSessionId(userSession.getId());
         model.setUserId(userSession.getUser().getId());
+        model.setTimestamp(entity.getTimestamp());
         model.setData(entity.getData());
         return new PersistentClientSessionAdapter(model, realm, client, userSession);
     }
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 05ae8bc..b03c463 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
@@ -345,6 +345,17 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     }
 
     @Override
+    public int getOfflineSessionIdleTimeout() {
+        return realm.getOfflineSessionIdleTimeout();
+    }
+
+    @Override
+    public void setOfflineSessionIdleTimeout(int seconds) {
+        realm.setOfflineSessionIdleTimeout(seconds);
+        updateRealm();
+    }
+
+    @Override
     public int getAccessTokenLifespan() {
         return realm.getAccessTokenLifespan();
     }
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java
index 0e5f2b9..23c1286 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java
@@ -10,6 +10,7 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.UserSessionProvider;
 import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.session.UserSessionPersisterProvider;
 import org.keycloak.models.sessions.infinispan.compat.entities.ClientSessionEntity;
 import org.keycloak.models.sessions.infinispan.compat.entities.UserSessionEntity;
 import org.keycloak.models.sessions.infinispan.compat.entities.UsernameLoginFailureEntity;
@@ -297,6 +298,8 @@ public class MemUserSessionProvider implements UserSessionProvider {
 
     @Override
     public void removeExpiredUserSessions(RealmModel realm) {
+        UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
+
         Iterator<UserSessionEntity> itr = userSessions.values().iterator();
         while (itr.hasNext()) {
             UserSessionEntity s = itr.next();
@@ -314,6 +317,31 @@ public class MemUserSessionProvider implements UserSessionProvider {
                 citr.remove();
             }
         }
+
+        // Remove expired offline user sessions
+        itr = offlineUserSessions.values().iterator();
+        while (itr.hasNext()) {
+            UserSessionEntity s = itr.next();
+            if (s.getRealm().equals(realm.getId()) && (s.getLastSessionRefresh() < Time.currentTime() - realm.getOfflineSessionIdleTimeout())) {
+                itr.remove();
+                remove(s, true);
+
+                // propagate to persister
+                persister.removeUserSession(s.getId(), true);
+            }
+        }
+
+        // Remove expired offline client sessions
+        citr = offlineClientSessions.values().iterator();
+        while (citr.hasNext()) {
+            ClientSessionEntity s = citr.next();
+            if (s.getRealmId().equals(realm.getId()) && (s.getTimestamp() < Time.currentTime() - realm.getOfflineSessionIdleTimeout())) {
+                citr.remove();
+
+                // propagate to persister
+                persister.removeClientSession(s.getId(), true);
+            }
+        }
     }
 
     @Override
@@ -407,6 +435,18 @@ public class MemUserSessionProvider implements UserSessionProvider {
 
     @Override
     public UserSessionModel createOfflineUserSession(UserSessionModel userSession) {
+        UserSessionAdapter importedUserSession = importUserSession(userSession, true);
+
+        // started and lastSessionRefresh set to current time
+        int currentTime = Time.currentTime();
+        importedUserSession.getEntity().setStarted(currentTime);
+        importedUserSession.setLastSessionRefresh(currentTime);
+
+        return importedUserSession;
+    }
+
+    @Override
+    public UserSessionAdapter importUserSession(UserSessionModel userSession, boolean offline) {
         UserSessionEntity entity = new UserSessionEntity();
         entity.setId(userSession.getId());
         entity.setRealm(userSession.getRealm().getId());
@@ -415,17 +455,19 @@ public class MemUserSessionProvider implements UserSessionProvider {
         entity.setBrokerSessionId(userSession.getBrokerSessionId());
         entity.setBrokerUserId(userSession.getBrokerUserId());
         entity.setIpAddress(userSession.getIpAddress());
-        entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
         entity.setLoginUsername(userSession.getLoginUsername());
         if (userSession.getNotes() != null) {
             entity.getNotes().putAll(userSession.getNotes());
         }
         entity.setRememberMe(userSession.isRememberMe());
-        entity.setStarted(userSession.getStarted());
         entity.setState(userSession.getState());
         entity.setUser(userSession.getUser().getId());
 
-        offlineUserSessions.put(userSession.getId(), entity);
+        entity.setStarted(userSession.getStarted());
+        entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
+
+        ConcurrentHashMap<String, UserSessionEntity> sessionsMap = offline ? offlineUserSessions : userSessions;
+        sessionsMap.put(userSession.getId(), entity);
         return new UserSessionAdapter(session, this, userSession.getRealm(), entity);
     }
 
@@ -450,6 +492,17 @@ public class MemUserSessionProvider implements UserSessionProvider {
 
     @Override
     public ClientSessionModel createOfflineClientSession(ClientSessionModel clientSession) {
+        ClientSessionAdapter offlineClientSession = importClientSession(clientSession, true);
+
+        // update timestamp to current time
+        offlineClientSession.setTimestamp(Time.currentTime());
+
+        return offlineClientSession;
+    }
+
+    @Override
+    public ClientSessionAdapter importClientSession(ClientSessionModel clientSession, boolean offline) {
+
         ClientSessionEntity entity = new ClientSessionEntity();
         entity.setId(clientSession.getId());
         entity.setRealmId(clientSession.getRealm().getId());
@@ -473,7 +526,8 @@ public class MemUserSessionProvider implements UserSessionProvider {
             entity.getUserSessionNotes().putAll(clientSession.getUserSessionNotes());
         }
 
-        offlineClientSessions.put(clientSession.getId(), entity);
+        ConcurrentHashMap<String, ClientSessionEntity> clientSessionsMap = offline ? offlineClientSessions : clientSessions;
+        clientSessionsMap.put(clientSession.getId(), entity);
         return new ClientSessionAdapter(session, this, clientSession.getRealm(), entity);
     }
 
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/SimpleUserSessionInitializer.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/SimpleUserSessionInitializer.java
index 450bbe1..cb5a7e7 100644
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/SimpleUserSessionInitializer.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/SimpleUserSessionInitializer.java
@@ -22,11 +22,23 @@ public class SimpleUserSessionInitializer {
     }
 
     public void loadPersistentSessions() {
+        // Rather use separate transactions for update and loading
+
+        KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+            @Override
+            public void run(KeycloakSession session) {
+                sessionLoader.init(session);
+            }
+
+        });
+
         KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
 
             @Override
             public void run(KeycloakSession session) {
                 int count = sessionLoader.getSessionsCount(session);
+
                 for (int i=0 ; i<=count ; i+=sessionsPerSegment) {
                     sessionLoader.loadSessions(session, i, sessionsPerSegment);
                 }
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 dbfecb4..627edcf 100755
--- 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
@@ -13,6 +13,7 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.UserSessionProvider;
 import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.session.UserSessionPersisterProvider;
 import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
 import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
 import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
@@ -302,8 +303,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
 
     @Override
     public void removeExpiredUserSessions(RealmModel realm) {
+        UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
+
         int expired = Time.currentTime() - realm.getSsoSessionMaxLifespan();
         int expiredRefresh = Time.currentTime() - realm.getSsoSessionIdleTimeout();
+        int expiredOffline = Time.currentTime() - realm.getOfflineSessionIdleTimeout();
         int expiredDettachedClientSession = Time.currentTime() - RealmInfoUtil.getDettachedClientSessionLifespan(realm);
 
         Map<String, String> map = new MapReduceTask(sessionCache)
@@ -323,6 +327,36 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         for (String id : map.keySet()) {
             tx.remove(sessionCache, id);
         }
+
+        // Remove expired offline user sessions
+        Map<String, SessionEntity> map2 = new MapReduceTask(offlineSessionCache)
+                .mappedWith(UserSessionMapper.create(realm.getId()).expired(null, expiredOffline))
+                .reducedWith(new FirstResultReducer())
+                .execute();
+
+        for (Map.Entry<String, SessionEntity> entry : map2.entrySet()) {
+            String userSessionId = entry.getKey();
+            tx.remove(offlineSessionCache, userSessionId);
+            // Propagate to persister
+            persister.removeUserSession(userSessionId, true);
+
+            UserSessionEntity entity = (UserSessionEntity) entry.getValue();
+            for (String clientSessionId : entity.getClientSessions()) {
+                tx.remove(offlineSessionCache, clientSessionId);
+            }
+        }
+
+        // Remove expired offline client sessions
+        map = new MapReduceTask(offlineSessionCache)
+                .mappedWith(ClientSessionMapper.create(realm.getId()).expiredRefresh(expiredOffline).emitKey())
+                .reducedWith(new FirstResultReducer())
+                .execute();
+
+        for (String clientSessionId : map.keySet()) {
+            tx.remove(offlineSessionCache, clientSessionId);
+            persister.removeClientSession(clientSessionId, true);
+        }
+
     }
 
     @Override
@@ -477,6 +511,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
 
         tx.remove(cache, userSessionId);
 
+        // TODO: Isn't more effective to retrieve from userSessionEntity directly?
         Map<String, String> map = new MapReduceTask(cache)
                 .mappedWith(ClientSessionMapper.create(realm.getId()).userSession(userSessionId).emitKey())
                 .reducedWith(new FirstResultReducer())
@@ -526,24 +561,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
 
     @Override
     public UserSessionModel createOfflineUserSession(UserSessionModel userSession) {
-        UserSessionEntity entity = new UserSessionEntity();
-        entity.setId(userSession.getId());
-        entity.setRealm(userSession.getRealm().getId());
+        UserSessionAdapter offlineUserSession = importUserSession(userSession, true);
 
-        entity.setAuthMethod(userSession.getAuthMethod());
-        entity.setBrokerSessionId(userSession.getBrokerSessionId());
-        entity.setBrokerUserId(userSession.getBrokerUserId());
-        entity.setIpAddress(userSession.getIpAddress());
-        entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
-        entity.setLoginUsername(userSession.getLoginUsername());
-        entity.setNotes(userSession.getNotes());
-        entity.setRememberMe(userSession.isRememberMe());
-        entity.setStarted(userSession.getStarted());
-        entity.setState(userSession.getState());
-        entity.setUser(userSession.getUser().getId());
+        // started and lastSessionRefresh set to current time
+        int currentTime = Time.currentTime();
+        offlineUserSession.getEntity().setStarted(currentTime);
+        offlineUserSession.setLastSessionRefresh(currentTime);
 
-        tx.put(offlineSessionCache, userSession.getId(), entity);
-        return wrap(userSession.getRealm(), entity, true);
+        return offlineUserSession;
     }
 
     @Override
@@ -558,26 +583,12 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
 
     @Override
     public ClientSessionModel createOfflineClientSession(ClientSessionModel clientSession) {
-        ClientSessionEntity entity = new ClientSessionEntity();
-        entity.setId(clientSession.getId());
-        entity.setRealm(clientSession.getRealm().getId());
+        ClientSessionAdapter offlineClientSession = importClientSession(clientSession, true);
 
-        entity.setAction(clientSession.getAction());
-        entity.setAuthenticatorStatus(clientSession.getExecutionStatus());
-        entity.setAuthMethod(clientSession.getAuthMethod());
-        if (clientSession.getAuthenticatedUser() != null) {
-            entity.setAuthUserId(clientSession.getAuthenticatedUser().getId());
-        }
-        entity.setClient(clientSession.getClient().getId());
-        entity.setNotes(clientSession.getNotes());
-        entity.setProtocolMappers(clientSession.getProtocolMappers());
-        entity.setRedirectUri(clientSession.getRedirectUri());
-        entity.setRoles(clientSession.getRoles());
-        entity.setTimestamp(clientSession.getTimestamp());
-        entity.setUserSessionNotes(clientSession.getUserSessionNotes());
+        // update timestamp to current time
+        offlineClientSession.setTimestamp(Time.currentTime());
 
-        tx.put(offlineSessionCache, clientSession.getId(), entity);
-        return wrap(clientSession.getRealm(), entity, true);
+        return offlineClientSession;
     }
 
     @Override
@@ -622,6 +633,55 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         return getUserSessions(realm, client, first, max, true);
     }
 
+    @Override
+    public UserSessionAdapter importUserSession(UserSessionModel userSession, boolean offline) {
+        UserSessionEntity entity = new UserSessionEntity();
+        entity.setId(userSession.getId());
+        entity.setRealm(userSession.getRealm().getId());
+
+        entity.setAuthMethod(userSession.getAuthMethod());
+        entity.setBrokerSessionId(userSession.getBrokerSessionId());
+        entity.setBrokerUserId(userSession.getBrokerUserId());
+        entity.setIpAddress(userSession.getIpAddress());
+        entity.setLoginUsername(userSession.getLoginUsername());
+        entity.setNotes(userSession.getNotes());
+        entity.setRememberMe(userSession.isRememberMe());
+        entity.setState(userSession.getState());
+        entity.setUser(userSession.getUser().getId());
+
+        entity.setStarted(userSession.getStarted());
+        entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
+
+        Cache<String, SessionEntity> cache = getCache(offline);
+        tx.put(cache, userSession.getId(), entity);
+        return wrap(userSession.getRealm(), entity, offline);
+    }
+
+    @Override
+    public ClientSessionAdapter importClientSession(ClientSessionModel clientSession, boolean offline) {
+        ClientSessionEntity entity = new ClientSessionEntity();
+        entity.setId(clientSession.getId());
+        entity.setRealm(clientSession.getRealm().getId());
+
+        entity.setAction(clientSession.getAction());
+        entity.setAuthenticatorStatus(clientSession.getExecutionStatus());
+        entity.setAuthMethod(clientSession.getAuthMethod());
+        if (clientSession.getAuthenticatedUser() != null) {
+            entity.setAuthUserId(clientSession.getAuthenticatedUser().getId());
+        }
+        entity.setClient(clientSession.getClient().getId());
+        entity.setNotes(clientSession.getNotes());
+        entity.setProtocolMappers(clientSession.getProtocolMappers());
+        entity.setRedirectUri(clientSession.getRedirectUri());
+        entity.setRoles(clientSession.getRoles());
+        entity.setTimestamp(clientSession.getTimestamp());
+        entity.setUserSessionNotes(clientSession.getUserSessionNotes());
+
+        Cache<String, SessionEntity> cache = getCache(offline);
+        tx.put(cache, clientSession.getId(), entity);
+        return wrap(clientSession.getRealm(), entity, offline);
+    }
+
     class InfinispanKeycloakTransaction implements KeycloakTransaction {
 
         private boolean active;
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 c88e490..1d7c279 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
@@ -63,20 +63,12 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
                 if (compatMode) {
                     compatProviderFactory = new MemUserSessionProviderFactory();
                 }
-
-                log.debug("Clearing detached sessions from persistent storage");
-                UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
-                if (persister == null) {
-                    throw new RuntimeException("userSessionPersister not configured. Please see the migration docs and upgrade your configuration");
-                } else {
-                    persister.clearDetachedUserSessions();
-                }
             }
 
         });
 
         // Max count of worker errors. Initialization will end with exception when this number is reached
-        int maxErrors = config.getInt("maxErrors", 50);
+        int maxErrors = config.getInt("maxErrors", 20);
 
         // Count of sessions to be computed in each segment
         int sessionsPerSegment = config.getInt("sessionsPerSegment", 100);
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InfinispanUserSessionInitializer.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InfinispanUserSessionInitializer.java
index 0d038bd..89f2d4f 100644
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InfinispanUserSessionInitializer.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InfinispanUserSessionInitializer.java
@@ -82,8 +82,8 @@ public class InfinispanUserSessionInitializer {
 
 
     private boolean isFinished() {
-        InitializerState stateEntity = (InitializerState) cache.get(stateKey);
-        return stateEntity != null && stateEntity.isFinished();
+        InitializerState state = (InitializerState) cache.get(stateKey);
+        return state != null && state.isFinished();
     }
 
 
@@ -92,6 +92,16 @@ public class InfinispanUserSessionInitializer {
         if (state == null) {
             final int[] count = new int[1];
 
+            // Rather use separate transactions for update and counting
+
+            KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+                @Override
+                public void run(KeycloakSession session) {
+                    sessionLoader.init(session);
+                }
+
+            });
+
             KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
                 @Override
                 public void run(KeycloakSession session) {
@@ -133,7 +143,7 @@ public class InfinispanUserSessionInitializer {
     }
 
 
-    // Just coordinator is supposed to run this
+    // Just coordinator will run this
     private void startLoading() {
         InitializerState state = getOrCreateInitializerState();
 
@@ -196,7 +206,7 @@ public class InfinispanUserSessionInitializer {
                 saveStateToCache(state);
 
                 // TODO
-                log.info("New initializer state pushed. The state is: " + state.printState(false));
+                log.info("New initializer state pushed. The state is: " + state.printState());
             }
         } finally {
             distributedExecutorService.shutdown();
@@ -225,7 +235,7 @@ public class InfinispanUserSessionInitializer {
         @ViewChanged
         public void viewChanged(ViewChangedEvent event) {
             boolean isCoordinator = isCoordinator();
-            // TODO:
+            // TODO: debug
             log.info("View Changed: is coordinator: " + isCoordinator);
 
             if (isCoordinator) {
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InitializerState.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InitializerState.java
index eda7370..6066077 100644
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InitializerState.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InitializerState.java
@@ -15,6 +15,7 @@ public class InitializerState extends SessionEntity {
 
     private int sessionsCount;
     private List<Boolean> segments = new ArrayList<>();
+    private int lowestUnfinishedSegment = 0;
 
 
     public void init(int sessionsCount, int sessionsPerSegment) {
@@ -25,24 +26,27 @@ public class InitializerState extends SessionEntity {
             segmentsCount = segmentsCount + 1;
         }
 
-        // TODO: trace
-        log.info(String.format("sessionsCount: %d, sessionsPerSegment: %d, segmentsCount: %d", sessionsCount, sessionsPerSegment, segmentsCount));
+        // TODO: debug
+        log.infof("sessionsCount: %d, sessionsPerSegment: %d, segmentsCount: %d", sessionsCount, sessionsPerSegment, segmentsCount);
 
         for (int i=0 ; i<segmentsCount ; i++) {
             segments.add(false);
         }
+
+        updateLowestUnfinishedSegment();
     }
 
     // Return true just if computation is entirely finished (all segments are true)
     public boolean isFinished() {
-        return getNextUnfinishedSegmentFromIndex(0) == -1;
+        return lowestUnfinishedSegment == -1;
     }
 
     // Return next un-finished segments. It can return "segmentCount" segments or less
     public List<Integer> getUnfinishedSegments(int segmentCount) {
         List<Integer> result = new ArrayList<>();
-        boolean remaining = true;
-        int next=0;
+        int next = lowestUnfinishedSegment;
+        boolean remaining = lowestUnfinishedSegment != -1;
+
         while (remaining && result.size() < segmentCount) {
             next = getNextUnfinishedSegmentFromIndex(next);
             if (next == -1) {
@@ -58,6 +62,11 @@ public class InitializerState extends SessionEntity {
 
     public void markSegmentFinished(int index) {
         segments.set(index, true);
+        updateLowestUnfinishedSegment();
+    }
+
+    private void updateLowestUnfinishedSegment() {
+        this.lowestUnfinishedSegment = getNextUnfinishedSegmentFromIndex(lowestUnfinishedSegment);
     }
 
     private int getNextUnfinishedSegmentFromIndex(int index) {
@@ -72,25 +81,17 @@ public class InitializerState extends SessionEntity {
         return -1;
     }
 
-    public String printState(boolean includeSegments) {
+    public String printState() {
         int finished = 0;
         int nonFinished = 0;
-        List<Integer> finishedList = new ArrayList<>();
-        List<Integer> nonFinishedList = new ArrayList<>();
 
         int size = segments.size();
         for (int i=0 ; i<size ; i++) {
             Boolean done = segments.get(i);
             if (done) {
                 finished++;
-                if (includeSegments) {
-                    finishedList.add(i);
-                }
             } else {
                 nonFinished++;
-                if (includeSegments) {
-                    nonFinishedList.add(i);
-                }
             }
         }
 
@@ -98,11 +99,6 @@ public class InitializerState extends SessionEntity {
                 .append(", finished segments count: " + finished)
                 .append(", non-finished segments count: " + nonFinished);
 
-        if (includeSegments) {
-            strBuilder.append(", finished segments: " + finishedList)
-                    .append(", non-finished segments: " + nonFinishedList);
-        }
-
         return strBuilder.toString();
     }
 }
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java
index e532369..db218e2 100644
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java
@@ -2,6 +2,7 @@ package org.keycloak.models.sessions.infinispan.initializer;
 
 import java.util.List;
 
+import org.jboss.logging.Logger;
 import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.UserSessionModel;
@@ -13,6 +14,20 @@ import org.keycloak.util.Time;
  */
 public class OfflineUserSessionLoader implements SessionLoader {
 
+    private static final Logger log = Logger.getLogger(OfflineUserSessionLoader.class);
+
+    @Override
+    public void init(KeycloakSession session) {
+        UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
+        int startTime = (int)(session.getKeycloakSessionFactory().getServerStartupTimestamp() / 1000);
+
+        // TODO: debug
+        log.infof("Clearing detached sessions from persistent storage and updating timestamps to %d", startTime);
+
+        persister.clearDetachedUserSessions();
+        persister.updateAllTimestamps(startTime);
+    }
+
     @Override
     public int getSessionsCount(KeycloakSession session) {
         UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
@@ -21,23 +36,19 @@ public class OfflineUserSessionLoader implements SessionLoader {
 
     @Override
     public boolean loadSessions(KeycloakSession session, int first, int max) {
+        // TODO: trace
+        log.infof("Loading sessions - first: %d, max: %d", first, max);
+
         UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
         List<UserSessionModel> sessions = persister.loadUserSessions(first, max, true);
 
-        // TODO: Each worker may have different time. Improve if needed...
-        int currentTime = Time.currentTime();
-
         for (UserSessionModel persistentSession : sessions) {
 
-            // Update and persist lastSessionRefresh time
-            persistentSession.setLastSessionRefresh(currentTime);
-            persister.updateUserSession(persistentSession, true);
-
             // Save to memory/infinispan
-            UserSessionModel offlineUserSession = session.sessions().createOfflineUserSession(persistentSession);
+            UserSessionModel offlineUserSession = session.sessions().importUserSession(persistentSession, true);
 
             for (ClientSessionModel persistentClientSession : persistentSession.getClientSessions()) {
-                ClientSessionModel offlineClientSession = session.sessions().createOfflineClientSession(persistentClientSession);
+                ClientSessionModel offlineClientSession = session.sessions().importClientSession(persistentClientSession, true);
                 offlineClientSession.setUserSession(offlineUserSession);
             }
         }
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/SessionLoader.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/SessionLoader.java
index 5014147..1fe977a 100644
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/SessionLoader.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/SessionLoader.java
@@ -9,6 +9,8 @@ import org.keycloak.models.KeycloakSession;
  */
 public interface SessionLoader extends Serializable {
 
+    void init(KeycloakSession session);
+
     int getSessionsCount(KeycloakSession session);
 
     boolean loadSessions(KeycloakSession session, int first, int max);
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientSessionsOfUserSessionMapper.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientSessionsOfUserSessionMapper.java
index 8300944..2f538ae 100644
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientSessionsOfUserSessionMapper.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientSessionsOfUserSessionMapper.java
@@ -13,18 +13,29 @@ import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
  *
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
-public class ClientSessionsOfUserSessionMapper implements Mapper<String, SessionEntity, String, ClientSessionEntity>, Serializable {
+public class ClientSessionsOfUserSessionMapper implements Mapper<String, SessionEntity, String, Object>, Serializable {
 
     private String realm;
     private Collection<String> userSessions;
 
+    private EmitValue emit = EmitValue.ENTITY;
+
+    private enum EmitValue {
+        KEY, ENTITY
+    }
+
     public ClientSessionsOfUserSessionMapper(String realm, Collection<String> userSessions) {
         this.realm = realm;
         this.userSessions = userSessions;
     }
 
+    public ClientSessionsOfUserSessionMapper emitKey() {
+        emit = EmitValue.KEY;
+        return this;
+    }
+
     @Override
-    public void map(String key, SessionEntity e, Collector<String, ClientSessionEntity> collector) {
+    public void map(String key, SessionEntity e, Collector<String, Object> collector) {
         if (!realm.equals(e.getRealm())) {
             return;
         }
@@ -35,9 +46,14 @@ public class ClientSessionsOfUserSessionMapper implements Mapper<String, Session
 
         ClientSessionEntity entity = (ClientSessionEntity) e;
 
-        for (String userSessionId : userSessions) {
-            if (userSessionId.equals(((ClientSessionEntity) e).getUserSession())) {
-                collector.emit(entity.getId(), entity);
+        if (userSessions.contains(entity.getUserSession())) {
+            switch (emit) {
+                case KEY:
+                    collector.emit(entity.getId(), entity.getId());
+                    break;
+                case ENTITY:
+                    collector.emit(entity.getId(), entity);
+                    break;
             }
         }
     }
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/UserSessionMapper.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/UserSessionMapper.java
index 210b6d5..139810d 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/UserSessionMapper.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/UserSessionMapper.java
@@ -26,9 +26,9 @@ public class UserSessionMapper implements Mapper<String, SessionEntity, String, 
 
     private String user;
 
-    private Long expired;
+    private Integer expired;
 
-    private Long expiredRefresh;
+    private Integer expiredRefresh;
 
     private String brokerSessionId;
     private String brokerUserId;
@@ -47,7 +47,7 @@ public class UserSessionMapper implements Mapper<String, SessionEntity, String, 
         return this;
     }
 
-    public UserSessionMapper expired(long expired, long expiredRefresh) {
+    public UserSessionMapper expired(Integer expired, Integer expiredRefresh) {
         this.expired = expired;
         this.expiredRefresh = expiredRefresh;
         return this;
@@ -86,6 +86,10 @@ public class UserSessionMapper implements Mapper<String, SessionEntity, String, 
             return;
         }
 
+        if (expired == null && expiredRefresh != null && entity.getLastSessionRefresh() > expiredRefresh) {
+            return;
+        }
+
         switch (emit) {
             case KEY:
                 collector.emit(key, key);
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java b/services/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
index 9b02eb8..681e76c 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
@@ -72,6 +72,12 @@ public interface AuthenticationFlowContext extends AbstractAuthenticationFlowCon
     void cancelLogin();
 
     /**
+     * Reset the current flow to the beginning and restarts it.
+     *
+     */
+    void resetFlow();
+
+    /**
      * Fork the current flow.  The client session will be cloned and set to point at the realm's browser login flow.  The Response will be the result
      * of this fork.  The previous flow will still be set at the current execution.  This is used by reset password when it sends an email.
      * It sends an email linking to the current flow and redirects the browser to a new browser login flow.
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 409b0e7..35b2ab9 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -55,6 +55,7 @@ public class AuthenticationProcessor {
     protected HttpRequest request;
     protected String flowId;
     protected String flowPath;
+    protected boolean browserFlow;
     /**
      * This could be an error message forwarded from another authenticator
      */
@@ -73,6 +74,15 @@ public class AuthenticationProcessor {
     public AuthenticationProcessor() {
     }
 
+    public boolean isBrowserFlow() {
+        return browserFlow;
+    }
+
+    public AuthenticationProcessor setBrowserFlow(boolean browserFlow) {
+        this.browserFlow = browserFlow;
+        return this;
+    }
+
     public RealmModel getRealm() {
         return realm;
     }
@@ -455,6 +465,11 @@ public class AuthenticationProcessor {
         }
 
         @Override
+        public void resetFlow() {
+            this.status = FlowStatus.FLOW_RESET;
+        }
+
+        @Override
         public void fork() {
             this.status = FlowStatus.FORK;
         }
@@ -640,6 +655,31 @@ public class AuthenticationProcessor {
         }
     }
 
+    public Response createSuccessRedirect() {
+        // redirect to non-action url so browser refresh button works without reposting past data
+        String code = generateCode();
+
+        URI redirect = LoginActionsService.loginActionsBaseUrl(getUriInfo())
+                .path(flowPath)
+                .queryParam(OAuth2Constants.CODE, code).build(getRealm().getName());
+        return Response.status(302).location(redirect).build();
+
+    }
+
+    public static Response createRequiredActionRedirect(RealmModel realm, ClientSessionModel clientSession, UriInfo uriInfo) {
+
+        // redirect to non-action url so browser refresh button works without reposting past data
+        ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
+        accessCode.setAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name());
+        clientSession.setTimestamp(Time.currentTime());
+
+        URI redirect = LoginActionsService.loginActionsBaseUrl(uriInfo)
+                .path(LoginActionsService.REQUIRED_ACTION)
+                .queryParam(OAuth2Constants.CODE, accessCode.getCode()).build(realm.getName());
+        return Response.status(302).location(redirect).build();
+
+    }
+
     public static void resetFlow(ClientSessionModel clientSession) {
         clientSession.setTimestamp(Time.currentTime());
         clientSession.setAuthenticatedUser(null);
@@ -773,7 +813,8 @@ public class AuthenticationProcessor {
 
     protected Response authenticationComplete() {
         attachSession();
-        return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, connection, request, uriInfo, event);
+        return createRequiredActionRedirect(realm, clientSession, uriInfo);
+        //return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, connection, request, uriInfo, event);
 
     }
 
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java
index e3427e0..4a0c48d 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java
@@ -37,6 +37,10 @@ public class OTPFormAuthenticator extends AbstractUsernameFormAuthenticator impl
 
     public void validateOTP(AuthenticationFlowContext context) {
         MultivaluedMap<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
+        if (inputData.containsKey("cancel")) {
+            context.resetFlow();
+            return;
+        }
         List<UserCredentialModel> credentials = new LinkedList<>();
         String password = inputData.getFirst(CredentialRepresentation.TOTP);
         if (password == null) {
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java
index 24ff085..8749360 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java
@@ -105,6 +105,8 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory
             context.failure(AuthenticationFlowError.INTERNAL_ERROR, challenge);
             return;
         }
+        // We now know email is valid, so set it to valid.
+        context.getUser().setEmailVerified(true);
         context.success();
     }
 
diff --git a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
index cf825c0..e4b6dc2 100755
--- a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
@@ -1,11 +1,19 @@
 package org.keycloak.authentication;
 
+import org.keycloak.OAuth2Constants;
 import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.resources.LoginActionsService;
+import org.keycloak.util.Time;
+import org.omg.PortableInterceptor.SUCCESSFUL;
+
+import static org.keycloak.authentication.FlowStatus.*;
 
 import javax.ws.rs.core.Response;
+import java.net.URI;
 import java.util.Iterator;
 import java.util.List;
 
@@ -61,7 +69,14 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
                 AuthenticationProcessor.Result result = processor.createAuthenticatorContext(model, authenticator, executions);
                 authenticator.action(result);
                 Response response = processResult(result);
-                if (response == null) return processFlow();
+                if (response == null) {
+                    if (result.status == SUCCESS && processor.isBrowserFlow()) {
+                         // redirect to a non-action URL so browser refresh works without reposting.
+                         return processor.createSuccessRedirect();
+                    } else {
+                        return processFlow();
+                    }
+                }
                 else return response;
             }
         }
@@ -153,62 +168,65 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
     public Response processResult(AuthenticationProcessor.Result result) {
         AuthenticationExecutionModel execution = result.getExecution();
         FlowStatus status = result.getStatus();
-        if (status == FlowStatus.SUCCESS) {
-            AuthenticationProcessor.logger.debugv("authenticator SUCCESS: {0}", execution.getAuthenticator());
-            processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
-            if (execution.isAlternative()) alternativeSuccessful = true;
-            return null;
-        } else if (status == FlowStatus.FAILED) {
-            AuthenticationProcessor.logger.debugv("authenticator FAILED: {0}", execution.getAuthenticator());
-            processor.logFailure();
-            processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED);
-            if (result.getChallenge() != null) {
-                return sendChallenge(result, execution);
-            }
-            throw new AuthenticationFlowException(result.getError());
-        } else if (status == FlowStatus.FORK) {
-            AuthenticationProcessor.logger.debugv("reset browser login from authenticator: {0}", execution.getAuthenticator());
-            processor.getClientSession().setNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, execution.getId());
-            throw new ForkFlowException(result.getSuccessMessage(), result.getErrorMessage());
-        } else if (status == FlowStatus.FORCE_CHALLENGE) {
-            processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
-            return sendChallenge(result, execution);
-        } else if (status == FlowStatus.CHALLENGE) {
-            AuthenticationProcessor.logger.debugv("authenticator CHALLENGE: {0}", execution.getAuthenticator());
-            if (execution.isRequired()) {
+        switch (status) {
+            case SUCCESS:
+                AuthenticationProcessor.logger.debugv("authenticator SUCCESS: {0}", execution.getAuthenticator());
+                processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
+                if (execution.isAlternative()) alternativeSuccessful = true;
+                return null;
+            case FAILED:
+                AuthenticationProcessor.logger.debugv("authenticator FAILED: {0}", execution.getAuthenticator());
+                processor.logFailure();
+                processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED);
+                if (result.getChallenge() != null) {
+                    return sendChallenge(result, execution);
+                }
+                throw new AuthenticationFlowException(result.getError());
+            case FORK:
+                AuthenticationProcessor.logger.debugv("reset browser login from authenticator: {0}", execution.getAuthenticator());
+                processor.getClientSession().setNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, execution.getId());
+                throw new ForkFlowException(result.getSuccessMessage(), result.getErrorMessage());
+            case FORCE_CHALLENGE:
                 processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
                 return sendChallenge(result, execution);
-            }
-            UserModel authenticatedUser = processor.getClientSession().getAuthenticatedUser();
-            if (execution.isOptional() && authenticatedUser != null && result.getAuthenticator().configuredFor(processor.getSession(), processor.getRealm(), authenticatedUser)) {
+            case CHALLENGE:
+                AuthenticationProcessor.logger.debugv("authenticator CHALLENGE: {0}", execution.getAuthenticator());
+                if (execution.isRequired()) {
+                    processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+                    return sendChallenge(result, execution);
+                }
+                UserModel authenticatedUser = processor.getClientSession().getAuthenticatedUser();
+                if (execution.isOptional() && authenticatedUser != null && result.getAuthenticator().configuredFor(processor.getSession(), processor.getRealm(), authenticatedUser)) {
+                    processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+                    return sendChallenge(result, execution);
+                }
+                if (execution.isAlternative()) {
+                    alternativeChallenge = result.getChallenge();
+                    challengedAlternativeExecution = execution;
+                } else {
+                    processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
+                }
+                return null;
+            case FAILURE_CHALLENGE:
+                AuthenticationProcessor.logger.debugv("authenticator FAILURE_CHALLENGE: {0}", execution.getAuthenticator());
+                processor.logFailure();
                 processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
                 return sendChallenge(result, execution);
-            }
-            if (execution.isAlternative()) {
-                alternativeChallenge = result.getChallenge();
-                challengedAlternativeExecution = execution;
-            } else {
-                processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
-            }
-            return null;
-        } else if (status == FlowStatus.FAILURE_CHALLENGE) {
-            AuthenticationProcessor.logger.debugv("authenticator FAILURE_CHALLENGE: {0}", execution.getAuthenticator());
-            processor.logFailure();
-            processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
-            return sendChallenge(result, execution);
-        } else if (status == FlowStatus.ATTEMPTED) {
-            AuthenticationProcessor.logger.debugv("authenticator ATTEMPTED: {0}", execution.getAuthenticator());
-            if (execution.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
-                throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CREDENTIALS);
-            }
-            processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED);
-            return null;
-        } else {
-            AuthenticationProcessor.logger.debugv("authenticator INTERNAL_ERROR: {0}", execution.getAuthenticator());
-            AuthenticationProcessor.logger.error("Unknown result status");
-            throw new AuthenticationFlowException(AuthenticationFlowError.INTERNAL_ERROR);
+            case ATTEMPTED:
+                AuthenticationProcessor.logger.debugv("authenticator ATTEMPTED: {0}", execution.getAuthenticator());
+                if (execution.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
+                    throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CREDENTIALS);
+                }
+                processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED);
+                return null;
+            case FLOW_RESET:
+                AuthenticationProcessor.resetFlow(processor.getClientSession());
+                return processor.authenticate();
+            default:
+                AuthenticationProcessor.logger.debugv("authenticator INTERNAL_ERROR: {0}", execution.getAuthenticator());
+                AuthenticationProcessor.logger.error("Unknown result status");
+                throw new AuthenticationFlowException(AuthenticationFlowError.INTERNAL_ERROR);
         }
-
     }
 
     public Response sendChallenge(AuthenticationProcessor.Result result, AuthenticationExecutionModel execution) {
diff --git a/services/src/main/java/org/keycloak/authentication/FlowStatus.java b/services/src/main/java/org/keycloak/authentication/FlowStatus.java
index 6e6ecbd..c8acbc7 100755
--- a/services/src/main/java/org/keycloak/authentication/FlowStatus.java
+++ b/services/src/main/java/org/keycloak/authentication/FlowStatus.java
@@ -48,6 +48,13 @@ public enum FlowStatus {
      * This flow is being forked.  The current client session is being cloned, reset, and redirected to browser login.
      *
      */
-    FORK
+    FORK,
+
+    /**
+     * This flow was reset to the beginning.  An example is hitting cancel on the OTP page which will bring you back to the
+     * username password page.
+     *
+     */
+    FLOW_RESET
 
 }
diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
index c94e8cb..9fb9b1b 100755
--- a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
@@ -219,8 +219,10 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
             action.setRequiredActions(processor.getSession(), processor.getRealm(), processor.getClientSession().getAuthenticatedUser());
 
         }
-        return null;
+        processor.getClientSession().setExecutionStatus(actionExecution, ClientSessionModel.ExecutionStatus.SUCCESS);
 
+        // redirect to no execution so browser refresh button works without reposting past data
+        return processor.createSuccessRedirect();
     }
 
     public URI getActionUrl(String executionId, String code) {
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContext.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContext.java
index 9b650c8..cc37f89 100755
--- a/services/src/main/java/org/keycloak/authentication/RequiredActionContext.java
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContext.java
@@ -86,7 +86,7 @@ public interface RequiredActionContext {
      *
      * @return
      */
-    String generateAccessCode(String action);
+    String generateCode();
 
     Status getStatus();
 
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
index b9e06f8..f2ada5d 100755
--- a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
@@ -12,6 +12,7 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.resources.LoginActionsService;
+import org.keycloak.util.Time;
 
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
@@ -93,13 +94,6 @@ public class RequiredActionContextResult implements RequiredActionContext {
     }
 
     @Override
-    public String generateAccessCode(String action) {
-        ClientSessionCode code = new ClientSessionCode(getRealm(), getClientSession());
-        code.setAction(action);
-        return code.getCode();
-    }
-
-    @Override
     public Status getStatus() {
         return status;
     }
@@ -136,15 +130,23 @@ public class RequiredActionContextResult implements RequiredActionContext {
     }
 
     @Override
+    public String generateCode() {
+        ClientSessionCode accessCode = new ClientSessionCode(getRealm(), getClientSession());
+        clientSession.setTimestamp(Time.currentTime());
+        return accessCode.getCode();
+    }
+
+
+    @Override
     public URI getActionUrl() {
-        String accessCode = generateAccessCode(factory.getId());
+        String accessCode = generateCode();
         return getActionUrl(accessCode);
 
     }
 
     @Override
     public LoginFormsProvider form() {
-        String accessCode = generateAccessCode(factory.getId());
+        String accessCode = generateCode();
         URI action = getActionUrl(accessCode);
         LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class)
                 .setUser(getUser())
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
index 11755b2..1a593cb 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
@@ -36,9 +36,7 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
     }
     @Override
     public void requiredActionChallenge(RequiredActionContext context) {
-        // if this is EXECUTE_ACTIONS we know that the email sent is valid so we can verify it automatically
-        if (context.getClientSession().getNote(ClientSessionModel.Action.EXECUTE_ACTIONS.name()) != null) {
-            context.getUser().setEmailVerified(true);
+        if (context.getUser().isEmailVerified()) {
             context.success();
             return;
         }
@@ -52,7 +50,7 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
         LoginActionsService.createActionCookie(context.getRealm(), context.getUriInfo(), context.getConnection(), context.getUserSession().getId());
 
         LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
-                .setClientSessionCode(context.generateAccessCode(UserModel.RequiredAction.VERIFY_EMAIL.name()))
+                .setClientSessionCode(context.generateCode())
                 .setUser(context.getUser());
         Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.VERIFY_EMAIL);
         context.challenge(challenge);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
index 7c51b74..da13ae6 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
@@ -22,10 +22,10 @@ import org.keycloak.protocol.RestartLoginCookie;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.protocol.oidc.utils.RedirectUtils;
 import org.keycloak.services.ErrorPageException;
+import org.keycloak.services.Urls;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.messages.Messages;
-import org.keycloak.services.Urls;
 import org.keycloak.services.resources.LoginActionsService;
 
 import javax.ws.rs.GET;
@@ -174,7 +174,7 @@ public class AuthorizationEndpoint {
     private void checkClient() {
         if (clientId == null) {
             event.error(Errors.INVALID_REQUEST);
-            throw new ErrorPageException(session, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM );
+            throw new ErrorPageException(session, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM);
         }
 
         event.client(clientId);
@@ -182,12 +182,12 @@ public class AuthorizationEndpoint {
         client = realm.getClientByClientId(clientId);
         if (client == null) {
             event.error(Errors.CLIENT_NOT_FOUND);
-            throw new ErrorPageException(session, Messages.CLIENT_NOT_FOUND );
+            throw new ErrorPageException(session, Messages.CLIENT_NOT_FOUND);
         }
 
         if (client.isBearerOnly()) {
             event.error(Errors.NOT_ALLOWED);
-            throw new ErrorPageException(session, Messages.BEARER_ONLY );
+            throw new ErrorPageException(session, Messages.BEARER_ONLY);
         }
 
         if (client.isDirectGrantsOnly()) {
@@ -204,7 +204,7 @@ public class AuthorizationEndpoint {
                 responseType = legacyResponseType;
             } else {
                 event.error(Errors.INVALID_REQUEST);
-                throw new ErrorPageException(session, Messages.MISSING_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM );
+                throw new ErrorPageException(session, Messages.MISSING_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
             }
         }
 
@@ -216,7 +216,7 @@ public class AuthorizationEndpoint {
             }
         } else {
             event.error(Errors.INVALID_REQUEST);
-            throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM );
+            throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
         }
     }
 
@@ -280,29 +280,43 @@ public class AuthorizationEndpoint {
         String flowId = flow.getId();
         AuthenticationProcessor processor = createProcessor(flowId, LoginActionsService.AUTHENTICATE_PATH);
 
-        Response challenge = null;
-        try {
-            challenge = processor.authenticateOnly();
-            if (challenge == null) {
-                challenge = processor.attachSessionExecutionRequiredActions();
+        if (prompt != null && prompt.equals("none")) {
+            // OIDC prompt == NONE
+            // This means that client is just checking if the user is already completely logged in.
+            //
+            // here we cancel login if any authentication action or required action is required
+            Response challenge = null;
+            try {
+                challenge = processor.authenticateOnly();
+                if (challenge == null) {
+                    challenge = processor.attachSessionExecutionRequiredActions();
+                }
+            } catch (Exception e) {
+                return processor.handleBrowserException(e);
             }
-        } catch (Exception e) {
-            return processor.handleBrowserException(e);
-        }
 
-        if (challenge != null && prompt != null && prompt.equals("none")) {
-            if (processor.isUserSessionCreated()) {
-                session.sessions().removeUserSession(realm, processor.getUserSession());
+            if (challenge != null) {
+                if (processor.isUserSessionCreated()) {
+                    session.sessions().removeUserSession(realm, processor.getUserSession());
+                }
+                OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo, headers, event);
+                return oauth.cancelLogin(clientSession);
             }
-            OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo, headers, event);
-            return oauth.cancelLogin(clientSession);
-        }
 
-        if (challenge == null) {
-            return processor.finishAuthentication();
+            if (challenge == null) {
+                return processor.finishAuthentication();
+            } else {
+                RestartLoginCookie.setRestartCookie(realm, clientConnection, uriInfo, clientSession);
+                return challenge;
+            }
         } else {
-            RestartLoginCookie.setRestartCookie(realm, clientConnection, uriInfo, clientSession);
-            return challenge;
+            try {
+                RestartLoginCookie.setRestartCookie(realm, clientConnection, uriInfo, clientSession);
+                return processor.authenticate();
+            } catch (Exception e) {
+                return processor.handleBrowserException(e);
+            }
+
         }
     }
 
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index 0888dab..a2acde9 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -44,6 +44,8 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -98,9 +100,17 @@ public class TokenManager {
         ClientSessionModel clientSession = null;
         if (TokenUtil.TOKEN_TYPE_OFFLINE.equals(oldToken.getType())) {
 
-            clientSession = new UserSessionManager(session).findOfflineClientSession(realm, oldToken.getClientSession(), oldToken.getSessionState());
+            UserSessionManager sessionManager = new UserSessionManager(session);
+            clientSession = sessionManager.findOfflineClientSession(realm, oldToken.getClientSession(), oldToken.getSessionState());
             if (clientSession != null) {
                 userSession = clientSession.getUserSession();
+
+                // Revoke timeouted offline userSession
+                if (userSession.getLastSessionRefresh() < Time.currentTime() - realm.getOfflineSessionIdleTimeout()) {
+                    sessionManager.revokeOfflineUserSession(userSession);
+                    userSession = null;
+                    clientSession = null;
+                }
             }
         } else {
             // Find userSession regularly for online tokens
@@ -162,26 +172,24 @@ public class TokenManager {
 
         int currentTime = Time.currentTime();
 
-        if (realm.isRevokeRefreshToken() && !refreshToken.getType().equals(TokenUtil.TOKEN_TYPE_OFFLINE)) {
-            if (refreshToken.getIssuedAt() < validation.clientSession.getTimestamp()) {
+        if (realm.isRevokeRefreshToken()) {
+            int serverStartupTime = (int)(session.getKeycloakSessionFactory().getServerStartupTimestamp() / 1000);
+
+            if (refreshToken.getIssuedAt() < validation.clientSession.getTimestamp() && (serverStartupTime != validation.clientSession.getTimestamp())) {
                 throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
             }
 
-            validation.clientSession.setTimestamp(currentTime);
         }
 
+        validation.clientSession.setTimestamp(currentTime);
         validation.userSession.setLastSessionRefresh(currentTime);
 
-        AccessTokenResponseBuilder responseBuilder = responseBuilder(realm, authorizedClient, event, session, validation.userSession, validation.clientSession)
+        AccessTokenResponse res = responseBuilder(realm, authorizedClient, event, session, validation.userSession, validation.clientSession)
                 .accessToken(validation.newToken)
-                .generateIDToken();
-
-        // Don't generate refresh token again if refresh was triggered with offline token
-        if (!refreshToken.getType().equals(TokenUtil.TOKEN_TYPE_OFFLINE)) {
-            responseBuilder.generateRefreshToken();
-        }
+                .generateIDToken()
+                .generateRefreshToken()
+                .build();
 
-        AccessTokenResponse res = responseBuilder.build();
         return new RefreshResult(res, TokenUtil.TOKEN_TYPE_OFFLINE.equals(refreshToken.getType()));
     }
 
@@ -321,6 +329,21 @@ public class TokenManager {
                     }
                 }
             }
+
+            // Add all roles specified in scope parameter directly into requestedRoles, even if they are available just through composite role
+            List<RoleModel> scopeRoles = new LinkedList<>();
+            for (String scopeParamPart : scopeParamRoles) {
+                RoleModel scopeParamRole = getRoleFromScopeParam(client.getRealm(), scopeParamPart);
+                if (scopeParamRole != null) {
+                    for (RoleModel role : roles) {
+                        if (role.hasRole(scopeParamRole)) {
+                            scopeRoles.add(scopeParamRole);
+                        }
+                    }
+                }
+            }
+
+            roles.addAll(scopeRoles);
             requestedRoles = roles;
         }
 
@@ -337,6 +360,17 @@ public class TokenManager {
         }
     }
 
+    // For now, just use "roleName" for realm roles and "clientId/roleName" for client roles
+    private static RoleModel getRoleFromScopeParam(RealmModel realm, String scopeParamRole) {
+        String[] parts = scopeParamRole.split("/");
+        if (parts.length == 1) {
+            return realm.getRole(parts[0]);
+        } else {
+            ClientModel roleClient = realm.getClientByClientId(parts[0]);
+            return roleClient!=null ? roleClient.getRole(parts[1]) : null;
+        }
+    }
+
     public void verifyAccess(AccessToken token, AccessToken newToken) throws OAuthErrorException {
         if (token.getRealmAccess() != null) {
             if (newToken.getRealmAccess() == null) throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User no long has permission for realm roles");
@@ -507,7 +541,7 @@ public class TokenManager {
 
                 refreshToken = new RefreshToken(accessToken);
                 refreshToken.type(TokenUtil.TOKEN_TYPE_OFFLINE);
-                sessionManager.persistOfflineSession(clientSession, userSession);
+                sessionManager.createOrUpdateOfflineSession(clientSession, userSession);
             } else {
                 refreshToken = new RefreshToken(accessToken);
                 refreshToken.expiration(Time.currentTime() + realm.getSsoSessionIdleTimeout());
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
index c5be21b..08699e0 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
@@ -28,6 +28,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
     private Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap = new HashMap<Class<? extends Provider>, Map<String, ProviderFactory>>();
     protected CopyOnWriteArrayList<ProviderEventListener> listeners = new CopyOnWriteArrayList<ProviderEventListener>();
 
+    // TODO: Likely should be changed to int and use Time.currentTime() to be compatible with all our "time" reps
     protected long serverStartupTimestamp;
     
     @Override
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 95b4243..d356965 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
@@ -52,6 +52,7 @@ public class ApplianceBootstrap {
         realm.setSsoSessionIdleTimeout(1800);
         realm.setAccessTokenLifespan(60);
         realm.setSsoSessionMaxLifespan(36000);
+        realm.setOfflineSessionIdleTimeout(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT);
         realm.setAccessCodeLifespan(60);
         realm.setAccessCodeLifespanUserAction(300);
         realm.setAccessCodeLifespanLogin(1800);
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 332d71d..f31d1ff 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -68,6 +68,7 @@ public class AuthenticationManager {
     public static final String KEYCLOAK_SESSION_COOKIE = "KEYCLOAK_SESSION";
     public static final String KEYCLOAK_REMEMBER_ME = "KEYCLOAK_REMEMBER_ME";
     public static final String KEYCLOAK_LOGOUT_PROTOCOL = "KEYCLOAK_LOGOUT_PROTOCOL";
+    public static final String CURRENT_REQUIRED_ACTION = "CURRENT_REQUIRED_ACTION";
 
     protected BruteForceProtector protector;
 
@@ -525,6 +526,7 @@ public class AuthenticationManager {
                 return protocol.consentDenied(context.getClientSession());
             }
             else if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
+                clientSession.setNote(CURRENT_REQUIRED_ACTION, model.getProviderId());
                 return context.getChallenge();
             }
             else if (context.getStatus() == RequiredActionContext.Status.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 6ebe4f3..672bb2a 100755
--- a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
@@ -73,8 +73,12 @@ public class ClientSessionCode {
     }
 
     public static ParseResult parseResult(String code, KeycloakSession session, RealmModel realm) {
+        ParseResult result = new ParseResult();
+        if (code == null) {
+            result.illegalHash = true;
+            return result;
+        }
         try {
-            ParseResult result = new ParseResult();
             String[] parts = code.split("\\.");
             String id = parts[1];
 
@@ -93,7 +97,8 @@ public class ClientSessionCode {
             result.code = new ClientSessionCode(realm, clientSession);
             return result;
         } catch (RuntimeException e) {
-            return null;
+            result.illegalHash = true;
+            return result;
         }
     }
 
diff --git a/services/src/main/java/org/keycloak/services/managers/UserSessionManager.java b/services/src/main/java/org/keycloak/services/managers/UserSessionManager.java
index 5759d20..2f18d24 100644
--- a/services/src/main/java/org/keycloak/services/managers/UserSessionManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/UserSessionManager.java
@@ -1,6 +1,7 @@
 package org.keycloak.services.managers;
 
 import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
 
@@ -15,6 +16,7 @@ import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.session.UserSessionPersisterProvider;
+import org.keycloak.util.Time;
 
 /**
  *
@@ -32,17 +34,23 @@ public class UserSessionManager {
         this.persister = session.getProvider(UserSessionPersisterProvider.class);
     }
 
-    public void persistOfflineSession(ClientSessionModel clientSession, UserSessionModel userSession) {
+    public void createOrUpdateOfflineSession(ClientSessionModel clientSession, UserSessionModel userSession) {
         UserModel user = userSession.getUser();
 
-        // Verify if we already have UserSession with this ID. If yes, don't create another one
+        // Create and persist offline userSession if we don't have one
         UserSessionModel offlineUserSession = kcSession.sessions().getOfflineUserSession(clientSession.getRealm(), userSession.getId());
         if (offlineUserSession == null) {
             offlineUserSession = createOfflineUserSession(user, userSession);
+        } else {
+            // update lastSessionRefresh but don't need to persist
+            offlineUserSession.setLastSessionRefresh(Time.currentTime());
         }
 
-        // Create clientSession and save to DB.
-        createOfflineClientSession(user, clientSession, offlineUserSession);
+        // Create and persist clientSession
+        ClientSessionModel offlineClientSession = kcSession.sessions().getOfflineClientSession(clientSession.getRealm(), clientSession.getId());
+        if (offlineClientSession == null) {
+            createOfflineClientSession(user, clientSession, offlineUserSession);
+        }
     }
 
     // userSessionId is provided from offline token. It's used just to verify if it match the ID from clientSession representation
@@ -69,6 +77,15 @@ public class UserSessionManager {
         return clients;
     }
 
+    public List<UserSessionModel> findOfflineSessions(RealmModel realm, ClientModel client, UserModel user) {
+        List<ClientSessionModel> clientSessions = kcSession.sessions().getOfflineClientSessions(realm, user);
+        List<UserSessionModel> userSessions = new LinkedList<>();
+        for (ClientSessionModel clientSession : clientSessions) {
+            userSessions.add(clientSession.getUserSession());
+        }
+        return userSessions;
+    }
+
     public boolean revokeOfflineToken(UserModel user, ClientModel client) {
         RealmModel realm = client.getRealm();
 
@@ -91,6 +108,14 @@ public class UserSessionManager {
         return anyRemoved;
     }
 
+    public void revokeOfflineUserSession(UserSessionModel userSession) {
+        if (logger.isTraceEnabled()) {
+            logger.tracef("Removing offline user session '%s' for user '%s' ", userSession.getId(), userSession.getLoginUsername());
+        }
+        kcSession.sessions().removeOfflineUserSession(userSession.getRealm(), userSession.getId());
+        persister.removeUserSession(userSession.getId(), true);
+    }
+
     public boolean isOfflineTokenAllowed(ClientSessionModel clientSession) {
         RoleModel offlineAccessRole = clientSession.getRealm().getRole(Constants.OFFLINE_ACCESS_ROLE);
         if (offlineAccessRole == null) {
@@ -107,7 +132,7 @@ public class UserSessionManager {
         }
 
         UserSessionModel offlineUserSession = kcSession.sessions().createOfflineUserSession(userSession);
-        persister.createUserSession(userSession, true);
+        persister.createUserSession(offlineUserSession, true);
         return offlineUserSession;
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index 46e2a09..0c90af9 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -7,6 +7,7 @@ import org.jboss.resteasy.spi.NotFoundException;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.events.admin.OperationType;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelDuplicateException;
 import org.keycloak.models.RealmModel;
@@ -433,6 +434,15 @@ public class ClientResource {
         List<UserSessionModel> userSessions = session.sessions().getOfflineUserSessions(client.getRealm(), client, firstResult, maxResults);
         for (UserSessionModel userSession : userSessions) {
             UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(userSession);
+
+            // Update lastSessionRefresh with the timestamp from clientSession
+            for (ClientSessionModel clientSession : userSession.getClientSessions()) {
+                if (client.getId().equals(clientSession.getClient().getId())) {
+                    rep.setLastAccess(Time.toMillis(clientSession.getTimestamp()));
+                    break;
+                }
+            }
+
             sessions.add(rep);
         }
         return sessions;
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 22ebbb5..bd441d1 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
@@ -79,6 +79,7 @@ import org.keycloak.models.UsernameLoginFailureModel;
 import org.keycloak.services.managers.BruteForceProtector;
 import org.keycloak.services.managers.UserSessionManager;
 import org.keycloak.services.resources.AccountService;
+import org.keycloak.util.Time;
 
 /**
  * Base resource for managing users
@@ -350,6 +351,44 @@ public class UsersResource {
     }
 
     /**
+     * Get offline sessions associated with the user and client
+     *
+     * @param id User id
+     * @return
+     */
+    @Path("{id}/offline-sessions/{clientId}")
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<UserSessionRepresentation> getSessions(final @PathParam("id") String id, final @PathParam("clientId") String clientId) {
+        auth.requireView();
+        UserModel user = session.users().getUserById(id, realm);
+        if (user == null) {
+            throw new NotFoundException("User not found");
+        }
+        ClientModel client = realm.getClientById(clientId);
+        if (client == null) {
+            throw new NotFoundException("Client not found");
+        }
+        List<UserSessionModel> sessions = new UserSessionManager(session).findOfflineSessions(realm, client, user);
+        List<UserSessionRepresentation> reps = new ArrayList<UserSessionRepresentation>();
+        for (UserSessionModel session : sessions) {
+            UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(session);
+
+            // Update lastSessionRefresh with the timestamp from clientSession
+            for (ClientSessionModel clientSession : session.getClientSessions()) {
+                if (clientId.equals(clientSession.getClient().getId())) {
+                    rep.setLastAccess(Time.toMillis(clientSession.getTimestamp()));
+                    break;
+                }
+            }
+
+            reps.add(rep);
+        }
+        return reps;
+    }
+
+    /**
      * Get social logins associated with the user
      *
      * @param id User id
@@ -469,7 +508,14 @@ public class UsersResource {
             currentRep.put("grantedRealmRoles", (rep==null ? Collections.emptyList() : rep.getGrantedRealmRoles()));
             currentRep.put("grantedClientRoles", (rep==null ? Collections.emptyMap() : rep.getGrantedClientRoles()));
 
-            List<String> additionalGrants = hasOfflineToken ? Arrays.asList("Offline Token") : Collections.<String>emptyList();
+            List<Map<String, String>> additionalGrants = new LinkedList<>();
+            if (hasOfflineToken) {
+                Map<String, String> offlineTokens = new HashMap<>();
+                offlineTokens.put("client", client.getId());
+                // TODO: translate
+                offlineTokens.put("key", "Offline Token");
+                additionalGrants.add(offlineTokens);
+            }
             currentRep.put("additionalGrants", additionalGrants);
 
             result.add(currentRep);
@@ -882,15 +928,42 @@ public class UsersResource {
     }
 
     /**
-     * Send a password-reset email to the user
+     * Send an email to the user with a link they can click to reset their password.
+     * The redirectUri and clientId parameters are optional. The default for the
+     * redirect is the account client.
+     *
+     * This endpoint has been deprecated.  Please use the execute-actions-email passing a list with
+     * UPDATE_PASSWORD within it.
+     *
+     * @param id
+     * @param redirectUri redirect uri
+     * @param clientId client id
+     * @return
+     */
+    @Deprecated
+    @Path("{id}/reset-password-email")
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response resetPasswordEmail(@PathParam("id") String id,
+                                        @QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri,
+                                        @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId) {
+        List<String> actions = new LinkedList<>();
+        actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
+        return executeActionsEmail(id, redirectUri, clientId, actions);
+    }
+
+
+    /**
+     * Send a update account email to the user
      *
-     * An email contains a link the user can click to reset their password.
+     * An email contains a link the user can click to perform a set of required actions.
      * The redirectUri and clientId parameters are optional. The default for the
      * redirect is the account client.
      *
      * @param id User is
      * @param redirectUri Redirect uri
      * @param clientId Client id
+     * @param actions required actions the user needs to complete
      * @return
      */
     @Path("{id}/execute-actions-email")
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index 9bbfcf5..e6be392 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -322,8 +322,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
             LOGGER.debugf("Performing local authentication for user [%s].", federatedUser);
         }
 
-        return AuthenticationManager.nextActionAfterAuthentication(this.session, userSession, clientSession, this.clientConnection, this.request,
-                this.uriInfo, event);
+        return AuthenticationProcessor.createRequiredActionRedirect(realmModel, clientSession, uriInfo);
     }
 
     @Override
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 b6e4ce3..6e52534 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -85,6 +85,7 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import javax.ws.rs.ext.Providers;
+import java.net.URI;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
@@ -99,6 +100,7 @@ public class LoginActionsService {
     public static final String AUTHENTICATE_PATH = "authenticate";
     public static final String REGISTRATION_PATH = "registration";
     public static final String RESET_CREDENTIALS_PATH = "reset-credentials";
+    public static final String REQUIRED_ACTION = "required-action";
 
     private RealmModel realm;
 
@@ -167,50 +169,33 @@ public class LoginActionsService {
         boolean verifyCode(String code, String requiredAction) {
             if (!verifyCode(code)) {
                 return false;
-            } else if (!clientCode.isValidAction(requiredAction)) {
-                event.client(clientCode.getClientSession().getClient());
-                event.error(Errors.INVALID_CODE);
-                response = ErrorPage.error(session, Messages.INVALID_CODE);
-                return false;
-            } else if (!clientCode.isActionActive(requiredAction)) {
-                event.client(clientCode.getClientSession().getClient());
-                event.clone().error(Errors.EXPIRED_CODE);
-                if (clientCode.getClientSession().getAction().equals(ClientSessionModel.Action.AUTHENTICATE.name())) {
-                    AuthenticationProcessor.resetFlow(clientCode.getClientSession());
-                    response = processAuthentication(null, clientCode.getClientSession(), Messages.LOGIN_TIMEOUT);
-                    return false;
-                }
-                response = ErrorPage.error(session, Messages.EXPIRED_CODE);
+            }
+            if (!verifyAction(requiredAction)) {
                 return false;
             } else {
                 return true;
             }
         }
 
-        boolean verifyCode(String code, String requiredAction, String alternativeRequiredAction) {
-            if (!verifyCode(code)) {
-                return false;
-            } else if (!(clientCode.isValidAction(requiredAction) || clientCode.isValidAction(alternativeRequiredAction))) {
+        public boolean verifyAction(String requiredAction) {
+            if (!clientCode.isValidAction(requiredAction)) {
                 event.client(clientCode.getClientSession().getClient());
                 event.error(Errors.INVALID_CODE);
                 response = ErrorPage.error(session, Messages.INVALID_CODE);
                 return false;
-            } else if (!(clientCode.isActionActive(requiredAction) || clientCode.isActionActive(alternativeRequiredAction))) {
+            }
+            if (!clientCode.isActionActive(requiredAction)) {
                 event.client(clientCode.getClientSession().getClient());
                 event.clone().error(Errors.EXPIRED_CODE);
                 if (clientCode.getClientSession().getAction().equals(ClientSessionModel.Action.AUTHENTICATE.name())) {
                     AuthenticationProcessor.resetFlow(clientCode.getClientSession());
                     response = processAuthentication(null, clientCode.getClientSession(), Messages.LOGIN_TIMEOUT);
-                } else {
-                    if (clientCode.getClientSession().getUserSession() == null) {
-                        session.sessions().removeClientSession(realm, clientCode.getClientSession());
-                    }
-                    response = ErrorPage.error(session, Messages.EXPIRED_CODE);
+                    return false;
                 }
+                response = ErrorPage.error(session, Messages.EXPIRED_CODE);
                 return false;
-            } else {
-                return true;
             }
+            return true;
         }
 
         public boolean verifyCode(String code) {
@@ -298,6 +283,7 @@ public class LoginActionsService {
         AuthenticationProcessor processor = new AuthenticationProcessor();
         processor.setClientSession(clientSession)
                 .setFlowPath(flowPath)
+                .setBrowserFlow(true)
                 .setFlowId(flow.getId())
                 .setConnection(clientConnection)
                 .setEventBuilder(event)
@@ -559,11 +545,17 @@ public class LoginActionsService {
         event.event(EventType.VERIFY_EMAIL);
         if (key != null) {
             Checks checks = new Checks();
-            if (!checks.verifyCode(key, ClientSessionModel.Action.VERIFY_EMAIL.name())) {
+            if (!checks.verifyCode(key, ClientSessionModel.Action.REQUIRED_ACTIONS.name())) {
                 return checks.response;
             }
             ClientSessionCode accessCode = checks.clientCode;
             ClientSessionModel clientSession = accessCode.getClientSession();
+            if (!ClientSessionModel.Action.VERIFY_EMAIL.name().equals(clientSession.getNote(AuthenticationManager.CURRENT_REQUIRED_ACTION))) {
+                logger.error("required action doesn't match current required action");
+                event.error(Errors.INVALID_CODE);
+                throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
+            }
+
             UserSessionModel userSession = clientSession.getUserSession();
             UserModel user = userSession.getUser();
             initEvent(clientSession);
@@ -583,10 +575,10 @@ public class LoginActionsService {
 
             event = event.clone().removeDetail(Details.EMAIL).event(EventType.LOGIN);
 
-            return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
+            return AuthenticationProcessor.createRequiredActionRedirect(realm, clientSession, uriInfo);
         } else {
             Checks checks = new Checks();
-            if (!checks.verifyCode(code, ClientSessionModel.Action.VERIFY_EMAIL.name())) {
+            if (!checks.verifyCode(code, ClientSessionModel.Action.REQUIRED_ACTIONS.name())) {
                 return checks.response;
             }
             ClientSessionCode accessCode = checks.clientCode;
@@ -619,9 +611,11 @@ public class LoginActionsService {
                 return checks.response;
             }
             ClientSessionModel clientSession = checks.clientCode.getClientSession();
+            // verify user email as we know it is valid as this entry point would never have gotten here.
+            clientSession.getUserSession().getUser().setEmailVerified(true);
             clientSession.setNote(AuthenticationManager.END_AFTER_REQUIRED_ACTIONS, "true");
             clientSession.setNote(ClientSessionModel.Action.EXECUTE_ACTIONS.name(), "true");
-            return AuthenticationManager.nextActionAfterAuthentication(session, clientSession.getUserSession(), clientSession, clientConnection, request, uriInfo, event);
+            return AuthenticationProcessor.createRequiredActionRedirect(realm, clientSession, uriInfo);
         } else {
             event.error(Errors.INVALID_CODE);
             return ErrorPage.error(session, Messages.INVALID_CODE);
@@ -658,7 +652,7 @@ public class LoginActionsService {
         }
     }
 
-    @Path("required-action")
+    @Path(REQUIRED_ACTION)
     @POST
     public Response requiredActionPOST(@QueryParam("code") final String code,
                                        @QueryParam("action") String action) {
@@ -668,7 +662,7 @@ public class LoginActionsService {
 
     }
 
-    @Path("required-action")
+    @Path(REQUIRED_ACTION)
     @GET
     public Response requiredActionGET(@QueryParam("code") final String code,
                                        @QueryParam("action") String action) {
@@ -678,22 +672,8 @@ public class LoginActionsService {
     public Response processRequireAction(final String code, String action) {
         event.event(EventType.CUSTOM_REQUIRED_ACTION);
         event.detail(Details.CUSTOM_REQUIRED_ACTION, action);
-        if (action == null) {
-            logger.error("required action query param was null");
-            event.error(Errors.INVALID_CODE);
-            throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
-
-        }
-
-        RequiredActionFactory factory = (RequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, action);
-        if (factory == null) {
-            logger.error("required action provider was null");
-            event.error(Errors.INVALID_CODE);
-            throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
-        }
-        RequiredActionProvider provider = factory.create(session);
         Checks checks = new Checks();
-        if (!checks.verifyCode(code, action)) {
+        if (!checks.verifyCode(code, ClientSessionModel.Action.REQUIRED_ACTIONS.name())) {
             return checks.response;
         }
         final ClientSessionCode clientCode = checks.clientCode;
@@ -704,24 +684,31 @@ public class LoginActionsService {
             event.error(Errors.USER_SESSION_NOT_FOUND);
             throw new WebApplicationException(ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE));
         }
+        if (action == null && clientSession.getUserSession() != null) { // do next required action only if user is already authenticated
+            initEvent(clientSession);
+            event.event(EventType.LOGIN);
+            return AuthenticationManager.nextActionAfterAuthentication(session, clientSession.getUserSession(), clientSession, clientConnection, request, uriInfo, event);
+        }
+
+        if (!action.equals(clientSession.getNote(AuthenticationManager.CURRENT_REQUIRED_ACTION))) {
+            logger.error("required action doesn't match current required action");
+            event.error(Errors.INVALID_CODE);
+            throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
+        }
+
+        RequiredActionFactory factory = (RequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, action);
+        if (factory == null) {
+            logger.error("required action provider was null");
+            event.error(Errors.INVALID_CODE);
+            throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
+        }
+        RequiredActionProvider provider = factory.create(session);
 
         initEvent(clientSession);
         event.event(EventType.CUSTOM_REQUIRED_ACTION);
 
 
         RequiredActionContextResult context = new RequiredActionContextResult(clientSession.getUserSession(), clientSession, realm, event, session, request, clientSession.getUserSession().getUser(), factory) {
-             @Override
-            public String generateAccessCode(String action) {
-                String clientSessionAction = clientSession.getAction();
-                if (action.equals(clientSessionAction)) {
-                    clientSession.setTimestamp(Time.currentTime());
-                    return code;
-                }
-                ClientSessionCode code = new ClientSessionCode(getRealm(), getClientSession());
-                code.setAction(action);
-                return code.getCode();
-            }
-
             @Override
             public void ignore() {
                 throw new RuntimeException("Cannot call ignore within processAction()");
@@ -729,13 +716,16 @@ public class LoginActionsService {
         };
         provider.processAction(context);
         if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
-            event.clone().success();
+            event.success();
             // do both
             clientSession.removeRequiredAction(factory.getId());
             clientSession.getUserSession().getUser().removeRequiredAction(factory.getId());
-            event.event(EventType.LOGIN);
-            return AuthenticationManager.nextActionAfterAuthentication(session, clientSession.getUserSession(), clientSession, clientConnection, request, uriInfo, event);
-        }
+            // redirect to a generic code URI so that browser refresh will work
+            URI redirect = LoginActionsService.loginActionsBaseUrl(uriInfo)
+                    .path(LoginActionsService.REQUIRED_ACTION)
+                    .queryParam(OAuth2Constants.CODE, code).build(realm.getName());
+            return Response.status(302).location(redirect).build();
+       }
         if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
             return context.getChallenge();
         }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
index 8c41dfa..bb33515 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
@@ -15,6 +15,8 @@ import java.io.IOException;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.HashSet;
+import java.util.Arrays;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -103,6 +105,31 @@ public class RealmTest extends AbstractClientTest {
     }
 
     @Test
+    public void updateRealmWithNewRepresentation() {
+        // first change
+        RealmRepresentation rep = new RealmRepresentation();
+        rep.setEditUsernameAllowed(true);
+        rep.setSupportedLocales(new HashSet<>(Arrays.asList("en", "de")));
+
+        realm.update(rep);
+
+        rep = realm.toRepresentation();
+
+        assertEquals(Boolean.TRUE, rep.isEditUsernameAllowed());
+        assertEquals(2, rep.getSupportedLocales().size());
+
+        // second change
+        rep = new RealmRepresentation();
+        rep.setEditUsernameAllowed(false);
+
+        realm.update(rep);
+
+        rep = realm.toRepresentation();
+        assertEquals(Boolean.FALSE, rep.isEditUsernameAllowed());
+        assertEquals(2, rep.getSupportedLocales().size());
+    }
+
+    @Test
     public void getRealmRepresentation() {
         RealmRepresentation rep = realm.toRepresentation();
         assertEquals(REALM_NAME, rep.getRealm());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
index 8eb05b6..294fdb5 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
@@ -175,6 +175,7 @@ public abstract class AbstractKerberosTest {
         events.clear();
         Response spnegoResponse = spnegoLogin("jduke", "theduke");
         Assert.assertEquals(302, spnegoResponse.getStatus());
+        String redirect = spnegoResponse.getLocation().toString();
         events.expectLogin()
                 .client("kerberos-app")
                 .user(keycloakRule.getUser("test", "jduke").getId())
@@ -244,6 +245,13 @@ public abstract class AbstractKerberosTest {
         spnegoSchemeFactory.setCredentials(username, password);
         Response response = client.target(kcLoginPageLocation).request().get();
         SpnegoAuthenticator.bypassChallengeJavascript = false;
+        if (response.getStatus() == 302) {
+            if (response.getLocation() == null) return response;
+            String uri = response.getLocation().toString();
+            if (uri.contains("login-actions/required-action")) {
+                response = client.target(uri).request().get();
+            }
+        }
         return response;
 
     }
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 b37401c..6eda3fa 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
@@ -154,6 +154,16 @@ public class LoginTotpTest {
     }
 
     @Test
+    public void loginWithTotpCancel() throws Exception {
+        loginPage.open();
+        loginPage.login("test-user@localhost", "password");
+
+        loginTotpPage.assertCurrent();
+        loginTotpPage.cancel();
+        loginPage.assertCurrent();
+    }
+
+    @Test
     public void loginWithTotpInvalidPassword() throws Exception {
         loginPage.open();
         loginPage.login("test-user@localhost", "invalid");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
index b13bcdc..5d7eae7 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
@@ -76,6 +76,7 @@ public class ImportTest extends AbstractModelTest {
     // Moved to static method, so it's possible to test this from other places too (for example export-import tests)
     public static void assertDataImportedInRealm(KeycloakSession session, RealmModel realm) {
         Assert.assertTrue(realm.isVerifyEmail());
+        Assert.assertEquals(3600000, realm.getOfflineSessionIdleTimeout());
 
         List<RequiredCredentialModel> creds = realm.getRequiredCredentials();
         Assert.assertEquals(1, creds.size());
@@ -361,6 +362,7 @@ public class ImportTest extends AbstractModelTest {
         RealmModel realm =manager.importRealm(rep);
 
         Assert.assertEquals(600, realm.getAccessCodeLifespanUserAction());
+        Assert.assertEquals(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT, realm.getOfflineSessionIdleTimeout());
         verifyRequiredCredentials(realm.getRequiredCredentials(), "password");
     }
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
index cf7cc8a..6508cfb 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
@@ -65,10 +65,13 @@ public class UserSessionInitializerTest {
         resetSession();
 
         // Create and persist offline sessions
+        int started = Time.currentTime();
+        int serverStartTime = (int)(session.getKeycloakSessionFactory().getServerStartupTimestamp() / 1000);
+
         for (UserSessionModel origSession : origSessions) {
             UserSessionModel userSession = session.sessions().getUserSession(realm, origSession.getId());
             for (ClientSessionModel clientSession : userSession.getClientSessions()) {
-                sessionManager.persistOfflineSession(clientSession, userSession);
+                sessionManager.createOrUpdateOfflineSession(clientSession, userSession);
             }
         }
 
@@ -88,32 +91,23 @@ public class UserSessionInitializerTest {
         Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(realm, testApp));
         Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(realm, thirdparty));
 
-        int started = Time.currentTime();
-
-        try {
-            // Set some offset to ensure lastSessionRefresh will be updated
-            Time.setOffset(10);
+        // Load sessions from persister into infinispan/memory
+        UserSessionProviderFactory userSessionFactory = (UserSessionProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserSessionProvider.class);
+        userSessionFactory.loadPersistentSessions(session.getKeycloakSessionFactory(), 1, 2);
 
-            // Load sessions from persister into infinispan/memory
-            UserSessionProviderFactory userSessionFactory = (UserSessionProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserSessionProvider.class);
-            userSessionFactory.loadPersistentSessions(session.getKeycloakSessionFactory(), 10, 2);
-
-            resetSession();
+        resetSession();
 
-            // Assert sessions are in
-            testApp = realm.getClientByClientId("test-app");
-            Assert.assertEquals(3, session.sessions().getOfflineSessionsCount(realm, testApp));
-            Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
+        // Assert sessions are in
+        testApp = realm.getClientByClientId("test-app");
+        Assert.assertEquals(3, session.sessions().getOfflineSessionsCount(realm, testApp));
+        Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
 
-            List<UserSessionModel> loadedSessions = session.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
-            UserSessionProviderTest.assertSessions(loadedSessions, origSessions);
+        List<UserSessionModel> loadedSessions = session.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
+        UserSessionProviderTest.assertSessions(loadedSessions, origSessions);
 
-            UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[0].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started+10, "test-app", "third-party");
-            UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[1].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started+10, "test-app");
-            UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[2].getId(), session.users().getUserByUsername("user2", realm), "127.0.0.3", started, started+10, "test-app");
-        } finally {
-            Time.setOffset(0);
-        }
+        UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[0].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, serverStartTime, "test-app", "third-party");
+        UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[1].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.2", started, serverStartTime, "test-app");
+        UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[2].getId(), session.users().getUserByUsername("user2", realm), "127.0.0.3", started, serverStartTime, "test-app");
     }
 
     private ClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java
index 53480aa..4edf951 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java
@@ -94,6 +94,52 @@ public class UserSessionPersisterProviderTest {
     }
 
     @Test
+    public void testUpdateTimestamps() {
+        // Create some sessions in infinispan
+        int started = Time.currentTime();
+        UserSessionModel[] origSessions = createSessions();
+
+        resetSession();
+
+        // Persist 3 created userSessions and clientSessions as offline
+        ClientModel testApp = realm.getClientByClientId("test-app");
+        List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
+        for (UserSessionModel userSession : userSessions) {
+            persistUserSession(userSession, true);
+        }
+
+        // Persist 1 online session
+        UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
+        persistUserSession(userSession, false);
+
+        resetSession();
+
+        // update timestamps
+        int newTime = started + 50;
+        persister.updateAllTimestamps(newTime);
+
+        // Assert online session
+        List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(false, 1, 1, 1);
+        Assert.assertEquals(2, assertTimestampsUpdated(loadedSessions, newTime));
+
+        // Assert offline sessions
+        loadedSessions = loadPersistedSessionsPaginated(true, 2, 2, 3);
+        Assert.assertEquals(4, assertTimestampsUpdated(loadedSessions, newTime));
+    }
+
+    private int assertTimestampsUpdated(List<UserSessionModel> loadedSessions, int expectedTime) {
+        int clientSessionsCount = 0;
+        for (UserSessionModel loadedSession : loadedSessions) {
+            Assert.assertEquals(expectedTime, loadedSession.getLastSessionRefresh());
+            for (ClientSessionModel clientSession : loadedSession.getClientSessions()) {
+                Assert.assertEquals(expectedTime, clientSession.getTimestamp());
+                clientSessionsCount++;
+            }
+        }
+        return clientSessionsCount;
+    }
+
+    @Test
     public void testUpdateAndRemove() {
         // Create some sessions in infinispan
         int started = Time.currentTime();
@@ -245,11 +291,6 @@ public class UserSessionPersisterProviderTest {
         realmMgr.removeRealm(realmMgr.getRealm("foo"));
     }
 
-//    @Test
-//    public void testExpiredUserSessions() {
-//
-//    }
-
 
     private ClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
         ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java
index c843928..dc15b44 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java
@@ -2,6 +2,7 @@ package org.keycloak.testsuite.model;
 
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -17,6 +18,7 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.session.UserSessionPersisterProvider;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.services.managers.ClientManager;
 import org.keycloak.services.managers.RealmManager;
@@ -36,6 +38,7 @@ public class UserSessionProviderOfflineTest {
     private KeycloakSession session;
     private RealmModel realm;
     private UserSessionManager sessionManager;
+    private UserSessionPersisterProvider persister;
 
     @Before
     public void before() {
@@ -44,6 +47,7 @@ public class UserSessionProviderOfflineTest {
         session.users().addUser(realm, "user1").setEmail("user1@localhost");
         session.users().addUser(realm, "user2").setEmail("user2@localhost");
         sessionManager = new UserSessionManager(session);
+        persister = session.getProvider(UserSessionPersisterProvider.class);
     }
 
     @After
@@ -157,7 +161,7 @@ public class UserSessionProviderOfflineTest {
         fooRealm = session.realms().getRealm("foo");
         userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
         clientSession = session.sessions().getClientSession(fooRealm, clientSession.getId());
-        sessionManager.persistOfflineSession(userSession.getClientSessions().get(0), userSession);
+        sessionManager.createOrUpdateOfflineSession(userSession.getClientSessions().get(0), userSession);
 
         resetSession();
 
@@ -291,13 +295,97 @@ public class UserSessionProviderOfflineTest {
 
     }
 
+    @Test
+    public void testExpired() {
+        // Create some online sessions in infinispan
+        int started = Time.currentTime();
+        UserSessionModel[] origSessions = createSessions();
+
+        resetSession();
+
+        Map<String, String> offlineSessions = new HashMap<>();
+
+        // Persist 3 created userSessions and clientSessions as offline
+        ClientModel testApp = realm.getClientByClientId("test-app");
+        List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
+        for (UserSessionModel userSession : userSessions) {
+            offlineSessions.putAll(createOfflineSessionIncludeClientSessions(userSession));
+        }
+
+        resetSession();
+
+        // Assert all previously saved offline sessions found
+        for (Map.Entry<String, String> entry : offlineSessions.entrySet()) {
+            Assert.assertTrue(sessionManager.findOfflineClientSession(realm, entry.getKey(), entry.getValue()) != null);
+        }
+
+        UserSessionModel session0 = session.sessions().getOfflineUserSession(realm, origSessions[0].getId());
+        Assert.assertNotNull(session0);
+        List<String> clientSessions = new LinkedList<>();
+        for (ClientSessionModel clientSession : session0.getClientSessions()) {
+            clientSessions.add(clientSession.getId());
+            Assert.assertNotNull(session.sessions().getOfflineClientSession(realm, clientSession.getId()));
+        }
+
+        UserSessionModel session1 = session.sessions().getOfflineUserSession(realm, origSessions[1].getId());
+        Assert.assertEquals(1, session1.getClientSessions().size());
+        ClientSessionModel cls1 = session1.getClientSessions().get(0);
+
+        // sessions are in persister too
+        Assert.assertEquals(3, persister.getUserSessionsCount(true));
+
+        // Set lastSessionRefresh to session[0] to 0
+        session0.setLastSessionRefresh(0);
+
+        // Set timestamp to cls1 to 0
+        cls1.setTimestamp(0);
+
+        resetSession();
+
+        session.sessions().removeExpiredUserSessions(realm);
+
+        resetSession();
+
+        // assert session0 not found now
+        Assert.assertNull(session.sessions().getOfflineUserSession(realm, origSessions[0].getId()));
+        for (String clientSession : clientSessions) {
+            Assert.assertNull(session.sessions().getOfflineClientSession(realm, origSessions[0].getId()));
+            offlineSessions.remove(clientSession);
+        }
+
+        // Assert cls1 not found too
+        for (Map.Entry<String, String> entry : offlineSessions.entrySet()) {
+            String userSessionId = entry.getValue();
+            if (userSessionId.equals(session1.getId())) {
+                Assert.assertFalse(sessionManager.findOfflineClientSession(realm, entry.getKey(), userSessionId) != null);
+            } else {
+                Assert.assertTrue(sessionManager.findOfflineClientSession(realm, entry.getKey(), userSessionId) != null);
+            }
+        }
+        Assert.assertEquals(1, persister.getUserSessionsCount(true));
+
+        // Expire everything and assert nothing found
+        Time.setOffset(3000000);
+        try {
+            session.sessions().removeExpiredUserSessions(realm);
+
+            resetSession();
+
+            for (Map.Entry<String, String> entry : offlineSessions.entrySet()) {
+                Assert.assertTrue(sessionManager.findOfflineClientSession(realm, entry.getKey(), entry.getValue()) == null);
+            }
+            Assert.assertEquals(0, persister.getUserSessionsCount(true));
+
+        } finally {
+            Time.setOffset(0);
+        }
+    }
+
     private Map<String, String> createOfflineSessionIncludeClientSessions(UserSessionModel userSession) {
         Map<String, String> offlineSessions = new HashMap<>();
 
-        UserSessionModel offlineUserSession = session.sessions().createOfflineUserSession(userSession);
         for (ClientSessionModel clientSession : userSession.getClientSessions()) {
-            ClientSessionModel offlineClientSession = session.sessions().createOfflineClientSession(clientSession);
-            offlineClientSession.setUserSession(offlineUserSession);
+            sessionManager.createOrUpdateOfflineSession(clientSession, userSession);
             offlineSessions.put(clientSession.getId(), userSession.getId());
         }
         return offlineSessions;
@@ -310,6 +398,7 @@ public class UserSessionProviderOfflineTest {
         session = kc.startSession();
         realm = session.realms().getRealm("test");
         sessionManager = new UserSessionManager(session);
+        persister = session.getProvider(UserSessionPersisterProvider.class);
     }
 
     private ClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
index 9f95f26..4a86fec 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
@@ -30,6 +30,7 @@ import org.keycloak.models.Constants;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.protocol.oidc.TokenManager;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.RefreshToken;
 import org.keycloak.services.managers.ClientManager;
@@ -227,10 +228,27 @@ public class OfflineTokenTest {
         Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
         Assert.assertEquals(0, offlineToken.getExpiration());
 
-        testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, sessionId, userId);
+        String newRefreshTokenString = testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, sessionId, userId);
+
+        // Change offset to very big value to ensure offline session expires
+        Time.setOffset(3000000);
+
+        OAuthClient.AccessTokenResponse response = oauth.doRefreshTokenRequest(newRefreshTokenString, "secret1");
+        Assert.assertEquals(400, response.getStatusCode());
+        assertEquals("invalid_grant", response.getError());
+
+        events.expectRefresh(offlineToken.getId(), sessionId)
+                .client("offline-client")
+                .error(Errors.INVALID_TOKEN)
+                .user(userId)
+                .clearDetails()
+                .assertEvent();
+
+
+        Time.setOffset(0);
     }
 
-    private void testRefreshWithOfflineToken(AccessToken oldToken, RefreshToken offlineToken, String offlineTokenString,
+    private String testRefreshWithOfflineToken(AccessToken oldToken, RefreshToken offlineToken, String offlineTokenString,
                                              final String sessionId, String userId) {
         // Change offset to big value to ensure userSession expired
         Time.setOffset(99999);
@@ -261,13 +279,13 @@ public class OfflineTokenTest {
         Assert.assertEquals(200, response.getStatusCode());
         Assert.assertEquals(sessionId, refreshedToken.getSessionState());
 
-        // Assert no refreshToken in the response
-        Assert.assertNull(response.getRefreshToken());
+        // Assert new refreshToken in the response
+        String newRefreshToken = response.getRefreshToken();
+        Assert.assertNotNull(newRefreshToken);
         Assert.assertNotEquals(oldToken.getId(), refreshedToken.getId());
 
         Assert.assertEquals(userId, refreshedToken.getSubject());
 
-        Assert.assertEquals(2, refreshedToken.getRealmAccess().getRoles().size());
         Assert.assertTrue(refreshedToken.getRealmAccess().isUserInRole("user"));
         Assert.assertTrue(refreshedToken.getRealmAccess().isUserInRole(Constants.OFFLINE_ACCESS_ROLE));
 
@@ -283,6 +301,7 @@ public class OfflineTokenTest {
         Assert.assertNotEquals(oldToken.getId(), refreshEvent.getDetails().get(Details.TOKEN_ID));
 
         Time.setOffset(0);
+        return newRefreshToken;
     }
 
     @Test
@@ -313,6 +332,71 @@ public class OfflineTokenTest {
         Assert.assertEquals(0, offlineToken.getExpiration());
 
         testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), userId);
+
+        // Assert same token can be refreshed again
+        testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), userId);
+    }
+
+    @Test
+    public void offlineTokenDirectGrantFlowWithRefreshTokensRevoked() throws Exception {
+        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.setRevokeRefreshToken(true);
+            }
+
+        });
+
+        oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
+        oauth.clientId("offline-client");
+        OAuthClient.AccessTokenResponse tokenResponse = oauth.doGrantAccessTokenRequest("secret1", "test-user@localhost", "password");
+
+        AccessToken token = oauth.verifyToken(tokenResponse.getAccessToken());
+        String offlineTokenString = tokenResponse.getRefreshToken();
+        RefreshToken offlineToken = oauth.verifyRefreshToken(offlineTokenString);
+
+        events.expectLogin()
+                .client("offline-client")
+                .user(userId)
+                .session(token.getSessionState())
+                .detail(Details.RESPONSE_TYPE, "token")
+                .detail(Details.TOKEN_ID, token.getId())
+                .detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
+                .detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
+                .detail(Details.USERNAME, "test-user@localhost")
+                .removeDetail(Details.CODE_ID)
+                .removeDetail(Details.REDIRECT_URI)
+                .removeDetail(Details.CONSENT)
+                .assertEvent();
+
+        Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
+        Assert.assertEquals(0, offlineToken.getExpiration());
+
+        String offlineTokenString2 = testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), userId);
+        RefreshToken offlineToken2 = oauth.verifyRefreshToken(offlineTokenString2);
+
+        // Assert second refresh with same refresh token will fail
+        OAuthClient.AccessTokenResponse response = oauth.doRefreshTokenRequest(offlineTokenString, "secret1");
+        Assert.assertEquals(400, response.getStatusCode());
+        events.expectRefresh(offlineToken.getId(), token.getSessionState())
+                .client("offline-client")
+                .error(Errors.INVALID_TOKEN)
+                .user(userId)
+                .clearDetails()
+                .assertEvent();
+
+        // Refresh with new refreshToken is successful now
+        testRefreshWithOfflineToken(token, offlineToken2, offlineTokenString2, token.getSessionState(), userId);
+
+        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.setRevokeRefreshToken(false);
+            }
+
+        });
     }
 
     @Test
@@ -366,6 +450,54 @@ public class OfflineTokenTest {
     }
 
     @Test
+    public void offlineTokenAllowedWithCompositeRole() throws Exception {
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                ClientModel offlineClient = appRealm.getClientByClientId("offline-client");
+                UserModel testUser = session.users().getUserByUsername("test-user@localhost", appRealm);
+                RoleModel offlineAccess = appRealm.getRole(Constants.OFFLINE_ACCESS_ROLE);
+
+                // Test access
+                Assert.assertFalse(TokenManager.getAccess(null, true, offlineClient, testUser).contains(offlineAccess));
+                Assert.assertTrue(TokenManager.getAccess(OAuth2Constants.OFFLINE_ACCESS, true, offlineClient, testUser).contains(offlineAccess));
+
+                // Grant offline_access role indirectly through composite role
+                RoleModel composite = appRealm.addRole("composite");
+                composite.addCompositeRole(offlineAccess);
+
+                testUser.deleteRoleMapping(offlineAccess);
+                testUser.grantRole(composite);
+
+                // Test access
+                Assert.assertFalse(TokenManager.getAccess(null, true, offlineClient, testUser).contains(offlineAccess));
+                Assert.assertTrue(TokenManager.getAccess(OAuth2Constants.OFFLINE_ACCESS, true, offlineClient, testUser).contains(offlineAccess));
+            }
+
+        });
+
+        // Integration test
+        offlineTokenDirectGrantFlow();
+
+        // Revert changes
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                RoleModel composite = appRealm.getRole("composite");
+                RoleModel offlineAccess = appRealm.getRole(Constants.OFFLINE_ACCESS_ROLE);
+                UserModel testUser = session.users().getUserByUsername("test-user@localhost", appRealm);
+
+                testUser.deleteRoleMapping(composite);
+                appRealm.removeRole(composite);
+                testUser.grantRole(offlineAccess);
+            }
+
+        });
+    }
+
+    @Test
     public void testServlet() {
         OfflineTokenServlet.tokenInfo = null;
 
@@ -382,11 +514,11 @@ public class OfflineTokenTest {
         String accessTokenId = OfflineTokenServlet.tokenInfo.accessToken.getId();
         String refreshTokenId = OfflineTokenServlet.tokenInfo.refreshToken.getId();
 
-        // Assert access token will be refreshed, but offline token will be still the same
+        // Assert access token and offline token are refreshed
         Time.setOffset(9999);
         driver.navigate().to(offlineClientAppUri);
         Assert.assertTrue(driver.getCurrentUrl().startsWith(offlineClientAppUri));
-        Assert.assertEquals(OfflineTokenServlet.tokenInfo.refreshToken.getId(), refreshTokenId);
+        Assert.assertNotEquals(OfflineTokenServlet.tokenInfo.refreshToken.getId(), refreshTokenId);
         Assert.assertNotEquals(OfflineTokenServlet.tokenInfo.accessToken.getId(), accessTokenId);
 
         // Ensure that logout works for webapp (even if offline token will be still valid in Keycloak DB)
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java
index b725a16..232b102 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java
@@ -39,6 +39,9 @@ public class LoginTotpPage extends AbstractPage {
     @FindBy(css = "input[type=\"submit\"]")
     private WebElement submitButton;
 
+    @FindBy(id = "kc-cancel")
+    private WebElement cancelButton;
+
     @FindBy(className = "feedback-error")
     private WebElement loginErrorMessage;
 
@@ -49,6 +52,10 @@ public class LoginTotpPage extends AbstractPage {
         submitButton.click();
     }
 
+    public void cancel() {
+        cancelButton.click();
+    }
+
     public String getError() {
         return loginErrorMessage != null ? loginErrorMessage.getText() : null;
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java
index d5d7296..9cabdb4 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java
@@ -172,6 +172,14 @@ public class AccessTokenPerfTest {
             URI uri = null;
             Assert.assertEquals(302, response.getStatus());
             uri = response.getLocation();
+            if (response.getStatus() == 302) {
+                while (uri.toString().contains("login-actions/")) {
+                    response = client.target(uri).request().get();
+                    Assert.assertEquals(302, response.getStatus());
+                    uri = response.getLocation();
+                }
+            }
+
             for (String header : response.getHeaders().keySet()) {
                 for (Object value : response.getHeaders().get(header)) {
                     System.out.println(header + ": " + value);
diff --git a/testsuite/integration/src/test/resources/model/testrealm.json b/testsuite/integration/src/test/resources/model/testrealm.json
index 521ef83..53e7f7e 100755
--- a/testsuite/integration/src/test/resources/model/testrealm.json
+++ b/testsuite/integration/src/test/resources/model/testrealm.json
@@ -4,6 +4,7 @@
     "accessTokenLifespan": 6000,
     "accessCodeLifespan": 30,
     "accessCodeLifespanUserAction": 600,
+    "offlineSessionIdleTimeout": 3600000,
     "requiredCredentials": [ "password" ],
     "defaultRoles": [ "foo", "bar" ],
     "verifyEmail" : "true",
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java
old mode 100644
new mode 100755
index 95bff41..68edaa1
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java
@@ -91,7 +91,7 @@ public class ResetCredentialsTest extends AbstractAccountManagementTest {
         
         log.info("navigating to " + url);
         driver.navigate().to(url);
-        assertCurrentUrlStartsWith(testRealmResetCredentialsPage);
+        //assertCurrentUrlStartsWith(testRealmResetCredentialsPage);
         testRealmResetCredentialsPage.updatePassword("newPassword");
         assertCurrentUrlStartsWith(testRealmAccountManagementPage);
         testRealmAccountManagementPage.signOut();
diff --git a/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/Jetty8Test.java b/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/Jetty8Test.java
index a2e4b42..d158555 100755
--- a/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/Jetty8Test.java
+++ b/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/Jetty8Test.java
@@ -85,9 +85,11 @@ public class Jetty8Test {
 
     @AfterClass
     public static void shutdownJetty() throws Exception {
-        server.stop();
-        server.destroy();
-        Thread.sleep(1000);
+        try {
+            server.stop();
+            server.destroy();
+            Thread.sleep(100);
+        } catch (Exception e) {}
     }
 
     @Rule
diff --git a/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/JettySamlTest.java b/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/JettySamlTest.java
index 916d855..1506b0c 100755
--- a/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/JettySamlTest.java
+++ b/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/JettySamlTest.java
@@ -97,9 +97,11 @@ public class JettySamlTest {
 
     @AfterClass
     public static void shutdownJetty() throws Exception {
-        server.stop();
-        server.destroy();
-        Thread.sleep(1000);
+        try {
+            server.stop();
+            server.destroy();
+            Thread.sleep(100);
+        } catch (Exception e) {}
     }
 
     @Test
diff --git a/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/Jetty9Test.java b/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/Jetty9Test.java
index fcf75ca..402779b 100755
--- a/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/Jetty9Test.java
+++ b/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/Jetty9Test.java
@@ -85,9 +85,11 @@ public class Jetty9Test {
 
     @AfterClass
     public static void shutdownJetty() throws Exception {
-        server.stop();
-        server.destroy();
-        Thread.sleep(1000);
+        try {
+            server.stop();
+            server.destroy();
+            Thread.sleep(100);
+        } catch (Exception e) {}
     }
 
     @Rule
diff --git a/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/JettySamlTest.java b/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/JettySamlTest.java
index c6092b4..e71887e 100755
--- a/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/JettySamlTest.java
+++ b/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/JettySamlTest.java
@@ -96,9 +96,11 @@ public class JettySamlTest {
 
     @AfterClass
     public static void shutdownJetty() throws Exception {
-        server.stop();
-        server.destroy();
-        Thread.sleep(1000);
+        try {
+            server.stop();
+            server.destroy();
+            Thread.sleep(100);
+        } catch (Exception e) {}
     }
 
     @Test
diff --git a/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/Jetty9Test.java b/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/Jetty9Test.java
index 037769f..15ee763 100755
--- a/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/Jetty9Test.java
+++ b/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/Jetty9Test.java
@@ -85,8 +85,11 @@ public class Jetty9Test {
 
     @AfterClass
     public static void shutdownJetty() throws Exception {
-        server.stop();
-        server.destroy();
+        try {
+            server.stop();
+            server.destroy();
+            Thread.sleep(100);
+        } catch (Exception e) {}
     }
 
     @Rule
diff --git a/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/JettySamlTest.java b/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/JettySamlTest.java
index 96709ad..e71887e 100755
--- a/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/JettySamlTest.java
+++ b/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/JettySamlTest.java
@@ -96,8 +96,11 @@ public class JettySamlTest {
 
     @AfterClass
     public static void shutdownJetty() throws Exception {
-        server.stop();
-        server.destroy();
+        try {
+            server.stop();
+            server.destroy();
+            Thread.sleep(100);
+        } catch (Exception e) {}
     }
 
     @Test