keycloak-uncached

Changes

forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/MessageBean.java 60(+0 -60)

services/pom.xml 2(+2 -0)

Details

diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
index c67b195..8d57b2c 100755
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
@@ -143,7 +143,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
     ;
 
     protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) {
-        return UriBuilder.fromPath(getConfig().getAuthorizationUrl())
+        return UriBuilder.fromUri(getConfig().getAuthorizationUrl())
                 .queryParam(OAUTH2_PARAMETER_SCOPE, getConfig().getDefaultScope())
                 .queryParam(OAUTH2_PARAMETER_STATE, request.getState())
                 .queryParam(OAUTH2_PARAMETER_RESPONSE_TYPE, "code")
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml
index 54f7e08..9258db7 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml
@@ -168,11 +168,5 @@
             <column name="VALUE" type="VARCHAR(255)"/>
         </createTable>
         <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_ENABLED_EVENT_TYPES" constraintName="FK_H846O4H0W8EPX5NWEDRF5Y69J" referencedColumnNames="ID" referencedTableName="REALM"/>
-
-        <addColumn tableName="EVENT_ENTITY">
-            <column name="EVENT_GROUP" type="VARCHAR(255)"/>
-            <column name="REPRESENTATION" type="BLOB"/>
-        </addColumn>
-        
     </changeSet>
 </databaseChangeLog>
diff --git a/core/src/main/java/org/keycloak/RSATokenVerifier.java b/core/src/main/java/org/keycloak/RSATokenVerifier.java
index e2f5f2b..c68da0f 100755
--- a/core/src/main/java/org/keycloak/RSATokenVerifier.java
+++ b/core/src/main/java/org/keycloak/RSATokenVerifier.java
@@ -35,6 +35,9 @@ public class RSATokenVerifier {
         if (user == null) {
             throw new VerificationException("Token user was null.");
         }
+        if (realmUrl == null) {
+            throw new VerificationException("Realm URL is null. Make sure to add auth-server-url to the configuration of your adapter!");
+        }
         if (!realmUrl.equals(token.getIssuer())) {
             throw new VerificationException("Token audience doesn't match domain.");
 
diff --git a/core/src/main/java/org/keycloak/util/ObjectUtil.java b/core/src/main/java/org/keycloak/util/ObjectUtil.java
new file mode 100644
index 0000000..23a6bef
--- /dev/null
+++ b/core/src/main/java/org/keycloak/util/ObjectUtil.java
@@ -0,0 +1,27 @@
+package org.keycloak.util;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ObjectUtil {
+
+    private ObjectUtil() {}
+
+    /**
+     *
+     * @param str1
+     * @param str2
+     * @return true if both strings are null or equal
+     */
+    public static boolean isEqualOrNull(Object str1, Object str2) {
+        if (str1 == null && str2 == null) {
+            return true;
+        }
+
+        if ((str1 != null && str2 == null) || (str1 == null && str2 != null)) {
+            return false;
+        }
+
+        return str1.equals(str2);
+    }
+}
diff --git a/docbook/reference/en/en-US/modules/direct-access.xml b/docbook/reference/en/en-US/modules/direct-access.xml
index c08735b..39b0e5c 100755
--- a/docbook/reference/en/en-US/modules/direct-access.xml
+++ b/docbook/reference/en/en-US/modules/direct-access.xml
@@ -87,6 +87,7 @@ try {
 
    if (isPublic()) { // if client is public access type
        formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, "customer-portal"));
+       formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, "password"));
    } else {
        String authorization = BasicAuthHelper.createHeader("customer-portal", "secret-secret-secret");
        post.setHeader("Authorization", authorization);
diff --git a/docbook/reference/en/en-US/modules/providers.xml b/docbook/reference/en/en-US/modules/providers.xml
index f983947..eda5aca 100755
--- a/docbook/reference/en/en-US/modules/providers.xml
+++ b/docbook/reference/en/en-US/modules/providers.xml
@@ -99,7 +99,7 @@ public class MyEventListenerProvider implements EventListenerProvider {
                 script or manually create a folder inside KEYCLOAK_HOME/modules and add your jar and a <literal>module.xml</literal>.
                 For example to add the event listener sysout example provider using the jboss-cli script execute:
 <programlisting><![CDATA[{
-    KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-sysout --resources=event-listener-sysout-example.jar"
+    KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-sysout --resources=target/event-listener-sysout-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-model-api,org.keycloak.keycloak-events-api"
 }]]></programlisting>
                 Or to manually create it start by creating the folder <literal>KEYCLOAK_HOME/modules/org/keycloak/examples/event-sysout/main</literal>.
                 Then copy <literal>event-listener-sysout-example.jar</literal> to this folder and create <literal>module.xml</literal>
diff --git a/docbook/reference/en/en-US/modules/server-installation.xml b/docbook/reference/en/en-US/modules/server-installation.xml
index 84d7796..9667a7b 100755
--- a/docbook/reference/en/en-US/modules/server-installation.xml
+++ b/docbook/reference/en/en-US/modules/server-installation.xml
@@ -780,175 +780,50 @@ All configuration options are optional. Default value for directory is <literal>
         </section>
 
     </section>
+
     <section>
-        <title>Configuring Servers from the Subsystem</title>
+        <title>Adding Keycloak server in Domain Mode</title>
         <para>
-            If you are using WildFly or EAP,he Keycloak server is deployed and configured from the Keycloak subsystem.  This makes provisioning simpler in a domain environment.
-            It also allows you to create more than one Keycloak server instance inside a single WildFly instance.  And, you can upload providers, themes, and
-            server configurations without disturbing Keycloak's auth-server.war.
+            In domain mode, you start the server with the "domain" command instead of the "standalone" command.  In this case, the Keycloak subsystem is
+            defined in domain/configuration/domain.xml instead of standalone/configuration.standalone.xml.  Inside domain.xml, you will see more than one
+            profile.  A Keycloak subsystem can be defined in zero or more of those profiles.
         </para>
-        <section>
-            <title>Manually Creating A Server</title>
-            <para>
-                A Keycloak server can be declared by editing standalone.xml or domain.xml.
-            </para>
-<para>
+        <para>
+            To enable Keycloak for a server profile edit domain/configuration/domain.xml. To the <literal>extensions</literal>
+            element add the Keycloak extension:
 <programlisting><![CDATA[
-<server xmlns="urn:jboss:domain:1.4">
-
-  <profile>
+<extensions>
+    ...
+    <extension module="org.keycloak.keycloak-subsystem"/>
+</extensions>
+]]></programlisting>
+            Then you need to add the server to the required server profiles. By default WildFly starts two servers
+            in the main-server-group which uses the full profile. To add Keycloak for this profile add the Keycloak
+            subsystem to the <literal>profile</literal> element with <literal>name</literal> full:
+<programlisting><![CDATA[
+<profile name="full">
+    ...
     <subsystem xmlns="urn:jboss:domain:keycloak:1.0">
-        <auth-server name="keycloak-1">
+        <auth-server name="main-auth-server">
             <enabled>true</enabled>
             <web-context>auth</web-context>
         </auth-server>
-        <auth-server name="keyclaok-2">
-            <enabled>false</enabled>
-            <web-context>auth2</web-context>
-        </auth-server>
     </subsystem>
-  </profile>
-]]>
-</programlisting>
-</para>
-            <warning>
-                <para>
-                    If you create more than one Keycloak server, you will need to use CLI to fully configure each instance.  At the least, 
-                    you will need to run the <link linkend="uploading-extra-config">update-server-config</link> operation.
-                </para>
-            </warning>
-        </section>
-        <section>
-            <title>Using CLI and CLI GUI with the Keycloak Subsystem</title>
-            <para>
-                Servers can also be added/removed or enabled/disabled at runtime using the <ulink url="https://developer.jboss.org/wiki/CommandLineInterface">CLI</ulink> or 
-                <ulink url="https://developer.jboss.org/wiki/AGUIForTheCommandLineInterface">CLI GUI</ulink> tool.  These are tools that ship with WildFly/EAP and also with
-                the Keycloak Appliance installation.  See <ulink url="https://developer.jboss.org/wiki/CommandLineInterface">CLI</ulink> or 
-                <ulink url="https://developer.jboss.org/wiki/AGUIForTheCommandLineInterface">CLI GUI</ulink> documentation to learn more about how to start the tools,
-                issue commands, and create CLI scripts.
-            </para>
-<para>
-To start CLI with the Keycloak Appliance install:
-<programlisting><![CDATA[
-cd <APPLIANCE_INSTALL_DIR>/keycloak/bin
-./jboss-cli.sh --gui
-or
-./jboss.cli.bat --gui]]>
-</programlisting>
-<note>Your server must be running to start in --gui mode.</note>
-</para>  
-            <section>
-                <title>Basic CLI Commands</title>
-<para>
-Command to add a server in CLI:
-<programlisting><![CDATA[
-/subsystem=keycloak/auth-server=my-auth-server/:add(web-context=my-auth, enabled=true)]]>
-</programlisting>
-Because "enabled=true", a new Keycloak server will be immediately deployed.  By default "enabled" is set to false.
-</para>  
-<para>
-Command to remove a server in CLI:
-<programlisting><![CDATA[
-/subsystem=keycloak/auth-server=my-auth-server/:remove]]>
-</programlisting>
-The Keycloak server will be immediately deleted and undeployed.
-</para>  
-<para>
-Command to enable or disable a server in CLI:
-<programlisting><![CDATA[
-/subsystem=keycloak/auth-server=foo/:write-attribute(name=enabled,value=true)]]>
-</programlisting>
-The Keycloak server will be immediately deployed or undeployed, but not deleted.
-</para>
-            </section>
-            <section id="uploading-extra-config">
-                <title>Uploading extra configuration using CLI</title>
-                <para>
-                    The Keycloak subsystem allows you to upload keycloak-server.json, provider jars, and theme jars to a Keycloak server instance.  The
-                    CLI operations for this are "update-server-config" and "add-provider".  You may use CLI, CLI GUI, or CLI scripts for these operations.  The following
-                    examples are shown using <ulink url="https://developer.jboss.org/wiki/AGUIForTheCommandLineInterface">CLI GUI</ulink> for clarity.
-                </para>
-                <para>
-                    To use a new keycloak-server.json file for your server, find your server under the Keycloak subsystem.  Then right-click the server,
-                    select "update-server-config", and upload your file.
-                </para>
-            
-                <para>
-                    <imagedata fileref="images/update-server-config-select.png"/>
-                </para>
-                <para>
-                    <imagedata fileref="images/update-server-config-dialog.png"/>
-                </para>
-            
-                <warning>
-                    <para>
-                    If you use the update-server-config operation, you should delete or rename &lt;WILDFLY_HOME&gt;/standalone/configuration/keycloak-server.json.
-                    Otherwise, all Keycloak server instances will use this file instead of your uploaded file.
-                    </para>
-                </warning>
-                <para>
-                    To upload a new provider jar or theme jar to your server, find your server under the Keycloak subsystem.  Then right-click the server,
-                    select "add-provider", and upload your file.
-                </para>
-                <para>
-                    <imagedata fileref="images/add-provider-select.png"/>
-                </para>
-                <para>
-                    <imagedata fileref="images/add-provider-dialog.png"/>
-                </para>
-            </section>
-            <section>
-                <title>Working with overlays</title>
-                <para>
-                    When you upload a provider jar, theme jar, or keycloak-server.json file, you are creating an overlay.  That is, the file is "overlayed"
-                    onto the Keycloak server at deploy time.  There are two additional operations that help you manage these overlays.  They are "list-overlays" and
-                    "remove-overlay".  Here are CLI examples of these operations.
-                </para>
-<para>
-    <programlisting>
-/subsystem=keycloak/auth-server=my-auth-server/:list-overlays
-{
-    "outcome" => "success",
-    "result" => [
-        "/WEB-INF/classes/META-INF/keycloak-server.json",
-        "/WEB-INF/lib/federation-properties-example.jar"
-    ],
-}</programlisting>
-    <programlisting>
-/subsystem=keycloak/auth-server=my-auth-server/:remove-overlay(overlay-file-path=/WEB-INF/lib/federation-properties-example.jar,redeploy=true)
-{
-    "outcome" => "success",
-}</programlisting>
-</para>
-                <para>
-                    <note>
-                        Notice in the "list-overlays" operation, the full path to the server config is 
-                        /WEB-INF/classes/META-INF/keycloak-server.json.  This is always the uploaded path for an "update-server-config" operation.
-                        If you remove this overlay, the Keycloak server will revert to its default keycloak-server.json.  If you have a 
-                        keycloak-server.json file in your &lt;WILDFLY_HOME&gt;/standalone/configuration directory, it will always take precedence
-                        over both the default and the overlay.
-                    </note>
-                </para>
-            </section>
-        </section>
-        <section>
-            <title>Adding a Keycloak server in Domain Mode</title>
-            <para>
-                In domain mode, you start the server with the "domain" command instead of the "standalone" command.  In this case, the Keycloak subsystem is
-                defined in domain/configuration/domain.xml instead of standalone/configuration.standalone.xml.  Inside domain.xml, you will see more than one
-                profile.  A Keycloak subsystem can be defined in zero or more of those profiles.
-            </para>
-            <para>
-                In the example below, a Keycloak server named "foo" is defined in the "full" profile.  The "full" profile is assigned to the "main-server-group".
-                Every WildFly instance that belongs to "main-server-group" will get an identically configured deployment of the "foo" Keycloak server.
-            </para>
-            <para>
-                All operations discussed earlier are valid for a Keycloak server in a domain.  You can enable/disable, upload new keyclaok-server.json, and add provider jars.
-                In the following example, any changes that are made to the "foo" server will be automatically propogated to every instance in "main-server-group".
-            </para>
-            <para>
-                <imagedata fileref="images/domain-mode.png"/>
-            </para>
-        </section>
+]]></programlisting>
+        </para>
+        <para>
+            To configure the server copy <literal>standalone/configuration/keycloak-server.json</literal> to
+            <literal>domain/servers/&lt;SERVER NAME&gt;/configuration</literal>. The configuration should be identical
+            for all servers in a group.
+        </para>
+        <para>
+            Follow the <link linkend='clustering'>Clustering</link> section of the documentation to configure Keycloak
+            for clustering. In domain mode it doesn't make much sense to not configure Keycloak in cluster mode.
+        </para>
+        <para>
+            To deploy custom providers and themes you should deploys these as modules and make sure the modules are
+            available to all servers in the group. See <link linkend='providers'>Providers</link> and
+            <link linkend='themes'>Themes</link> sections for more information on how to do this.
+        </para>
     </section>
 </chapter>
diff --git a/examples/broker/facebook-authentication/facebook-identity-provider-realm.json b/examples/broker/facebook-authentication/facebook-identity-provider-realm.json
index 2c90dad..32a3704 100644
--- a/examples/broker/facebook-authentication/facebook-identity-provider-realm.json
+++ b/examples/broker/facebook-authentication/facebook-identity-provider-realm.json
@@ -53,9 +53,8 @@
     ],
     "identityProviders": [
         {
-          "id" : "facebook",
+          "alias" : "facebook",
           "providerId" : "facebook",
-          "name" : "Facebook",
           "enabled": true,
           "updateProfileFirstLogin" : "true",
           "storeToken" : "true",
diff --git a/examples/broker/facebook-authentication/README.md b/examples/broker/facebook-authentication/README.md
index 3fe44c4..d7d10bd 100644
--- a/examples/broker/facebook-authentication/README.md
+++ b/examples/broker/facebook-authentication/README.md
@@ -33,7 +33,7 @@ documentation.
 
 Please take a look on [Facebook Developer Console](https://developers.facebook.com/apps/) for more details. Make sure to use the correct
 redirect URI to be used as URL on Facebook. The facebook will redirect to this URI after finish authentication. For this example, it's the URL
-[http://localhost:8080/auth/realms/facebook-identity-provider-realm/broker/facebook](http://localhost:8080/auth/realms/facebook-identity-provider-realm/broker/facebook) .
+[http://localhost:8080/auth/realms/facebook-identity-provider-realm/broker/facebook/endpoint](http://localhost:8080/auth/realms/facebook-identity-provider-realm/broker/facebook/endpoint) .
 You can also determine this redirect URI from Keycloak admin console (It's in Identity provider settings for Facebook provider).
 
 Once you have a Facebook Application configured, you need to obtain both **App ID** and **App Secret** and update the
diff --git a/examples/broker/google-authentication/google-identity-provider-realm.json b/examples/broker/google-authentication/google-identity-provider-realm.json
index f808658..8b4deef 100644
--- a/examples/broker/google-authentication/google-identity-provider-realm.json
+++ b/examples/broker/google-authentication/google-identity-provider-realm.json
@@ -53,9 +53,8 @@
     ],
     "identityProviders": [
         {
-          "id" : "google",
+          "alias" : "google",
           "providerId" : "google",
-          "name" : "Google",
           "enabled": true,
           "updateProfileFirstLogin" : "true",
           "storeToken" : "true",
diff --git a/examples/broker/google-authentication/README.md b/examples/broker/google-authentication/README.md
index 5d60d11..60c21ac 100644
--- a/examples/broker/google-authentication/README.md
+++ b/examples/broker/google-authentication/README.md
@@ -54,7 +54,7 @@ Once you have a Google Application configured, you need to obtain both **Client 
 Please, update both *clientId* and *clientSecret* configuration options with the **Client ID** and **Client Secret**.
 
 Make sure to use the correct redirect URI to be used as URL on Google. The Google will redirect to this URI after finish authentication. For this example, it's the URL
-[http://localhost:8080/auth/realms/google-identity-provider-realm/broker/google](http://localhost:8080/auth/realms/google-identity-provider-realm/broker/google) .
+[http://localhost:8080/auth/realms/google-identity-provider-realm/broker/google/endpoint](http://localhost:8080/auth/realms/google-identity-provider-realm/broker/google/endpoint) .
 You can also determine the redirect URI from Keycloak admin console (It's in Identity provider settings for Google provider).
 
 Make sure you've set up the Keycloak Server
diff --git a/examples/broker/saml-broker-authentication/README.md b/examples/broker/saml-broker-authentication/README.md
index f72c3cd..5525bac 100644
--- a/examples/broker/saml-broker-authentication/README.md
+++ b/examples/broker/saml-broker-authentication/README.md
@@ -5,7 +5,7 @@ What is it?
 
 This example demonstrates how to broker a SAML Identity Provider in KeyCloak. In this case, the SAML Identity Provider
 belongs to a different realm than the application and we want to trust users from one realm to authenticate and access the
-applications in aonther realm.
+applications in another realm.
 
 There are two main realms in this example:
 
diff --git a/examples/broker/saml-broker-authentication/saml-broker-authentication-realm.json b/examples/broker/saml-broker-authentication/saml-broker-authentication-realm.json
index 3369daf..f8fbc8a 100644
--- a/examples/broker/saml-broker-authentication/saml-broker-authentication-realm.json
+++ b/examples/broker/saml-broker-authentication/saml-broker-authentication-realm.json
@@ -47,9 +47,8 @@
     ],
     "identityProviders": [
         {
-          "id" : "saml-identity-provider",
+          "alias" : "saml-identity-provider",
           "providerId" : "saml",
-          "name" : "SAML v2 Identity Provider",
           "enabled": true,
           "updateProfileFirstLogin" : "true",
           "storeToken" : "true",
diff --git a/examples/broker/saml-broker-authentication/saml-broker-realm.json b/examples/broker/saml-broker-authentication/saml-broker-realm.json
index 016b843..7f1cde0 100644
--- a/examples/broker/saml-broker-authentication/saml-broker-realm.json
+++ b/examples/broker/saml-broker-authentication/saml-broker-realm.json
@@ -28,10 +28,11 @@
     },
     "applications": [
         {
-            "name": "http://localhost:8080/auth/",
+            "name": "http://localhost:8080/auth/realms/saml-broker-authentication-realm",
+            "protocol": "saml",
             "enabled": true,
             "redirectUris": [
-              "http://localhost:8080/auth/realms/saml-broker-authentication-realm/broker/saml-identity-provider"
+              "http://localhost:8080/auth/realms/saml-broker-authentication-realm/broker/saml-identity-provider/endpoint"
             ],
           "attributes": {
             "saml.assertion.signature": "true",
diff --git a/examples/broker/twitter-authentication/src/main/java/org/keycloak/examples/broker/twitter/TwitterShowUserServlet.java b/examples/broker/twitter-authentication/src/main/java/org/keycloak/examples/broker/twitter/TwitterShowUserServlet.java
index e00ec56..e8e7ba2 100644
--- a/examples/broker/twitter-authentication/src/main/java/org/keycloak/examples/broker/twitter/TwitterShowUserServlet.java
+++ b/examples/broker/twitter-authentication/src/main/java/org/keycloak/examples/broker/twitter/TwitterShowUserServlet.java
@@ -119,7 +119,7 @@ public class TwitterShowUserServlet extends HttpServlet {
     }
 
     private String getIdentityProviderTokenUrl() {
-        return this.authServer + "/realms/" + this.realmName + "/broker/" + this.identityProvider.getId() + "/token";
+        return this.authServer + "/realms/" + this.realmName + "/broker/" + this.identityProvider.getAlias() + "/token";
     }
 
     private void initKeyCloakClient(ServletConfig config) {
diff --git a/examples/broker/twitter-authentication/twitter-identity-provider-realm.json b/examples/broker/twitter-authentication/twitter-identity-provider-realm.json
index 27850eb..4b70904 100644
--- a/examples/broker/twitter-authentication/twitter-identity-provider-realm.json
+++ b/examples/broker/twitter-authentication/twitter-identity-provider-realm.json
@@ -64,9 +64,8 @@
     ],
     "identityProviders": [
         {
-          "id" : "twitter",
+          "alias" : "twitter",
           "providerId" : "twitter",
-          "name" : "Twitter",
           "enabled": true,
           "updateProfileFirstLogin" : "true",
           "storeToken" : "true",
diff --git a/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json b/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json
index e93b148..265049f 100755
--- a/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json
@@ -2,6 +2,7 @@
   "realm" : "cors",
   "resource" : "cors-database-service",
   "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+  "auth-server-url": "http://localhost-auth:8080/auth",
   "bearer-only" : true,
   "ssl-required": "external",
   "enable-cors": true
diff --git a/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java b/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java
index 8f6c951..8b3d40c 100755
--- a/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java
+++ b/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java
@@ -80,6 +80,7 @@ public class AdminClient {
             List <NameValuePair> formparams = new ArrayList <NameValuePair>();
             formparams.add(new BasicNameValuePair("username", "admin"));
             formparams.add(new BasicNameValuePair("password", "password"));
+            formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, "password"));
             formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, "admin-client"));
             UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
             post.setEntity(form);
diff --git a/examples/demo-template/customer-app-js/src/main/webapp/customers/view.html b/examples/demo-template/customer-app-js/src/main/webapp/customers/view.html
index 85a3354..685a3c4 100755
--- a/examples/demo-template/customer-app-js/src/main/webapp/customers/view.html
+++ b/examples/demo-template/customer-app-js/src/main/webapp/customers/view.html
@@ -26,11 +26,11 @@ User <b id="subject"></b> made this request.
 
         if (keycloak.idToken) {
             document.getElementById('profileType').innerHTML = 'IDToken';
-            document.getElementById('username').innerHTML = keycloak.idToken.preferred_username;
-            document.getElementById('email').innerHTML = keycloak.idToken.email;
-            document.getElementById('name').innerHTML = keycloak.idToken.name;
-            document.getElementById('givenName').innerHTML = keycloak.idToken.given_name;
-            document.getElementById('familyName').innerHTML = keycloak.idToken.family_name;
+            document.getElementById('username').innerHTML = keycloak.idTokenParsed.preferred_username;
+            document.getElementById('email').innerHTML = keycloak.idTokenParsed.email;
+            document.getElementById('name').innerHTML = keycloak.idTokenParsed.name;
+            document.getElementById('givenName').innerHTML = keycloak.idTokenParsed.given_name;
+            document.getElementById('familyName').innerHTML = keycloak.idTokenParsed.family_name;
         } else {
             keycloak.loadUserProfile(function() {
                 document.getElementById('profileType').innerHTML = 'Account Service';
diff --git a/examples/providers/event-listener-sysout/README.md b/examples/providers/event-listener-sysout/README.md
index e3cef35..0306af0 100644
--- a/examples/providers/event-listener-sysout/README.md
+++ b/examples/providers/event-listener-sysout/README.md
@@ -1,7 +1,17 @@
 Example Event Listener that prints events to System.out
 =======================================================
 
-To deploy copy target/event-listener-sysout-example.jar to standalone/configuration/providers. 
+To deploy copy target/event-listener-sysout-example.jar to standalone/configuration/providers. Alternatively you can deploy as a module by running:
+
+    KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-sysout --resources=target/event-listener-sysout-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-model-api,org.keycloak.keycloak-events-api"
+
+Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
+
+    "providers": [
+        ....
+        "module:org.keycloak.examples.event-sysout"
+    ],
+
 Then start (or restart) the server. Once started open the admin console, select your realm, then click on Events, 
 followed by config. Click on Listeners select box, then pick sysout from the dropdown. After this try to logout and 
 login again to see events printed to System.out.
diff --git a/examples/providers/event-store-mem/README.md b/examples/providers/event-store-mem/README.md
index c1762c0..c55a464 100644
--- a/examples/providers/event-store-mem/README.md
+++ b/examples/providers/event-store-mem/README.md
@@ -1,7 +1,16 @@
 Example Event Store that stores events in memory
 ================================================
 
-To deploy copy target/event-store-mem-example.jar to standalone/configuration/providers. 
+To deploy copy target/event-store-mem-example.jar to standalone/configuration/providers. Alternatively you can deploy as a module by running:
+
+    KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-inmem --resources=target/event-store-mem-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-model-api,org.keycloak.keycloak-events-api"
+
+Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
+
+    "providers": [
+        ....
+        "module:org.keycloak.examples.event-inmem"
+    ],
 
 Then edit standalone/configuration/keycloak-server.json, change:
 
diff --git a/examples/providers/event-store-mem/src/main/java/org/keycloak/examples/providers/events/MemEventStoreProvider.java b/examples/providers/event-store-mem/src/main/java/org/keycloak/examples/providers/events/MemEventStoreProvider.java
index d980af3..592455c 100755
--- a/examples/providers/event-store-mem/src/main/java/org/keycloak/examples/providers/events/MemEventStoreProvider.java
+++ b/examples/providers/event-store-mem/src/main/java/org/keycloak/examples/providers/events/MemEventStoreProvider.java
@@ -59,7 +59,7 @@ public class MemEventStoreProvider implements EventStoreProvider {
 
     @Override
     public void onEvent(Event event) {
-        if (!excludedEvents.contains(event.getType())) {
+        if (excludedEvents == null || !excludedEvents.contains(event.getType())) {
             events.add(0, event);
         }
     }
diff --git a/examples/providers/federation-provider/README.md b/examples/providers/federation-provider/README.md
index 9a25a2e..e65a778 100755
--- a/examples/providers/federation-provider/README.md
+++ b/examples/providers/federation-provider/README.md
@@ -2,7 +2,18 @@ Example User Federation Provider
 ===================================================
 
 This is an example of user federation backed by a simple properties file.  This properties file only contains username/password
-key pairs.  To deploy, build this directory then take the jar and copy it to standalone/configuration/providers.  
+key pairs.  To deploy, build this directory then take the jar and copy it to standalone/configuration/providers. Alternatively you can deploy as a module by running:
+
+    KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.userprops --resources=target/federation-properties-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-model-api"
+
+Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
+
+    "providers": [
+        ....
+        "module:org.keycloak.examples.userprops"
+    ],
+
+  
 You will then have to restart the authentication server.
 
 The ClasspathPropertiesFederationProvider is an example of a readonly provider.  If you go to the Users/Federation
@@ -13,4 +24,4 @@ a "test-users.properties" within the JAR that you can use as the variable.
 The FilePropertiesFederationProvider is an example of a writable provider.  It synchronizes changes made to
 username and password with the properties file.  If you go to the Users/Federation page of the admin console you will 
 see this provider listed under "file-properties".  To configure this provider you specify a fully qualified file path to 
-a properties file in the "path" field of the admin page for this plugin.  
\ No newline at end of file
+a properties file in the "path" field of the admin page for this plugin.  
diff --git a/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java b/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java
index bc6920b..6e5511e 100755
--- a/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java
+++ b/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java
@@ -1,17 +1,18 @@
 package org.keycloak.account;
 
-import org.apache.http.client.methods.HttpHead;
-import org.keycloak.events.Event;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserSessionModel;
-import org.keycloak.provider.Provider;
+import java.util.List;
 
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
-import java.util.List;
+
+import org.keycloak.events.Event;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.FormMessage;
+import org.keycloak.provider.Provider;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -26,6 +27,8 @@ public interface AccountProvider extends Provider {
 
     AccountProvider setError(String message, Object ... parameters);
 
+    AccountProvider setErrors(List<FormMessage> messages);
+
     AccountProvider setSuccess(String message, Object ... parameters);
 
     AccountProvider setWarning(String message, Object ... parameters);
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
index 4e0c007..cb3949a 100755
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
@@ -1,25 +1,54 @@
 package org.keycloak.account.freemarker;
 
+import java.io.IOException;
+import java.net.URI;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
 import org.jboss.logging.Logger;
 import org.keycloak.account.AccountPages;
 import org.keycloak.account.AccountProvider;
-import org.keycloak.account.freemarker.model.*;
+import org.keycloak.account.freemarker.model.AccountBean;
+import org.keycloak.account.freemarker.model.AccountFederatedIdentityBean;
+import org.keycloak.account.freemarker.model.FeaturesBean;
+import org.keycloak.account.freemarker.model.LogBean;
+import org.keycloak.account.freemarker.model.PasswordBean;
+import org.keycloak.account.freemarker.model.RealmBean;
+import org.keycloak.account.freemarker.model.ReferrerBean;
+import org.keycloak.account.freemarker.model.SessionsBean;
+import org.keycloak.account.freemarker.model.TotpBean;
+import org.keycloak.account.freemarker.model.UrlBean;
 import org.keycloak.events.Event;
-import org.keycloak.freemarker.*;
+import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
+import org.keycloak.freemarker.FreeMarkerException;
+import org.keycloak.freemarker.FreeMarkerUtil;
+import org.keycloak.freemarker.LocaleHelper;
+import org.keycloak.freemarker.Theme;
+import org.keycloak.freemarker.ThemeProvider;
+import org.keycloak.freemarker.beans.LocaleBean;
+import org.keycloak.freemarker.beans.MessageBean;
 import org.keycloak.freemarker.beans.MessageFormatterMethod;
+import org.keycloak.freemarker.beans.MessageType;
+import org.keycloak.freemarker.beans.MessagesPerFieldBean;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.FormMessage;
 import org.keycloak.services.resources.flows.Urls;
 
-import javax.ws.rs.core.*;
-import java.io.IOException;
-import java.net.URI;
-import java.text.MessageFormat;
-import java.util.*;
-import org.keycloak.freemarker.beans.LocaleBean;
-
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -43,13 +72,10 @@ public class FreeMarkerAccountProvider implements AccountProvider {
     private FreeMarkerUtil freeMarker;
     private HttpHeaders headers;
 
-    public static enum MessageType {SUCCESS, WARNING, ERROR}
-
     private UriInfo uriInfo;
 
-    private String message;
-    private Object[] parameters;
-    private MessageType messageType;
+    private List<FormMessage> messages = null;
+    private MessageType messageType = MessageType.ERROR;
 
     public FreeMarkerAccountProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
         this.session = session;
@@ -87,13 +113,13 @@ public class FreeMarkerAccountProvider implements AccountProvider {
         }
 
         Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, headers);
-        Properties messages;
+        Properties messagesBundle;
         try {
-            messages = theme.getMessages(locale);
-            attributes.put("msg", new MessageFormatterMethod(locale, messages));
+            messagesBundle = theme.getMessages(locale);
+            attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
         } catch (IOException e) {
             logger.warn("Failed to load messages", e);
-            messages = new Properties();
+            messagesBundle = new Properties();
         }
 
         URI baseUri = uriInfo.getBaseUri();
@@ -107,15 +133,19 @@ public class FreeMarkerAccountProvider implements AccountProvider {
             attributes.put("stateChecker", stateChecker);
         }
 
-        if (message != null) {
-            String formattedMessage;
-            if(messages.containsKey(message)){
-                formattedMessage = new MessageFormat(messages.getProperty(message),locale).format(parameters);
-            }else{
-                formattedMessage = message;
+        MessagesPerFieldBean messagesPerField = new MessagesPerFieldBean();
+        if (messages != null) {
+            MessageBean wholeMessage = new MessageBean(null, messageType);
+            for (FormMessage message : this.messages) {
+                String formattedMessageText = formatMessage(message, messagesBundle, locale);
+                if (formattedMessageText != null) {
+                    wholeMessage.appendSummaryLine(formattedMessageText);
+                    messagesPerField.addMessage(message.getField(), formattedMessageText, messageType);
+                }
             }
-            attributes.put("message", new MessageBean(formattedMessage, messageType));
+            attributes.put("message", wholeMessage);
         }
+        attributes.put("messagesPerField", messagesPerField);
 
         if (referrer != null) {
             attributes.put("referrer", new ReferrerBean(referrer));
@@ -134,7 +164,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
                     b = UriBuilder.fromUri(baseQueryUri).path(uriInfo.getPath());
                     break;
             }
-            attributes.put("locale", new LocaleBean(realm, locale, b, messages));
+            attributes.put("locale", new LocaleBean(realm, locale, b, messagesBundle));
         }
 
         attributes.put("features", new FeaturesBean(identityProviderEnabled, eventsEnabled, passwordUpdateSupported));
@@ -173,28 +203,46 @@ public class FreeMarkerAccountProvider implements AccountProvider {
         this.passwordSet = passwordSet;
         return this;
     }
+    
+    protected void setMessage(MessageType type, String message, Object... parameters) {
+        messageType = type;
+        messages = new ArrayList<>();
+        messages.add(new FormMessage(null, message, parameters));
+    }
 
+    protected String formatMessage(FormMessage message, Properties messagesBundle, Locale locale) {
+        if (message == null)
+            return null;
+        if (messagesBundle.containsKey(message.getMessage())) {
+	    return new MessageFormat(messagesBundle.getProperty(message.getMessage()), locale).format(message.getParameters());
+        } else {
+            return message.getMessage();
+        }
+    }
+  
     @Override
-    public AccountProvider setError(String message, Object ... parameters) {
-        this.message = message;
-        this.parameters = parameters;
+    public AccountProvider setErrors(List<FormMessage> messages) {
         this.messageType = MessageType.ERROR;
+        this.messages = new ArrayList<>(messages);
+        return this;
+    }
+
+
+    @Override
+    public AccountProvider setError(String message, Object ... parameters) {
+        setMessage(MessageType.ERROR, message, parameters);
         return this;
     }
 
     @Override
     public AccountProvider setSuccess(String message, Object ... parameters) {
-        this.message = message;
-        this.parameters = parameters;
-        this.messageType = MessageType.SUCCESS;
+        setMessage(MessageType.SUCCESS, message, parameters);
         return this;
     }
 
     @Override
     public AccountProvider setWarning(String message, Object ... parameters) {
-        this.message = message;
-        this.parameters = parameters;
-        this.messageType = MessageType.WARNING;
+        setMessage(MessageType.WARNING, message, parameters);
         return this;
     }
 
diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/MessagesPerFieldBean.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/MessagesPerFieldBean.java
new file mode 100644
index 0000000..5858b07
--- /dev/null
+++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/MessagesPerFieldBean.java
@@ -0,0 +1,73 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @authors tag. All rights reserved.
+ */
+package org.keycloak.freemarker.beans;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Bean used to hold form messages per field. Stored under <code>messagesPerField</code> key in Freemarker context.
+ * 
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public class MessagesPerFieldBean {
+
+    private Map<String, MessageBean> messagesPerField = new HashMap<String, MessageBean>();
+
+    public void addMessage(String field, String messageText, MessageType messageType) {
+        if (messageText == null || messageText.trim().isEmpty())
+            return;
+        if (field == null)
+            field = "global";
+
+        MessageBean fm = messagesPerField.get(field);
+        if (fm == null) {
+            messagesPerField.put(field, new MessageBean(messageText, messageType));
+        } else {
+            fm.appendSummaryLine(messageText);
+        }
+    }
+
+    /**
+     * Check if message for given field exists
+     * 
+     * @param field
+     * @return
+     */
+    public boolean exists(String field) {
+        return messagesPerField.containsKey(field);
+    }
+
+    /**
+     * Get message for given field.
+     * 
+     * @param fieldName
+     * @return message text or empty string
+     */
+    public String get(String fieldName) {
+        MessageBean mb = messagesPerField.get(fieldName);
+        if (mb != null) {
+            return mb.getSummary();
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * Print text if message for given field exists. Useful eg. to add css styles for fields with message.
+     * 
+     * @param fieldName to check for
+     * @param text to print
+     * @return text if message exists for given field, else empty string
+     */
+    public String printIfExists(String fieldName, String text) {
+        if (exists(fieldName))
+            return text;
+        else
+            return "";
+    }
+
+}
diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/MessageType.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/MessageType.java
new file mode 100644
index 0000000..12ec23c
--- /dev/null
+++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/MessageType.java
@@ -0,0 +1,17 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @authors tag. All rights reserved.
+ */
+package org.keycloak.freemarker.beans;
+
+/**
+ * Enum with types of messages.
+ *
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public enum MessageType {
+
+    SUCCESS, WARNING, ERROR
+
+}
diff --git a/forms/common-themes/src/main/resources/theme/base/account/account.ftl b/forms/common-themes/src/main/resources/theme/base/account/account.ftl
index a41d769..7c349df 100755
--- a/forms/common-themes/src/main/resources/theme/base/account/account.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/account/account.ftl
@@ -14,7 +14,7 @@
 
         <input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
 
-        <div class="form-group">
+        <div class="form-group ${messagesPerField.printIfExists('username','has-error')}">
             <div class="col-sm-2 col-md-2">
                 <label for="username" class="control-label">${msg("username")}</label>
             </div>
@@ -24,7 +24,7 @@
             </div>
         </div>
 
-        <div class="form-group">
+        <div class="form-group ${messagesPerField.printIfExists('email','has-error')}">
             <div class="col-sm-2 col-md-2">
             <label for="email" class="control-label">${msg("email")}</label> <span class="required">*</span>
             </div>
@@ -34,7 +34,7 @@
             </div>
         </div>
 
-        <div class="form-group">
+        <div class="form-group ${messagesPerField.printIfExists('firstName','has-error')}">
             <div class="col-sm-2 col-md-2">
                 <label for="firstName" class="control-label">${msg("firstName")}</label> <span class="required">*</span>
             </div>
@@ -44,7 +44,7 @@
             </div>
         </div>
 
-        <div class="form-group">
+        <div class="form-group ${messagesPerField.printIfExists('lastName','has-error')}">
             <div class="col-sm-2 col-md-2">
                 <label for="lastName" class="control-label">${msg("lastName")}</label> <span class="required">*</span>
             </div>
diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl
index 56b5cbe..a534073 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl
@@ -6,7 +6,7 @@
         ${msg("loginProfileTitle")}
     <#elseif section = "form">
         <form id="kc-update-profile-form" class="${properties.kcFormClass!}" action="${url.loginUpdateProfileUrl}" method="post">
-            <div class="${properties.kcFormGroupClass!}">
+            <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('email',properties.kcFormGroupErrorClass!)}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
                 </div>
@@ -15,7 +15,7 @@
                 </div>
             </div>
 
-            <div class="${properties.kcFormGroupClass!}">
+            <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('firstName',properties.kcFormGroupErrorClass!)}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
                 </div>
@@ -24,7 +24,7 @@
                 </div>
             </div>
 
-            <div class="${properties.kcFormGroupClass!}">
+            <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('lastName',properties.kcFormGroupErrorClass!)}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
                 </div>
diff --git a/forms/common-themes/src/main/resources/theme/base/login/register.ftl b/forms/common-themes/src/main/resources/theme/base/login/register.ftl
index 930a07d..e560cf1 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/register.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/login/register.ftl
@@ -7,7 +7,7 @@
     <#elseif section = "form">
         <form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
           <#if !realm.registrationEmailAsUsername>
-            <div class="${properties.kcFormGroupClass!}">
+            <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('username',properties.kcFormGroupErrorClass!)}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
                 </div>
@@ -16,7 +16,7 @@
                 </div>
             </div>
           </#if>
-            <div class="${properties.kcFormGroupClass!}">
+            <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('firstName',properties.kcFormGroupErrorClass!)}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
                 </div>
@@ -25,7 +25,7 @@
                 </div>
             </div>
 
-            <div class="${properties.kcFormGroupClass!}">
+            <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('lastName',properties.kcFormGroupErrorClass!)}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
                 </div>
@@ -34,7 +34,7 @@
                 </div>
             </div>
 
-            <div class="${properties.kcFormGroupClass!}">
+            <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('email',properties.kcFormGroupErrorClass!)}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
                 </div>
@@ -43,7 +43,7 @@
                 </div>
             </div>
 
-            <div class="${properties.kcFormGroupClass!}">
+            <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password',properties.kcFormGroupErrorClass!)}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
                 </div>
@@ -52,7 +52,7 @@
                 </div>
             </div>
 
-            <div class="${properties.kcFormGroupClass!}">
+            <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password-confirm',properties.kcFormGroupErrorClass!)}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="password-confirm" class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label>
                 </div>
diff --git a/forms/common-themes/src/main/resources/theme/patternfly/login/theme.properties b/forms/common-themes/src/main/resources/theme/patternfly/login/theme.properties
index 25427a7..7c83966 100644
--- a/forms/common-themes/src/main/resources/theme/patternfly/login/theme.properties
+++ b/forms/common-themes/src/main/resources/theme/patternfly/login/theme.properties
@@ -18,6 +18,7 @@ kcFormAreaClass=col-xs-12 col-sm-8 col-md-8 col-lg-6 login
 
 kcFormClass=form-horizontal
 kcFormGroupClass=form-group
+kcFormGroupErrorClass=has-error
 kcLabelClass=control-label
 kcLabelWrapperClass=col-xs-12 col-sm-12 col-md-4 col-lg-3
 kcInputClass=form-control
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
index 36a5252..c3a71d9 100755
--- a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
@@ -1,19 +1,21 @@
 package org.keycloak.login;
 
+import java.net.URI;
+import java.util.List;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.FormMessage;
 import org.keycloak.provider.Provider;
 
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
-import java.net.URI;
-import java.util.List;
-
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -48,7 +50,20 @@ public interface LoginFormsProvider extends Provider {
     public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested);
     public LoginFormsProvider setAccessRequest(String message);
 
+    /**
+     * Set one global error message.
+     * 
+     * @param message key of message
+     * @param parameters to be formatted into message
+     */
     public LoginFormsProvider setError(String message, Object ... parameters);
+    
+    /**
+     * Set multiple error messages.
+     * 
+     * @param messages to be set
+     */
+    public LoginFormsProvider setErrors(List<FormMessage> messages);
 
     public LoginFormsProvider setSuccess(String message, Object ... parameters);
 
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
index 9aca77f..0dd6342 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -1,25 +1,50 @@
 package org.keycloak.login.freemarker;
 
+import java.io.IOException;
+import java.net.URI;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.email.EmailException;
 import org.keycloak.email.EmailProvider;
-import org.keycloak.freemarker.*;
+import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
+import org.keycloak.freemarker.FreeMarkerException;
+import org.keycloak.freemarker.FreeMarkerUtil;
+import org.keycloak.freemarker.LocaleHelper;
+import org.keycloak.freemarker.Theme;
+import org.keycloak.freemarker.ThemeProvider;
 import org.keycloak.freemarker.beans.AdvancedMessageFormatterMethod;
+import org.keycloak.freemarker.beans.LocaleBean;
+import org.keycloak.freemarker.beans.MessageBean;
 import org.keycloak.freemarker.beans.MessageFormatterMethod;
+import org.keycloak.freemarker.beans.MessageType;
+import org.keycloak.freemarker.beans.MessagesPerFieldBean;
 import org.keycloak.login.LoginFormsPages;
 import org.keycloak.login.LoginFormsProvider;
 import org.keycloak.login.freemarker.model.ClientBean;
 import org.keycloak.login.freemarker.model.CodeBean;
-import org.keycloak.freemarker.beans.LocaleBean;
+import org.keycloak.login.freemarker.model.IdentityProviderBean;
 import org.keycloak.login.freemarker.model.LoginBean;
-import org.keycloak.login.freemarker.model.MessageBean;
 import org.keycloak.login.freemarker.model.OAuthGrantBean;
 import org.keycloak.login.freemarker.model.ProfileBean;
 import org.keycloak.login.freemarker.model.RealmBean;
 import org.keycloak.login.freemarker.model.RegisterBean;
-import org.keycloak.login.freemarker.model.IdentityProviderBean;
 import org.keycloak.login.freemarker.model.TotpBean;
 import org.keycloak.login.freemarker.model.UrlBean;
 import org.keycloak.models.ClientModel;
@@ -28,16 +53,10 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.FormMessage;
 import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.flows.Urls;
 
-import javax.ws.rs.core.*;
-import java.io.IOException;
-import java.net.URI;
-import java.text.MessageFormat;
-import java.util.*;
-import java.util.concurrent.TimeUnit;
-
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -45,8 +64,6 @@ import java.util.concurrent.TimeUnit;
 
     private static final Logger logger = Logger.getLogger(FreeMarkerLoginFormsProvider.class);
 
-    public static enum MessageType {SUCCESS, WARNING, ERROR}
-
     private String accessCode;
     private Response.Status status;
     private List<RoleModel> realmRolesRequested;
@@ -55,9 +72,8 @@ import java.util.concurrent.TimeUnit;
     private Map<String, String> httpResponseHeaders = new HashMap<String, String>();
     private String accessRequestMessage;
     private URI actionUri;
-    private Object[] parameters;
 
-    private String message;
+    private List<FormMessage> messages = null;
     private MessageType messageType = MessageType.ERROR;
 
     private MultivaluedMap<String, String> formData;
@@ -134,7 +150,7 @@ import java.util.concurrent.TimeUnit;
                 return Response.serverError().build();
         }
 
-        if (message == null) {
+        if (messages == null) {
             setWarning(actionMessage);
         }
 
@@ -175,25 +191,30 @@ import java.util.concurrent.TimeUnit;
             logger.warn("Failed to load properties", e);
         }
 
-        Properties messages;
+        Properties messagesBundle;
         Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, httpHeaders);
         try {
-            messages = theme.getMessages(locale);
-            attributes.put("msg", new MessageFormatterMethod(locale, messages));
+            messagesBundle = theme.getMessages(locale);
+            attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
         } catch (IOException e) {
             logger.warn("Failed to load messages", e);
-            messages = new Properties();
+            messagesBundle = new Properties();
         }
 
-        if (message != null) {
-            String formattedMessage;
-            if(messages.containsKey(message)){
-                formattedMessage = new MessageFormat(messages.getProperty(message),locale).format(parameters);
-            }else{
-                formattedMessage = message;
+        MessagesPerFieldBean messagesPerField = new MessagesPerFieldBean();
+        if (messages != null) {
+            MessageBean wholeMessage = new MessageBean(null, messageType);
+            for (FormMessage message : this.messages) {
+                String formattedMessageText = formatMessage(message, messagesBundle, locale);
+                if (formattedMessageText != null) {
+                    wholeMessage.appendSummaryLine(formattedMessageText);
+                    messagesPerField.addMessage(message.getField(), formattedMessageText, messageType);
+                }
             }
-            attributes.put("message", new MessageBean(formattedMessage, messageType));
+            attributes.put("message", wholeMessage);
         }
+        attributes.put("messagesPerField", messagesPerField);
+
         if (page == LoginFormsPages.OAUTH_GRANT) {
             // for some reason Resteasy 2.3.7 doesn't like query params and form params with the same name and will null out the code form param
             uriBuilder.replaceQuery(null);
@@ -218,7 +239,7 @@ import java.util.concurrent.TimeUnit;
                         b = UriBuilder.fromUri(baseUri).path(uriInfo.getPath());
                         break;
                 }
-                attributes.put("locale", new LocaleBean(realm, locale, b, messages));
+                attributes.put("locale", new LocaleBean(realm, locale, b, messagesBundle));
             }
         }
 
@@ -240,10 +261,10 @@ import java.util.concurrent.TimeUnit;
                 break;
             case OAUTH_GRANT:
                 attributes.put("oauth", new OAuthGrantBean(accessCode, clientSession, client, realmRolesRequested, resourceRolesRequested, this.accessRequestMessage));
-                attributes.put("advancedMsg", new AdvancedMessageFormatterMethod(locale, messages));
+                attributes.put("advancedMsg", new AdvancedMessageFormatterMethod(locale, messagesBundle));
                 break;
             case CODE:
-                attributes.put(OAuth2Constants.CODE, new CodeBean(accessCode, messageType == MessageType.ERROR ? message : null));
+                attributes.put(OAuth2Constants.CODE, new CodeBean(accessCode, messageType == MessageType.ERROR ? getFirstMessageUnformatted() : null));
                 break;
         }
 
@@ -303,24 +324,51 @@ import java.util.concurrent.TimeUnit;
         return createResponse(LoginFormsPages.CODE);
     }
 
-    public FreeMarkerLoginFormsProvider setError(String message, Object ... parameters) {
-        this.message = message;
+    protected void setMessage(MessageType type, String message, Object... parameters) {
+        messageType = type;
+        messages = new ArrayList<>();
+        messages.add(new FormMessage(null, message, parameters));
+    }
+
+    protected String getFirstMessageUnformatted() {
+        if (messages != null && !messages.isEmpty()) {
+            return messages.get(0).getMessage();
+        }
+        return null;
+    }
+
+    protected String formatMessage(FormMessage message, Properties messagesBundle, Locale locale) {
+        if (message == null)
+            return null;
+        if (messagesBundle.containsKey(message.getMessage())) {
+            return new MessageFormat(messagesBundle.getProperty(message.getMessage()), locale).format(message.getParameters());
+        } else {
+            return message.getMessage();
+        }
+    }
+
+    @Override
+    public FreeMarkerLoginFormsProvider setError(String message, Object... parameters) {
+        setMessage(MessageType.ERROR, message, parameters);
+        return this;
+    }
+
+    @Override
+    public LoginFormsProvider setErrors(List<FormMessage> messages) {
         this.messageType = MessageType.ERROR;
-        this.parameters = parameters;
+        this.messages = new ArrayList<>(messages);
         return this;
     }
 
+    @Override
     public FreeMarkerLoginFormsProvider setSuccess(String message, Object ... parameters) {
-        this.message = message;
-        this.messageType = MessageType.SUCCESS;
-        this.parameters = parameters;
+        setMessage(MessageType.SUCCESS, message, parameters);
         return this;
     }
 
+    @Override
     public FreeMarkerLoginFormsProvider setWarning(String message, Object ... parameters) {
-        this.message = message;
-        this.messageType = MessageType.WARNING;
-        this.parameters = parameters;
+        setMessage(MessageType.WARNING, message, parameters);
         return this;
     }
 
diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
index 390721d..ad6c327 100755
--- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
+++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
@@ -77,6 +77,7 @@ public class ServletOAuthClient extends AbstractOAuthClient {
         String state = getStateCode();
 
         KeycloakUriBuilder uriBuilder =  KeycloakUriBuilder.fromUri(getUrl(request, authUrl, true))
+                .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
                 .queryParam(OAuth2Constants.CLIENT_ID, clientId)
                 .queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
                 .queryParam(OAuth2Constants.STATE, state);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/FormMessage.java b/model/api/src/main/java/org/keycloak/models/utils/FormMessage.java
new file mode 100644
index 0000000..6a93c51
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/FormMessage.java
@@ -0,0 +1,69 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @authors tag. All rights reserved.
+ */
+package org.keycloak.models.utils;
+
+import java.util.Arrays;
+
+/**
+ * Message (eg. error) to be shown in form.
+ * 
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public class FormMessage {
+
+	/**
+	 * Value used for {@link #field} if message is global (not tied to any specific form field)
+	 */
+	public static final String GLOBAL = "global";
+
+	private String field;
+	private String message;
+	private Object[] parameters;
+
+	/**
+	 * Create message.
+	 * 
+	 * @param field this message is for. {@link #GLOBAL} is used if null
+	 * @param message key for the message
+	 * @param parameters to be formatted into message
+	 */
+	public FormMessage(String field, String message, Object... parameters) {
+		this(field, message);
+		this.parameters = parameters;
+	}
+	
+	/**
+     * Create message without parameters.
+     * 
+     * @param field this message is for. {@link #GLOBAL} is used if null
+     * @param message key for the message
+     */
+    public FormMessage(String field, String message) {
+        super();
+        if (field == null)
+            field = GLOBAL;
+        this.field = field;
+        this.message = message;
+    }
+
+	public String getField() {
+		return field;
+	}
+
+	public String getMessage() {
+		return message;
+	}
+
+	public Object[] getParameters() {
+		return parameters;
+	}
+
+	@Override
+	public String toString() {
+		return "FormMessage [field=" + field + ", message=" + message + ", parameters=" + Arrays.toString(parameters) + "]";
+	}
+
+}

services/pom.xml 2(+2 -0)

diff --git a/services/pom.xml b/services/pom.xml
index fb012c7..a7fddca 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -209,6 +209,7 @@
                         <id>generate-service-docs</id>
                         <phase>generate-resources</phase>
                         <configuration>
+                            <subpackages>org.keycloak.services.resources.admin:org.keycloak.protocol.oidc</subpackages>
                             <doclet>com.lunatech.doclets.jax.jaxrs.JAXRSDoclet</doclet>
                             <docletArtifacts>
                                 <docletArtifact>
@@ -225,6 +226,7 @@
                                 </offlineLink>
                             </offlineLinks>
                             <additionalparam>-disablejavascriptexample</additionalparam>
+                            <additionalparam>-pathexcludefilter '/admin/.*index.*' -pathexcludefilter '/admin' -pathexcludefilter '/admin/\\{realm\\}/console.*'</additionalparam>
                         </configuration>
                         <goals>
                             <goal>javadoc</goal>
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 66197b7..dfe6b2f 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
@@ -132,7 +132,7 @@ public class AuthorizationEndpoint {
         state = params.getFirst(OIDCLoginProtocol.STATE_PARAM);
         scope = params.getFirst(OIDCLoginProtocol.SCOPE_PARAM);
         loginHint = params.getFirst(OIDCLoginProtocol.LOGIN_HINT_PARAM);
-        prompt = params.getFirst(OIDCLoginProtocol.REDIRECT_URI_PARAM);
+        prompt = params.getFirst(OIDCLoginProtocol.PROMPT_PARAM);
         idpHint = params.getFirst(AdapterConstants.KC_IDP_HINT);
 
         checkSsl();
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 6a0e0b5..906e3e2 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -33,6 +33,7 @@ import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventStoreProvider;
 import org.keycloak.events.EventType;
 import org.keycloak.models.*;
+import org.keycloak.models.utils.FormMessage;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.TimeBasedOTP;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@@ -69,6 +70,7 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import javax.ws.rs.core.Variant;
+
 import java.lang.reflect.Method;
 import java.net.URI;
 import java.util.HashSet;
@@ -403,10 +405,10 @@ public class AccountService {
 
         UserModel user = auth.getUser();
 
-        String error = Validation.validateUpdateProfileForm(formData);
-        if (error != null) {
+        List<FormMessage> errors = Validation.validateUpdateProfileForm(formData);
+        if (errors != null && !errors.isEmpty()) {
             setReferrerOnPage();
-            return account.setError(error).setProfileFormData(formData).createResponse(AccountPages.ACCOUNT);
+            return account.setErrors(errors).setProfileFormData(formData).createResponse(AccountPages.ACCOUNT);
         }
 
         try {
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 872cfd8..df9771f 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -51,6 +51,7 @@ import org.keycloak.services.resources.flows.Flows;
 import org.keycloak.services.resources.flows.Urls;
 import org.keycloak.services.validation.Validation;
 import org.keycloak.social.SocialIdentityProvider;
+import org.keycloak.util.ObjectUtil;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
@@ -343,12 +344,15 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
     private void updateFederatedIdentity(FederatedIdentity updatedIdentity, UserModel federatedUser) {
         FederatedIdentityModel federatedIdentityModel = this.session.users().getFederatedIdentity(federatedUser, updatedIdentity.getIdentityProviderId(), this.realmModel);
 
-        federatedIdentityModel.setToken(updatedIdentity.getToken());
+        // Skip DB write if tokens are null or equal
+        if (!ObjectUtil.isEqualOrNull(updatedIdentity.getToken(), federatedIdentityModel.getToken())) {
+            federatedIdentityModel.setToken(updatedIdentity.getToken());
 
-        this.session.users().updateFederatedIdentity(this.realmModel, federatedUser, federatedIdentityModel);
+            this.session.users().updateFederatedIdentity(this.realmModel, federatedUser, federatedIdentityModel);
 
-        if (isDebugEnabled()) {
-            LOGGER.debugf("Identity [%s] update with response from identity provider [%s].", federatedUser, updatedIdentity.getIdentityProviderId());
+            if (isDebugEnabled()) {
+                LOGGER.debugf("Identity [%s] update with response from identity provider [%s].", federatedUser, updatedIdentity.getIdentityProviderId());
+            }
         }
     }
 
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 e2ad7f0..078e408 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -1,959 +1,954 @@
-/*
- * JBoss, Home of Professional Open Source.
- * Copyright 2012, 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.
- */
-package org.keycloak.services.resources;
-
-import org.jboss.logging.Logger;
-import org.jboss.resteasy.spi.HttpRequest;
-import org.keycloak.ClientConnection;
-import org.keycloak.email.EmailException;
-import org.keycloak.email.EmailProvider;
-import org.keycloak.events.Details;
-import org.keycloak.events.Errors;
-import org.keycloak.events.EventBuilder;
-import org.keycloak.events.EventType;
-import org.keycloak.jose.jws.JWSBuilder;
-import org.keycloak.login.LoginFormsProvider;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.ModelException;
-import org.keycloak.models.PasswordPolicy;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.RequiredCredentialModel;
-import org.keycloak.models.UserCredentialModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserModel.RequiredAction;
-import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.models.utils.TimeBasedOTP;
-import org.keycloak.protocol.LoginProtocol;
-import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
-import org.keycloak.protocol.oidc.TokenManager;
-import org.keycloak.representations.PasswordToken;
-import org.keycloak.representations.idm.CredentialRepresentation;
-import org.keycloak.services.managers.AuthenticationManager;
-import org.keycloak.services.managers.ClientSessionCode;
-import org.keycloak.services.messages.Messages;
-import org.keycloak.services.resources.flows.Flows;
-import org.keycloak.services.resources.flows.Urls;
-import org.keycloak.services.util.CookieHelper;
-import org.keycloak.services.validation.Validation;
-
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.Cookie;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.MultivaluedMap;
-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.util.LinkedList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-/**
- * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
- */
-public class LoginActionsService {
-
-    protected static final Logger logger = Logger.getLogger(LoginActionsService.class);
-
-    public static final String ACTION_COOKIE = "KEYCLOAK_ACTION";
-
-    private RealmModel realm;
-
-    @Context
-    private HttpRequest request;
-
-    @Context
-    protected HttpHeaders headers;
-
-    @Context
-    private UriInfo uriInfo;
-
-    @Context
-    private ClientConnection clientConnection;
-
-    @Context
-    protected Providers providers;
-
-    @Context
-    protected KeycloakSession session;
-
-    private AuthenticationManager authManager;
-
-    private EventBuilder event;
-
-    public static UriBuilder loginActionsBaseUrl(UriInfo uriInfo) {
-        UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
-        return loginActionsBaseUrl(baseUriBuilder);
-    }
-
-    public static UriBuilder loginActionsBaseUrl(UriBuilder baseUriBuilder) {
-        return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getLoginActionsService");
-    }
-
-    public static UriBuilder processLoginUrl(UriInfo uriInfo) {
-        UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
-        return processLoginUrl(baseUriBuilder);
-    }
-
-    public static UriBuilder processLoginUrl(UriBuilder baseUriBuilder) {
-        UriBuilder uriBuilder = loginActionsBaseUrl(baseUriBuilder);
-        return uriBuilder.path(OIDCLoginProtocolService.class, "processLogin");
-    }
-
-    public static UriBuilder processOAuthUrl(UriInfo uriInfo) {
-        UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
-        return processOAuthUrl(baseUriBuilder);
-    }
-
-    public static UriBuilder processOAuthUrl(UriBuilder baseUriBuilder) {
-        UriBuilder uriBuilder = loginActionsBaseUrl(baseUriBuilder);
-        return uriBuilder.path(OIDCLoginProtocolService.class, "processOAuth");
-    }
-
-    public LoginActionsService(RealmModel realm, AuthenticationManager authManager, EventBuilder event) {
-        this.realm = realm;
-        this.authManager = authManager;
-        this.event = event;
-    }
-
-    private boolean checkSsl() {
-        if (uriInfo.getBaseUri().getScheme().equals("https")) {
-            return true;
-        } else {
-            return !realm.getSslRequired().isRequired(clientConnection);
-        }
-    }
-
-
-    private class Checks {
-        ClientSessionCode clientCode;
-        Response response;
-
-        boolean check(String code, ClientSessionModel.Action requiredAction) {
-            if (!check(code)) {
-                return false;
-            } else if (!clientCode.isValid(requiredAction)) {
-                event.error(Errors.INVALID_CODE);
-                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_CODE);
-                return false;
-            } else {
-                return true;
-            }
-        }
-
-        boolean check(String code, ClientSessionModel.Action requiredAction, ClientSessionModel.Action alternativeRequiredAction) {
-            if (!check(code)) {
-                return false;
-            } else if (!(clientCode.isValid(requiredAction) || clientCode.isValid(alternativeRequiredAction))) {
-                event.error(Errors.INVALID_CODE);
-                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo,headers, Messages.INVALID_CODE);
-                return false;
-            } else {
-                return true;
-            }
-        }
-
-        public boolean check(String code) {
-            if (!checkSsl()) {
-                event.error(Errors.SSL_REQUIRED);
-                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
-                return false;
-            }
-            if (!realm.isEnabled()) {
-                event.error(Errors.REALM_DISABLED);
-                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
-                return false;
-            }
-            clientCode = ClientSessionCode.parse(code, session, realm);
-            if (clientCode == null) {
-                event.error(Errors.INVALID_CODE);
-                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
-                return false;
-            }
-            return true;
-        }
-    }
-
-    /**
-     * protocol independent login page entry point
-     *
-     *
-     * @param code
-     * @return
-     */
-    @Path("login")
-    @GET
-    public Response loginPage(@QueryParam("code") String code) {
-        event.event(EventType.LOGIN);
-        Checks checks = new Checks();
-        if (!checks.check(code)) {
-            return checks.response;
-        }
-        event.detail(Details.CODE_ID, code);
-        ClientSessionCode clientSessionCode = checks.clientCode;
-        ClientSessionModel clientSession = clientSessionCode.getClientSession();
-
-        if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD)) {
-            TokenManager.dettachClientSession(session.sessions(), realm, clientSession);
-            clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
-        }
-
-        LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers)
-                .setClientSessionCode(clientSessionCode.getCode());
-
-        return forms.createLogin();
-    }
-
-    /**
-     * protocol independent registration page entry point
-     *
-     * @param code
-     * @return
-     */
-    @Path("registration")
-    @GET
-    public Response registerPage(@QueryParam("code") String code) {
-        event.event(EventType.REGISTER);
-        if (!realm.isRegistrationAllowed()) {
-            event.error(Errors.REGISTRATION_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REGISTRATION_NOT_ALLOWED);
-        }
-
-        Checks checks = new Checks();
-        if (!checks.check(code)) {
-            return checks.response;
-        }
-        event.detail(Details.CODE_ID, code);
-        ClientSessionCode clientSessionCode = checks.clientCode;
-        ClientSessionModel clientSession = clientSessionCode.getClientSession();
-
-
-        authManager.expireIdentityCookie(realm, uriInfo, clientConnection);
-
-        return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers)
-                .setClientSessionCode(clientSessionCode.getCode())
-                .createRegistration();
-    }
-
-    /**
-     * URL called after login page.  YOU SHOULD NEVER INVOKE THIS DIRECTLY!
-     *
-     * @param code
-     * @param formData
-     * @return
-     */
-    @Path("request/login")
-    @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response processLogin(@QueryParam("code") String code,
-                                 final MultivaluedMap<String, String> formData) {
-        event.event(EventType.LOGIN);
-        if (!checkSsl()) {
-            event.error(Errors.SSL_REQUIRED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
-        }
-
-        if (!realm.isEnabled()) {
-            event.error(Errors.REALM_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
-        }
-        ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm);
-        if (clientCode == null) {
-            event.error(Errors.INVALID_CODE);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
-        }
-
-        ClientSessionModel clientSession = clientCode.getClientSession();
-        event.detail(Details.CODE_ID, clientSession.getId());
-
-        if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE) || clientSession.getUserSession() != null) {
-            clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE);
-            event.client(clientSession.getClient()).error(Errors.EXPIRED_CODE);
-            return Flows.forms(this.session, realm, clientSession.getClient(), uriInfo, headers).setError(Messages.EXPIRED_CODE)
-                    .setClientSessionCode(clientCode.getCode())
-                    .createLogin();
-        }
-
-        String username = formData.getFirst(AuthenticationManager.FORM_USERNAME);
-
-        String rememberMe = formData.getFirst("rememberMe");
-        boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on");
-
-        event.client(clientSession.getClient().getClientId())
-                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
-                .detail(Details.RESPONSE_TYPE, "code")
-                .detail(Details.AUTH_METHOD, "form")
-                .detail(Details.USERNAME, username);
-
-        if (remember) {
-            event.detail(Details.REMEMBER_ME, "true");
-        }
-
-
-        ClientModel client = clientSession.getClient();
-        if (client == null) {
-            event.error(Errors.CLIENT_NOT_FOUND);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER);
-        }
-        if (!client.isEnabled()) {
-            event.error(Errors.CLIENT_NOT_FOUND);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED);
-        }
-
-        if (formData.containsKey("cancel")) {
-            event.error(Errors.REJECTED_BY_USER);
-            LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
-            protocol.setRealm(realm)
-                    .setHttpHeaders(headers)
-                    .setUriInfo(uriInfo);
-            return protocol.cancelLogin(clientSession);
-        }
-
-        AuthenticationManager.AuthenticationStatus status = authManager.authenticateForm(session, clientConnection, realm, formData);
-
-        if (remember) {
-            authManager.createRememberMeCookie(realm, username, uriInfo, clientConnection);
-        } else {
-            authManager.expireRememberMeCookie(realm, uriInfo, clientConnection);
-        }
-
-        UserModel user = KeycloakModelUtils.findUserByNameOrEmail(session, realm, username);
-        if (user != null) {
-            event.user(user);
-        }
-
-        switch (status) {
-            case SUCCESS:
-            case ACTIONS_REQUIRED:
-                UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", remember, null, null);
-                TokenManager.attachClientSession(userSession, clientSession);
-                event.session(userSession);
-                return authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
-            case ACCOUNT_TEMPORARILY_DISABLED:
-                event.error(Errors.USER_TEMPORARILY_DISABLED);
-                return Flows.forms(this.session, realm, client, uriInfo, headers)
-                        .setError(Messages.ACCOUNT_TEMPORARILY_DISABLED)
-                        .setFormData(formData)
-                        .setClientSessionCode(clientCode.getCode())
-                        .createLogin();
-            case ACCOUNT_DISABLED:
-                event.error(Errors.USER_DISABLED);
-                return Flows.forms(this.session, realm, client, uriInfo, headers)
-                        .setError(Messages.ACCOUNT_DISABLED)
-                        .setClientSessionCode(clientCode.getCode())
-                        .setFormData(formData).createLogin();
-            case MISSING_TOTP:
-                formData.remove(CredentialRepresentation.PASSWORD);
-
-                String passwordToken = new JWSBuilder().jsonContent(new PasswordToken(realm.getName(), user.getId())).rsa256(realm.getPrivateKey());
-                formData.add(CredentialRepresentation.PASSWORD_TOKEN, passwordToken);
-
-                return Flows.forms(this.session, realm, client, uriInfo, headers)
-                        .setFormData(formData)
-                        .setClientSessionCode(clientCode.getCode())
-                        .createLoginTotp();
-            case INVALID_USER:
-                event.error(Errors.USER_NOT_FOUND);
-                return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.INVALID_USER)
-                        .setFormData(formData)
-                        .setClientSessionCode(clientCode.getCode())
-                        .createLogin();
-            default:
-                event.error(Errors.INVALID_USER_CREDENTIALS);
-                return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.INVALID_USER)
-                        .setFormData(formData)
-                        .setClientSessionCode(clientCode.getCode())
-                        .createLogin();
-        }
-    }
-
-    /**
-     * Registration
-     *
-     * @param code
-     * @param formData
-     * @return
-     */
-    @Path("request/registration")
-    @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response processRegister(@QueryParam("code") String code,
-                                    final MultivaluedMap<String, String> formData) {
-        event.event(EventType.REGISTER);
-        if (!checkSsl()) {
-            event.error(Errors.SSL_REQUIRED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
-        }
-
-        if (!realm.isEnabled()) {
-            event.error(Errors.REALM_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
-        }
-        if (!realm.isRegistrationAllowed()) {
-            event.error(Errors.REGISTRATION_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REGISTRATION_NOT_ALLOWED);
-        }
-        ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm);
-        if (clientCode == null) {
-            event.error(Errors.INVALID_CODE);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
-        }
-        if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) {
-            event.error(Errors.INVALID_CODE);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_CODE);
-        }
-
-        String username = formData.getFirst("username");
-        String email = formData.getFirst("email");
-        if (realm.isRegistrationEmailAsUsername()) {
-            username = email;
-            formData.putSingle(AuthenticationManager.FORM_USERNAME, username);
-        }
-        ClientSessionModel clientSession = clientCode.getClientSession();
-        event.client(clientSession.getClient())
-                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
-                .detail(Details.RESPONSE_TYPE, "code")
-                .detail(Details.USERNAME, username)
-                .detail(Details.EMAIL, email)
-                .detail(Details.REGISTER_METHOD, "form");
-
-        if (!realm.isEnabled()) {
-            event.error(Errors.REALM_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
-        }
-        ClientModel client = clientSession.getClient();
-        if (client == null) {
-            event.error(Errors.CLIENT_NOT_FOUND);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER);
-        }
-
-        if (!client.isEnabled()) {
-            event.error(Errors.CLIENT_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED);
-        }
-
-
-        List<String> requiredCredentialTypes = new LinkedList<String>();
-        for (RequiredCredentialModel m : realm.getRequiredCredentials()) {
-            requiredCredentialTypes.add(m.getType());
-        }
-
-        // Validate here, so user is not created if password doesn't validate to passwordPolicy of current realm
-        String errorMessage = Validation.validateRegistrationForm(realm, formData, requiredCredentialTypes);
-        Object[] parameters = new Object[0];
-        if (errorMessage == null) {
-            PasswordPolicy.Error error = Validation.validatePassword(formData, realm.getPasswordPolicy());
-            if(error != null){
-                errorMessage = error.getMessage();
-                parameters = error.getParameters();
-            }
-        }
-
-        if (errorMessage != null) {
-            event.error(Errors.INVALID_REGISTRATION);
-            return Flows.forms(session, realm, client, uriInfo, headers)
-                    .setError(errorMessage, parameters)
-                    .setFormData(formData)
-                    .setClientSessionCode(clientCode.getCode())
-                    .createRegistration();
-        }
-
-        // Validate that user with this username doesn't exist in realm or any federation provider
-        if (session.users().getUserByUsername(username, realm) != null) {
-            event.error(Errors.USERNAME_IN_USE);
-            return Flows.forms(session, realm, client, uriInfo, headers)
-                    .setError(Messages.USERNAME_EXISTS)
-                    .setFormData(formData)
-                    .setClientSessionCode(clientCode.getCode())
-                    .createRegistration();
-        }
-
-        // Validate that user with this email doesn't exist in realm or any federation provider
-        if (session.users().getUserByEmail(email, realm) != null) {
-            event.error(Errors.EMAIL_IN_USE);
-            return Flows.forms(session, realm, client, uriInfo, headers)
-                    .setError(Messages.EMAIL_EXISTS)
-                    .setFormData(formData)
-                    .setClientSessionCode(clientCode.getCode())
-                    .createRegistration();
-        }
-
-        UserModel user = session.users().addUser(realm, username);
-        user.setEnabled(true);
-        user.setFirstName(formData.getFirst("firstName"));
-        user.setLastName(formData.getFirst("lastName"));
-
-        user.setEmail(email);
-
-        if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) {
-            UserCredentialModel credentials = new UserCredentialModel();
-            credentials.setType(CredentialRepresentation.PASSWORD);
-            credentials.setValue(formData.getFirst("password"));
-
-            boolean passwordUpdateSuccessful;
-            String passwordUpdateError = null;
-            Object[] passwordUpdateErrorParameters = null;
-            try {
-                session.users().updateCredential(realm, user, UserCredentialModel.password(formData.getFirst("password")));
-                passwordUpdateSuccessful = true;
-            } catch (ModelException me) {
-                passwordUpdateSuccessful = false;
-                passwordUpdateError = me.getMessage();
-                passwordUpdateErrorParameters = me.getParameters();
-            } catch (Exception ape) {
-                passwordUpdateSuccessful = false;
-                passwordUpdateError = ape.getMessage();
-            }
-
-            // User already registered, but force him to update password
-            if (!passwordUpdateSuccessful) {
-                user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
-                return Flows.forms(session, realm, client, uriInfo, headers)
-                        .setError(passwordUpdateError, passwordUpdateErrorParameters)
-                        .setClientSessionCode(clientCode.getCode())
-                        .createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
-            }
-        }
-
-        AttributeFormDataProcessor.process(formData, realm, user);
-
-        event.user(user).success();
-        event = new EventBuilder(realm, session, clientConnection);
-
-        return processLogin(code, formData);
-    }
-
-    /**
-     * OAuth grant page.  You should not invoked this directly!
-     *
-     * @param formData
-     * @return
-     */
-    @Path("consent")
-    @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response processConsent(final MultivaluedMap<String, String> formData) {
-        event.event(EventType.LOGIN).detail(Details.RESPONSE_TYPE, "code");
-
-
-        if (!checkSsl()) {
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
-        }
-
-        String code = formData.getFirst("code");
-
-        ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
-        if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT)) {
-            event.error(Errors.INVALID_CODE);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_ACCESS_CODE);
-        }
-        ClientSessionModel clientSession = accessCode.getClientSession();
-        event.detail(Details.CODE_ID, clientSession.getId());
-
-        String redirect = clientSession.getRedirectUri();
-
-        event.client(clientSession.getClient())
-                .user(clientSession.getUserSession().getUser())
-                .detail(Details.RESPONSE_TYPE, "code")
-                .detail(Details.REDIRECT_URI, redirect);
-
-        UserSessionModel userSession = clientSession.getUserSession();
-        if (userSession != null) {
-            event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
-            event.detail(Details.USERNAME, userSession.getLoginUsername());
-            if (userSession.isRememberMe()) {
-                event.detail(Details.REMEMBER_ME, "true");
-            }
-        }
-
-        if (!AuthenticationManager.isSessionValid(realm, userSession)) {
-            AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
-            event.error(Errors.INVALID_CODE);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
-        }
-        event.session(userSession);
-
-        LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
-        protocol.setRealm(realm)
-                .setHttpHeaders(headers)
-                .setUriInfo(uriInfo);
-        if (formData.containsKey("cancel")) {
-            event.error(Errors.REJECTED_BY_USER);
-            return protocol.consentDenied(clientSession);
-        }
-
-        event.success();
-
-        return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection);
-    }
-
-    @Path("profile")
-    @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response updateProfile(@QueryParam("code") String code,
-                                  final MultivaluedMap<String, String> formData) {
-        event.event(EventType.UPDATE_PROFILE);
-        Checks checks = new Checks();
-        if (!checks.check(code, ClientSessionModel.Action.UPDATE_PROFILE)) {
-            return checks.response;
-        }
-        ClientSessionCode accessCode = checks.clientCode;
-        ClientSessionModel clientSession = accessCode.getClientSession();
-        UserSessionModel userSession = clientSession.getUserSession();
-        UserModel user = userSession.getUser();
-
-        initEvent(clientSession);
-
-        String error = Validation.validateUpdateProfileForm(formData);
-        if (error != null) {
-            return Flows.forms(session, realm, null, uriInfo, headers).setUser(user).setError(error)
-                    .setClientSessionCode(accessCode.getCode())
-                    .createResponse(RequiredAction.UPDATE_PROFILE);
-        }
-
-        user.setFirstName(formData.getFirst("firstName"));
-        user.setLastName(formData.getFirst("lastName"));
-
-        String email = formData.getFirst("email");
-
-        String oldEmail = user.getEmail();
-        boolean emailChanged = oldEmail != null ? !oldEmail.equals(email) : email != null;
-
-        if (emailChanged) {
-            UserModel userByEmail = session.users().getUserByEmail(email, realm);
-
-            // check for duplicated email
-            if (userByEmail != null && !userByEmail.getId().equals(user.getId())) {
-                return Flows.forms(session, realm, null, uriInfo, headers).setUser(user).setError(Messages.EMAIL_EXISTS)
-                        .setClientSessionCode(accessCode.getCode())
-                        .createResponse(RequiredAction.UPDATE_PROFILE);
-            }
-
-            user.setEmail(email);
-            user.setEmailVerified(false);
-        }
-
-        user.removeRequiredAction(RequiredAction.UPDATE_PROFILE);
-        event.clone().event(EventType.UPDATE_PROFILE).success();
-
-        if (emailChanged) {
-            event.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
-        }
-
-        return redirectOauth(user, accessCode, clientSession, userSession);
-    }
-
-    @Path("totp")
-    @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response updateTotp(@QueryParam("code") String code,
-                               final MultivaluedMap<String, String> formData) {
-        event.event(EventType.UPDATE_TOTP);
-        Checks checks = new Checks();
-        if (!checks.check(code, ClientSessionModel.Action.CONFIGURE_TOTP)) {
-            return checks.response;
-        }
-        ClientSessionCode accessCode = checks.clientCode;
-        ClientSessionModel clientSession = accessCode.getClientSession();
-        UserSessionModel userSession = clientSession.getUserSession();
-        UserModel user = userSession.getUser();
-
-        initEvent(clientSession);
-
-        String totp = formData.getFirst("totp");
-        String totpSecret = formData.getFirst("totpSecret");
-
-        LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo, headers).setUser(user);
-        if (Validation.isEmpty(totp)) {
-            return loginForms.setError(Messages.MISSING_TOTP)
-                    .setClientSessionCode(accessCode.getCode())
-                    .createResponse(RequiredAction.CONFIGURE_TOTP);
-        } else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
-            return loginForms.setError(Messages.INVALID_TOTP)
-                    .setClientSessionCode(accessCode.getCode())
-                    .createResponse(RequiredAction.CONFIGURE_TOTP);
-        }
-
-        UserCredentialModel credentials = new UserCredentialModel();
-        credentials.setType(CredentialRepresentation.TOTP);
-        credentials.setValue(totpSecret);
-        session.users().updateCredential(realm, user, credentials);
-
-        user.setTotp(true);
-
-        user.removeRequiredAction(RequiredAction.CONFIGURE_TOTP);
-
-        event.clone().event(EventType.UPDATE_TOTP).success();
-
-        return redirectOauth(user, accessCode, clientSession, userSession);
-    }
-
-    @Path("password")
-    @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response updatePassword(@QueryParam("code") String code,
-                                   final MultivaluedMap<String, String> formData) {
-        event.event(EventType.UPDATE_PASSWORD);
-        Checks checks = new Checks();
-        if (!checks.check(code, ClientSessionModel.Action.UPDATE_PASSWORD, ClientSessionModel.Action.RECOVER_PASSWORD)) {
-            return checks.response;
-        }
-        ClientSessionCode accessCode = checks.clientCode;
-        ClientSessionModel clientSession = accessCode.getClientSession();
-        UserSessionModel userSession = clientSession.getUserSession();
-        UserModel user = userSession.getUser();
-
-        initEvent(clientSession);
-
-        String passwordNew = formData.getFirst("password-new");
-        String passwordConfirm = formData.getFirst("password-confirm");
-
-        LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo, headers).setUser(user);
-        if (Validation.isEmpty(passwordNew)) {
-            return loginForms.setError(Messages.MISSING_PASSWORD)
-                    .setClientSessionCode(accessCode.getCode())
-                    .createResponse(RequiredAction.UPDATE_PASSWORD);
-        } else if (!passwordNew.equals(passwordConfirm)) {
-            return loginForms.setError(Messages.NOTMATCH_PASSWORD)
-                    .setClientSessionCode(accessCode.getCode())
-                    .createResponse(RequiredAction.UPDATE_PASSWORD);
-        }
-
-        try {
-            session.users().updateCredential(realm, user, UserCredentialModel.password(passwordNew));
-        } catch (ModelException me) {
-            return loginForms.setError(me.getMessage(), me.getParameters())
-                    .setClientSessionCode(accessCode.getCode())
-                    .createResponse(RequiredAction.UPDATE_PASSWORD);
-        } catch (Exception ape) {
-            return loginForms.setError(ape.getMessage())
-                    .setClientSessionCode(accessCode.getCode())
-                    .createResponse(RequiredAction.UPDATE_PASSWORD);
-        }
-
-        user.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
-
-        event.event(EventType.UPDATE_PASSWORD).success();
-
-        if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD)) {
-            String actionCookieValue = getActionCookie();
-            if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) {
-                return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers).setSuccess(Messages.ACCOUNT_PASSWORD_UPDATED).createInfoPage();
-            }
-        }
-
-        event = event.clone().event(EventType.LOGIN);
-
-        return redirectOauth(user, accessCode, clientSession, userSession);
-    }
-
-
-    @Path("email-verification")
-    @GET
-    public Response emailVerification(@QueryParam("code") String code, @QueryParam("key") String key) {
-        event.event(EventType.VERIFY_EMAIL);
-        if (key != null) {
-            Checks checks = new Checks();
-            if (!checks.check(key, ClientSessionModel.Action.VERIFY_EMAIL)) {
-                return checks.response;
-            }
-            ClientSessionCode accessCode = checks.clientCode;
-            ClientSessionModel clientSession = accessCode.getClientSession();
-            UserSessionModel userSession = clientSession.getUserSession();
-            UserModel user = userSession.getUser();
-            initEvent(clientSession);
-            user.setEmailVerified(true);
-
-            user.removeRequiredAction(RequiredAction.VERIFY_EMAIL);
-
-            event.event(EventType.VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
-
-            String actionCookieValue = getActionCookie();
-            if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) {
-                return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers).setSuccess(Messages.EMAIL_VERIFIED).createInfoPage();
-            }
-
-            event = event.clone().removeDetail(Details.EMAIL).event(EventType.LOGIN);
-
-            return redirectOauth(user, accessCode, clientSession, userSession);
-        } else {
-            Checks checks = new Checks();
-            if (!checks.check(code, ClientSessionModel.Action.VERIFY_EMAIL)) {
-                return checks.response;
-            }
-            ClientSessionCode accessCode = checks.clientCode;
-            ClientSessionModel clientSession = accessCode.getClientSession();
-            UserSessionModel userSession = clientSession.getUserSession();
-            initEvent(clientSession);
-
-            createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
-
-            return Flows.forms(session, realm, null, uriInfo, headers)
-                    .setClientSessionCode(accessCode.getCode())
-                    .setUser(userSession.getUser())
-                    .createResponse(RequiredAction.VERIFY_EMAIL);
-        }
-    }
-
-    @Path("password-reset")
-    @GET
-    public Response passwordReset(@QueryParam("code") String code, @QueryParam("key") String key) {
-        event.event(EventType.RESET_PASSWORD);
-        if (key != null) {
-            Checks checks = new Checks();
-            if (!checks.check(key, ClientSessionModel.Action.RECOVER_PASSWORD)) {
-                return checks.response;
-            }
-            ClientSessionCode accessCode = checks.clientCode;
-            return Flows.forms(session, realm, null, uriInfo, headers)
-                    .setClientSessionCode(accessCode.getCode())
-                    .createResponse(RequiredAction.UPDATE_PASSWORD);
-        } else {
-            return Flows.forms(session, realm, null, uriInfo, headers)
-                    .setClientSessionCode(code)
-                    .createPasswordReset();
-        }
-    }
-
-    @Path("password-reset")
-    @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response sendPasswordReset(@QueryParam("code") String code,
-                                      final MultivaluedMap<String, String> formData) {
-        event.event(EventType.SEND_RESET_PASSWORD);
-        if (!checkSsl()) {
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
-        }
-        if (!realm.isEnabled()) {
-            event.error(Errors.REALM_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
-        }
-        ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
-        if (accessCode == null) {
-            event.error(Errors.INVALID_CODE);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
-        }
-        ClientSessionModel clientSession = accessCode.getClientSession();
-
-        String username = formData.getFirst("username");
-
-        ClientModel client = clientSession.getClient();
-        if (client == null) {
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER);
-        }
-        if (!client.isEnabled()) {
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED);
-        }
-
-        event.client(client.getClientId())
-                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
-                .detail(Details.RESPONSE_TYPE, "code")
-                .detail(Details.AUTH_METHOD, "form")
-                .detail(Details.USERNAME, username);
-
-        UserModel user = session.users().getUserByUsername(username, realm);
-        if (user == null && username.contains("@")) {
-            user = session.users().getUserByEmail(username, realm);
-        }
-
-        if (user == null) {
-            event.error(Errors.USER_NOT_FOUND);
-        } else if(!user.isEnabled()) {
-            event.user(user).error(Errors.USER_DISABLED);
-        }
-        else if(user.getEmail() == null || user.getEmail().trim().length() == 0) {
-            event.user(user).error(Errors.INVALID_EMAIL);
-        } else{
-            event.user(user);
-
-            UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", false, null, null);
-            event.session(userSession);
-            TokenManager.attachClientSession(userSession, clientSession);
-
-            accessCode.setAction(ClientSessionModel.Action.RECOVER_PASSWORD);
-
-            try {
-                UriBuilder builder = Urls.loginPasswordResetBuilder(uriInfo.getBaseUri());
-                builder.queryParam("key", accessCode.getCode());
-
-                String link = builder.build(realm.getName()).toString();
-                long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
-
-                this.session.getProvider(EmailProvider.class).setRealm(realm).setUser(user).sendPasswordReset(link, expiration);
-
-                event.detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, clientSession.getId()).success();
-            } catch (EmailException e) {
-                event.error(Errors.EMAIL_SEND_FAILED);
-                logger.error("Failed to send password reset email", e);
-                return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.EMAIL_SENT_ERROR)
-                        .setClientSessionCode(accessCode.getCode())
-                        .createErrorPage();
-            }
-
-            createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
-        }
-
-        return Flows.forms(session, realm, client,  uriInfo, headers).setSuccess(Messages.EMAIL_SENT).setClientSessionCode(accessCode.getCode()).createPasswordReset();
-    }
-
-    private String getActionCookie() {
-        Cookie cookie = headers.getCookies().get(ACTION_COOKIE);
-        AuthenticationManager.expireCookie(realm, ACTION_COOKIE, AuthenticationManager.getRealmCookiePath(realm, uriInfo), realm.getSslRequired().isRequired(clientConnection), clientConnection);
-        return cookie != null ? cookie.getValue() : null;
-    }
-
-    public static void createActionCookie(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, String sessionId) {
-        CookieHelper.addCookie(ACTION_COOKIE, sessionId, AuthenticationManager.getRealmCookiePath(realm, uriInfo), null, null, -1, realm.getSslRequired().isRequired(clientConnection), true);
-    }
-
-    private Response redirectOauth(UserModel user, ClientSessionCode accessCode, ClientSessionModel clientSession, UserSessionModel userSession) {
-        return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
-    }
-
-    private void initEvent(ClientSessionModel clientSession) {
-        event.event(EventType.LOGIN).client(clientSession.getClient())
-                .user(clientSession.getUserSession().getUser())
-                .session(clientSession.getUserSession().getId())
-                .detail(Details.CODE_ID, clientSession.getId())
-                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
-                .detail(Details.RESPONSE_TYPE, "code");
-
-        UserSessionModel userSession = clientSession.getUserSession();
-
-        if (userSession != null) {
-            event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
-            event.detail(Details.USERNAME, userSession.getLoginUsername());
-            if (userSession.isRememberMe()) {
-                event.detail(Details.REMEMBER_ME, "true");
-            }
-        }
-    }
-}
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, 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.
+ */
+package org.keycloak.services.resources;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.ClientConnection;
+import org.keycloak.email.EmailException;
+import org.keycloak.email.EmailProvider;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RequiredCredentialModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserModel.RequiredAction;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.FormMessage;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.TimeBasedOTP;
+import org.keycloak.protocol.LoginProtocol;
+import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
+import org.keycloak.protocol.oidc.TokenManager;
+import org.keycloak.representations.PasswordToken;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.messages.Messages;
+import org.keycloak.services.resources.flows.Flows;
+import org.keycloak.services.resources.flows.Urls;
+import org.keycloak.services.util.CookieHelper;
+import org.keycloak.services.validation.Validation;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+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.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LoginActionsService {
+
+    protected static final Logger logger = Logger.getLogger(LoginActionsService.class);
+
+    public static final String ACTION_COOKIE = "KEYCLOAK_ACTION";
+
+    private RealmModel realm;
+
+    @Context
+    private HttpRequest request;
+
+    @Context
+    protected HttpHeaders headers;
+
+    @Context
+    private UriInfo uriInfo;
+
+    @Context
+    private ClientConnection clientConnection;
+
+    @Context
+    protected Providers providers;
+
+    @Context
+    protected KeycloakSession session;
+
+    private AuthenticationManager authManager;
+
+    private EventBuilder event;
+
+    public static UriBuilder loginActionsBaseUrl(UriInfo uriInfo) {
+        UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
+        return loginActionsBaseUrl(baseUriBuilder);
+    }
+
+    public static UriBuilder loginActionsBaseUrl(UriBuilder baseUriBuilder) {
+        return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getLoginActionsService");
+    }
+
+    public static UriBuilder processLoginUrl(UriInfo uriInfo) {
+        UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
+        return processLoginUrl(baseUriBuilder);
+    }
+
+    public static UriBuilder processLoginUrl(UriBuilder baseUriBuilder) {
+        UriBuilder uriBuilder = loginActionsBaseUrl(baseUriBuilder);
+        return uriBuilder.path(OIDCLoginProtocolService.class, "processLogin");
+    }
+
+    public static UriBuilder processOAuthUrl(UriInfo uriInfo) {
+        UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
+        return processOAuthUrl(baseUriBuilder);
+    }
+
+    public static UriBuilder processOAuthUrl(UriBuilder baseUriBuilder) {
+        UriBuilder uriBuilder = loginActionsBaseUrl(baseUriBuilder);
+        return uriBuilder.path(OIDCLoginProtocolService.class, "processOAuth");
+    }
+
+    public LoginActionsService(RealmModel realm, AuthenticationManager authManager, EventBuilder event) {
+        this.realm = realm;
+        this.authManager = authManager;
+        this.event = event;
+    }
+
+    private boolean checkSsl() {
+        if (uriInfo.getBaseUri().getScheme().equals("https")) {
+            return true;
+        } else {
+            return !realm.getSslRequired().isRequired(clientConnection);
+        }
+    }
+
+
+    private class Checks {
+        ClientSessionCode clientCode;
+        Response response;
+
+        boolean check(String code, ClientSessionModel.Action requiredAction) {
+            if (!check(code)) {
+                return false;
+            } else if (!clientCode.isValid(requiredAction)) {
+                event.error(Errors.INVALID_CODE);
+                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_CODE);
+                return false;
+            } else {
+                return true;
+            }
+        }
+
+        boolean check(String code, ClientSessionModel.Action requiredAction, ClientSessionModel.Action alternativeRequiredAction) {
+            if (!check(code)) {
+                return false;
+            } else if (!(clientCode.isValid(requiredAction) || clientCode.isValid(alternativeRequiredAction))) {
+                event.error(Errors.INVALID_CODE);
+                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo,headers, Messages.INVALID_CODE);
+                return false;
+            } else {
+                return true;
+            }
+        }
+
+        public boolean check(String code) {
+            if (!checkSsl()) {
+                event.error(Errors.SSL_REQUIRED);
+                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
+                return false;
+            }
+            if (!realm.isEnabled()) {
+                event.error(Errors.REALM_DISABLED);
+                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
+                return false;
+            }
+            clientCode = ClientSessionCode.parse(code, session, realm);
+            if (clientCode == null) {
+                event.error(Errors.INVALID_CODE);
+                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
+                return false;
+            }
+            return true;
+        }
+    }
+
+    /**
+     * protocol independent login page entry point
+     *
+     *
+     * @param code
+     * @return
+     */
+    @Path("login")
+    @GET
+    public Response loginPage(@QueryParam("code") String code) {
+        event.event(EventType.LOGIN);
+        Checks checks = new Checks();
+        if (!checks.check(code)) {
+            return checks.response;
+        }
+        event.detail(Details.CODE_ID, code);
+        ClientSessionCode clientSessionCode = checks.clientCode;
+        ClientSessionModel clientSession = clientSessionCode.getClientSession();
+
+        if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD)) {
+            TokenManager.dettachClientSession(session.sessions(), realm, clientSession);
+            clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
+        }
+
+        LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers)
+                .setClientSessionCode(clientSessionCode.getCode());
+
+        return forms.createLogin();
+    }
+
+    /**
+     * protocol independent registration page entry point
+     *
+     * @param code
+     * @return
+     */
+    @Path("registration")
+    @GET
+    public Response registerPage(@QueryParam("code") String code) {
+        event.event(EventType.REGISTER);
+        if (!realm.isRegistrationAllowed()) {
+            event.error(Errors.REGISTRATION_DISABLED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REGISTRATION_NOT_ALLOWED);
+        }
+
+        Checks checks = new Checks();
+        if (!checks.check(code)) {
+            return checks.response;
+        }
+        event.detail(Details.CODE_ID, code);
+        ClientSessionCode clientSessionCode = checks.clientCode;
+        ClientSessionModel clientSession = clientSessionCode.getClientSession();
+
+
+        authManager.expireIdentityCookie(realm, uriInfo, clientConnection);
+
+        return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers)
+                .setClientSessionCode(clientSessionCode.getCode())
+                .createRegistration();
+    }
+
+    /**
+     * URL called after login page.  YOU SHOULD NEVER INVOKE THIS DIRECTLY!
+     *
+     * @param code
+     * @param formData
+     * @return
+     */
+    @Path("request/login")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response processLogin(@QueryParam("code") String code,
+                                 final MultivaluedMap<String, String> formData) {
+        event.event(EventType.LOGIN);
+        if (!checkSsl()) {
+            event.error(Errors.SSL_REQUIRED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
+        }
+
+        if (!realm.isEnabled()) {
+            event.error(Errors.REALM_DISABLED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
+        }
+        ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm);
+        if (clientCode == null) {
+            event.error(Errors.INVALID_CODE);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
+        }
+
+        ClientSessionModel clientSession = clientCode.getClientSession();
+        event.detail(Details.CODE_ID, clientSession.getId());
+
+        if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE) || clientSession.getUserSession() != null) {
+            clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE);
+            event.client(clientSession.getClient()).error(Errors.EXPIRED_CODE);
+            return Flows.forms(this.session, realm, clientSession.getClient(), uriInfo, headers).setError(Messages.EXPIRED_CODE)
+                    .setClientSessionCode(clientCode.getCode())
+                    .createLogin();
+        }
+
+        String username = formData.getFirst(AuthenticationManager.FORM_USERNAME);
+
+        String rememberMe = formData.getFirst("rememberMe");
+        boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on");
+
+        event.client(clientSession.getClient().getClientId())
+                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
+                .detail(Details.RESPONSE_TYPE, "code")
+                .detail(Details.AUTH_METHOD, "form")
+                .detail(Details.USERNAME, username);
+
+        if (remember) {
+            event.detail(Details.REMEMBER_ME, "true");
+        }
+
+
+        ClientModel client = clientSession.getClient();
+        if (client == null) {
+            event.error(Errors.CLIENT_NOT_FOUND);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER);
+        }
+        if (!client.isEnabled()) {
+            event.error(Errors.CLIENT_NOT_FOUND);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED);
+        }
+
+        if (formData.containsKey("cancel")) {
+            event.error(Errors.REJECTED_BY_USER);
+            LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
+            protocol.setRealm(realm)
+                    .setHttpHeaders(headers)
+                    .setUriInfo(uriInfo);
+            return protocol.cancelLogin(clientSession);
+        }
+
+        AuthenticationManager.AuthenticationStatus status = authManager.authenticateForm(session, clientConnection, realm, formData);
+
+        if (remember) {
+            authManager.createRememberMeCookie(realm, username, uriInfo, clientConnection);
+        } else {
+            authManager.expireRememberMeCookie(realm, uriInfo, clientConnection);
+        }
+
+        UserModel user = KeycloakModelUtils.findUserByNameOrEmail(session, realm, username);
+        if (user != null) {
+            event.user(user);
+        }
+
+        switch (status) {
+            case SUCCESS:
+            case ACTIONS_REQUIRED:
+                UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", remember, null, null);
+                TokenManager.attachClientSession(userSession, clientSession);
+                event.session(userSession);
+                return authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
+            case ACCOUNT_TEMPORARILY_DISABLED:
+                event.error(Errors.USER_TEMPORARILY_DISABLED);
+                return Flows.forms(this.session, realm, client, uriInfo, headers)
+                        .setError(Messages.ACCOUNT_TEMPORARILY_DISABLED)
+                        .setFormData(formData)
+                        .setClientSessionCode(clientCode.getCode())
+                        .createLogin();
+            case ACCOUNT_DISABLED:
+                event.error(Errors.USER_DISABLED);
+                return Flows.forms(this.session, realm, client, uriInfo, headers)
+                        .setError(Messages.ACCOUNT_DISABLED)
+                        .setClientSessionCode(clientCode.getCode())
+                        .setFormData(formData).createLogin();
+            case MISSING_TOTP:
+                formData.remove(CredentialRepresentation.PASSWORD);
+
+                String passwordToken = new JWSBuilder().jsonContent(new PasswordToken(realm.getName(), user.getId())).rsa256(realm.getPrivateKey());
+                formData.add(CredentialRepresentation.PASSWORD_TOKEN, passwordToken);
+
+                return Flows.forms(this.session, realm, client, uriInfo, headers)
+                        .setFormData(formData)
+                        .setClientSessionCode(clientCode.getCode())
+                        .createLoginTotp();
+            case INVALID_USER:
+                event.error(Errors.USER_NOT_FOUND);
+                return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.INVALID_USER)
+                        .setFormData(formData)
+                        .setClientSessionCode(clientCode.getCode())
+                        .createLogin();
+            default:
+                event.error(Errors.INVALID_USER_CREDENTIALS);
+                return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.INVALID_USER)
+                        .setFormData(formData)
+                        .setClientSessionCode(clientCode.getCode())
+                        .createLogin();
+        }
+    }
+
+    /**
+     * Registration
+     *
+     * @param code
+     * @param formData
+     * @return
+     */
+    @Path("request/registration")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response processRegister(@QueryParam("code") String code,
+                                    final MultivaluedMap<String, String> formData) {
+        event.event(EventType.REGISTER);
+        if (!checkSsl()) {
+            event.error(Errors.SSL_REQUIRED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
+        }
+
+        if (!realm.isEnabled()) {
+            event.error(Errors.REALM_DISABLED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
+        }
+        if (!realm.isRegistrationAllowed()) {
+            event.error(Errors.REGISTRATION_DISABLED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REGISTRATION_NOT_ALLOWED);
+        }
+        ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm);
+        if (clientCode == null) {
+            event.error(Errors.INVALID_CODE);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
+        }
+        if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) {
+            event.error(Errors.INVALID_CODE);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_CODE);
+        }
+
+        String username = formData.getFirst(Validation.FIELD_USERNAME);
+        String email = formData.getFirst(Validation.FIELD_EMAIL);
+        if (realm.isRegistrationEmailAsUsername()) {
+            username = email;
+            formData.putSingle(AuthenticationManager.FORM_USERNAME, username);
+        }
+        ClientSessionModel clientSession = clientCode.getClientSession();
+        event.client(clientSession.getClient())
+                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
+                .detail(Details.RESPONSE_TYPE, "code")
+                .detail(Details.USERNAME, username)
+                .detail(Details.EMAIL, email)
+                .detail(Details.REGISTER_METHOD, "form");
+
+        if (!realm.isEnabled()) {
+            event.error(Errors.REALM_DISABLED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
+        }
+        ClientModel client = clientSession.getClient();
+        if (client == null) {
+            event.error(Errors.CLIENT_NOT_FOUND);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER);
+        }
+
+        if (!client.isEnabled()) {
+            event.error(Errors.CLIENT_DISABLED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED);
+        }
+
+
+        List<String> requiredCredentialTypes = new LinkedList<String>();
+        for (RequiredCredentialModel m : realm.getRequiredCredentials()) {
+            requiredCredentialTypes.add(m.getType());
+        }
+
+        // Validate here, so user is not created if password doesn't validate to passwordPolicy of current realm
+        List<FormMessage> errors = Validation.validateRegistrationForm(realm, formData, requiredCredentialTypes, realm.getPasswordPolicy());
+
+        if (errors != null && !errors.isEmpty()) {
+            event.error(Errors.INVALID_REGISTRATION);
+            return Flows.forms(session, realm, client, uriInfo, headers)
+                    .setErrors(errors)
+                    .setFormData(formData)
+                    .setClientSessionCode(clientCode.getCode())
+                    .createRegistration();
+        }
+
+        // Validate that user with this username doesn't exist in realm or any federation provider
+        if (session.users().getUserByUsername(username, realm) != null) {
+            event.error(Errors.USERNAME_IN_USE);
+            return Flows.forms(session, realm, client, uriInfo, headers)
+                    .setError(Messages.USERNAME_EXISTS)
+                    .setFormData(formData)
+                    .setClientSessionCode(clientCode.getCode())
+                    .createRegistration();
+        }
+
+        // Validate that user with this email doesn't exist in realm or any federation provider
+        if (email != null && session.users().getUserByEmail(email, realm) != null) {
+            event.error(Errors.EMAIL_IN_USE);
+            return Flows.forms(session, realm, client, uriInfo, headers)
+                    .setError(Messages.EMAIL_EXISTS)
+                    .setFormData(formData)
+                    .setClientSessionCode(clientCode.getCode())
+                    .createRegistration();
+        }
+
+        UserModel user = session.users().addUser(realm, username);
+        user.setEnabled(true);
+        user.setFirstName(formData.getFirst("firstName"));
+        user.setLastName(formData.getFirst("lastName"));
+
+        user.setEmail(email);
+
+        if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) {
+            UserCredentialModel credentials = new UserCredentialModel();
+            credentials.setType(CredentialRepresentation.PASSWORD);
+            credentials.setValue(formData.getFirst("password"));
+
+            boolean passwordUpdateSuccessful;
+            String passwordUpdateError = null;
+            Object[] passwordUpdateErrorParameters = null;
+            try {
+                session.users().updateCredential(realm, user, UserCredentialModel.password(formData.getFirst("password")));
+                passwordUpdateSuccessful = true;
+            } catch (ModelException me) {
+                passwordUpdateSuccessful = false;
+                passwordUpdateError = me.getMessage();
+                passwordUpdateErrorParameters = me.getParameters();
+            } catch (Exception ape) {
+                passwordUpdateSuccessful = false;
+                passwordUpdateError = ape.getMessage();
+            }
+
+            // User already registered, but force him to update password
+            if (!passwordUpdateSuccessful) {
+                user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
+                return Flows.forms(session, realm, client, uriInfo, headers)
+                        .setError(passwordUpdateError, passwordUpdateErrorParameters)
+                        .setClientSessionCode(clientCode.getCode())
+                        .createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
+            }
+        }
+
+        AttributeFormDataProcessor.process(formData, realm, user);
+
+        event.user(user).success();
+        event = new EventBuilder(realm, session, clientConnection);
+
+        return processLogin(code, formData);
+    }
+
+    /**
+     * OAuth grant page.  You should not invoked this directly!
+     *
+     * @param formData
+     * @return
+     */
+    @Path("consent")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response processConsent(final MultivaluedMap<String, String> formData) {
+        event.event(EventType.LOGIN).detail(Details.RESPONSE_TYPE, "code");
+
+
+        if (!checkSsl()) {
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
+        }
+
+        String code = formData.getFirst("code");
+
+        ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
+        if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT)) {
+            event.error(Errors.INVALID_CODE);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_ACCESS_CODE);
+        }
+        ClientSessionModel clientSession = accessCode.getClientSession();
+        event.detail(Details.CODE_ID, clientSession.getId());
+
+        String redirect = clientSession.getRedirectUri();
+
+        event.client(clientSession.getClient())
+                .user(clientSession.getUserSession().getUser())
+                .detail(Details.RESPONSE_TYPE, "code")
+                .detail(Details.REDIRECT_URI, redirect);
+
+        UserSessionModel userSession = clientSession.getUserSession();
+        if (userSession != null) {
+            event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
+            event.detail(Details.USERNAME, userSession.getLoginUsername());
+            if (userSession.isRememberMe()) {
+                event.detail(Details.REMEMBER_ME, "true");
+            }
+        }
+
+        if (!AuthenticationManager.isSessionValid(realm, userSession)) {
+            AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
+            event.error(Errors.INVALID_CODE);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
+        }
+        event.session(userSession);
+
+        LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
+        protocol.setRealm(realm)
+                .setHttpHeaders(headers)
+                .setUriInfo(uriInfo);
+        if (formData.containsKey("cancel")) {
+            event.error(Errors.REJECTED_BY_USER);
+            return protocol.consentDenied(clientSession);
+        }
+
+        event.success();
+
+        return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection);
+    }
+
+    @Path("profile")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response updateProfile(@QueryParam("code") String code,
+                                  final MultivaluedMap<String, String> formData) {
+        event.event(EventType.UPDATE_PROFILE);
+        Checks checks = new Checks();
+        if (!checks.check(code, ClientSessionModel.Action.UPDATE_PROFILE)) {
+            return checks.response;
+        }
+        ClientSessionCode accessCode = checks.clientCode;
+        ClientSessionModel clientSession = accessCode.getClientSession();
+        UserSessionModel userSession = clientSession.getUserSession();
+        UserModel user = userSession.getUser();
+
+        initEvent(clientSession);
+
+        List<FormMessage> errors = Validation.validateUpdateProfileForm(formData);
+        if (errors != null && !errors.isEmpty()) {
+            return Flows.forms(session, realm, null, uriInfo, headers).setUser(user).setErrors(errors)
+                    .setClientSessionCode(accessCode.getCode())
+                    .createResponse(RequiredAction.UPDATE_PROFILE);
+        }
+
+        user.setFirstName(formData.getFirst("firstName"));
+        user.setLastName(formData.getFirst("lastName"));
+
+        String email = formData.getFirst("email");
+
+        String oldEmail = user.getEmail();
+        boolean emailChanged = oldEmail != null ? !oldEmail.equals(email) : email != null;
+
+        if (emailChanged) {
+            UserModel userByEmail = session.users().getUserByEmail(email, realm);
+
+            // check for duplicated email
+            if (userByEmail != null && !userByEmail.getId().equals(user.getId())) {
+                return Flows.forms(session, realm, null, uriInfo, headers).setUser(user).setError(Messages.EMAIL_EXISTS)
+                        .setClientSessionCode(accessCode.getCode())
+                        .createResponse(RequiredAction.UPDATE_PROFILE);
+            }
+
+            user.setEmail(email);
+            user.setEmailVerified(false);
+        }
+
+        user.removeRequiredAction(RequiredAction.UPDATE_PROFILE);
+        event.clone().event(EventType.UPDATE_PROFILE).success();
+
+        if (emailChanged) {
+            event.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
+        }
+
+        return redirectOauth(user, accessCode, clientSession, userSession);
+    }
+
+    @Path("totp")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response updateTotp(@QueryParam("code") String code,
+                               final MultivaluedMap<String, String> formData) {
+        event.event(EventType.UPDATE_TOTP);
+        Checks checks = new Checks();
+        if (!checks.check(code, ClientSessionModel.Action.CONFIGURE_TOTP)) {
+            return checks.response;
+        }
+        ClientSessionCode accessCode = checks.clientCode;
+        ClientSessionModel clientSession = accessCode.getClientSession();
+        UserSessionModel userSession = clientSession.getUserSession();
+        UserModel user = userSession.getUser();
+
+        initEvent(clientSession);
+
+        String totp = formData.getFirst("totp");
+        String totpSecret = formData.getFirst("totpSecret");
+
+        LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo, headers).setUser(user);
+        if (Validation.isEmpty(totp)) {
+            return loginForms.setError(Messages.MISSING_TOTP)
+                    .setClientSessionCode(accessCode.getCode())
+                    .createResponse(RequiredAction.CONFIGURE_TOTP);
+        } else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
+            return loginForms.setError(Messages.INVALID_TOTP)
+                    .setClientSessionCode(accessCode.getCode())
+                    .createResponse(RequiredAction.CONFIGURE_TOTP);
+        }
+
+        UserCredentialModel credentials = new UserCredentialModel();
+        credentials.setType(CredentialRepresentation.TOTP);
+        credentials.setValue(totpSecret);
+        session.users().updateCredential(realm, user, credentials);
+
+        user.setTotp(true);
+
+        user.removeRequiredAction(RequiredAction.CONFIGURE_TOTP);
+
+        event.clone().event(EventType.UPDATE_TOTP).success();
+
+        return redirectOauth(user, accessCode, clientSession, userSession);
+    }
+
+    @Path("password")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response updatePassword(@QueryParam("code") String code,
+                                   final MultivaluedMap<String, String> formData) {
+        event.event(EventType.UPDATE_PASSWORD);
+        Checks checks = new Checks();
+        if (!checks.check(code, ClientSessionModel.Action.UPDATE_PASSWORD, ClientSessionModel.Action.RECOVER_PASSWORD)) {
+            return checks.response;
+        }
+        ClientSessionCode accessCode = checks.clientCode;
+        ClientSessionModel clientSession = accessCode.getClientSession();
+        UserSessionModel userSession = clientSession.getUserSession();
+        UserModel user = userSession.getUser();
+
+        initEvent(clientSession);
+
+        String passwordNew = formData.getFirst("password-new");
+        String passwordConfirm = formData.getFirst("password-confirm");
+
+        LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo, headers).setUser(user);
+        if (Validation.isEmpty(passwordNew)) {
+            return loginForms.setError(Messages.MISSING_PASSWORD)
+                    .setClientSessionCode(accessCode.getCode())
+                    .createResponse(RequiredAction.UPDATE_PASSWORD);
+        } else if (!passwordNew.equals(passwordConfirm)) {
+            return loginForms.setError(Messages.NOTMATCH_PASSWORD)
+                    .setClientSessionCode(accessCode.getCode())
+                    .createResponse(RequiredAction.UPDATE_PASSWORD);
+        }
+
+        try {
+            session.users().updateCredential(realm, user, UserCredentialModel.password(passwordNew));
+        } catch (ModelException me) {
+            return loginForms.setError(me.getMessage(), me.getParameters())
+                    .setClientSessionCode(accessCode.getCode())
+                    .createResponse(RequiredAction.UPDATE_PASSWORD);
+        } catch (Exception ape) {
+            return loginForms.setError(ape.getMessage())
+                    .setClientSessionCode(accessCode.getCode())
+                    .createResponse(RequiredAction.UPDATE_PASSWORD);
+        }
+
+        user.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
+
+        event.event(EventType.UPDATE_PASSWORD).success();
+
+        if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD)) {
+            String actionCookieValue = getActionCookie();
+            if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) {
+                return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers).setSuccess(Messages.ACCOUNT_PASSWORD_UPDATED).createInfoPage();
+            }
+        }
+
+        event = event.clone().event(EventType.LOGIN);
+
+        return redirectOauth(user, accessCode, clientSession, userSession);
+    }
+
+
+    @Path("email-verification")
+    @GET
+    public Response emailVerification(@QueryParam("code") String code, @QueryParam("key") String key) {
+        event.event(EventType.VERIFY_EMAIL);
+        if (key != null) {
+            Checks checks = new Checks();
+            if (!checks.check(key, ClientSessionModel.Action.VERIFY_EMAIL)) {
+                return checks.response;
+            }
+            ClientSessionCode accessCode = checks.clientCode;
+            ClientSessionModel clientSession = accessCode.getClientSession();
+            UserSessionModel userSession = clientSession.getUserSession();
+            UserModel user = userSession.getUser();
+            initEvent(clientSession);
+            user.setEmailVerified(true);
+
+            user.removeRequiredAction(RequiredAction.VERIFY_EMAIL);
+
+            event.event(EventType.VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
+
+            String actionCookieValue = getActionCookie();
+            if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) {
+                return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers).setSuccess(Messages.EMAIL_VERIFIED).createInfoPage();
+            }
+
+            event = event.clone().removeDetail(Details.EMAIL).event(EventType.LOGIN);
+
+            return redirectOauth(user, accessCode, clientSession, userSession);
+        } else {
+            Checks checks = new Checks();
+            if (!checks.check(code, ClientSessionModel.Action.VERIFY_EMAIL)) {
+                return checks.response;
+            }
+            ClientSessionCode accessCode = checks.clientCode;
+            ClientSessionModel clientSession = accessCode.getClientSession();
+            UserSessionModel userSession = clientSession.getUserSession();
+            initEvent(clientSession);
+
+            createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
+
+            return Flows.forms(session, realm, null, uriInfo, headers)
+                    .setClientSessionCode(accessCode.getCode())
+                    .setUser(userSession.getUser())
+                    .createResponse(RequiredAction.VERIFY_EMAIL);
+        }
+    }
+
+    @Path("password-reset")
+    @GET
+    public Response passwordReset(@QueryParam("code") String code, @QueryParam("key") String key) {
+        event.event(EventType.RESET_PASSWORD);
+        if (key != null) {
+            Checks checks = new Checks();
+            if (!checks.check(key, ClientSessionModel.Action.RECOVER_PASSWORD)) {
+                return checks.response;
+            }
+            ClientSessionCode accessCode = checks.clientCode;
+            return Flows.forms(session, realm, null, uriInfo, headers)
+                    .setClientSessionCode(accessCode.getCode())
+                    .createResponse(RequiredAction.UPDATE_PASSWORD);
+        } else {
+            return Flows.forms(session, realm, null, uriInfo, headers)
+                    .setClientSessionCode(code)
+                    .createPasswordReset();
+        }
+    }
+
+    @Path("password-reset")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response sendPasswordReset(@QueryParam("code") String code,
+                                      final MultivaluedMap<String, String> formData) {
+        event.event(EventType.SEND_RESET_PASSWORD);
+        if (!checkSsl()) {
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
+        }
+        if (!realm.isEnabled()) {
+            event.error(Errors.REALM_DISABLED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
+        }
+        ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
+        if (accessCode == null) {
+            event.error(Errors.INVALID_CODE);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
+        }
+        ClientSessionModel clientSession = accessCode.getClientSession();
+
+        String username = formData.getFirst("username");
+
+        ClientModel client = clientSession.getClient();
+        if (client == null) {
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER);
+        }
+        if (!client.isEnabled()) {
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED);
+        }
+
+        event.client(client.getClientId())
+                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
+                .detail(Details.RESPONSE_TYPE, "code")
+                .detail(Details.AUTH_METHOD, "form")
+                .detail(Details.USERNAME, username);
+
+        UserModel user = session.users().getUserByUsername(username, realm);
+        if (user == null && username.contains("@")) {
+            user = session.users().getUserByEmail(username, realm);
+        }
+
+        if (user == null) {
+            event.error(Errors.USER_NOT_FOUND);
+        } else if(!user.isEnabled()) {
+            event.user(user).error(Errors.USER_DISABLED);
+        }
+        else if(user.getEmail() == null || user.getEmail().trim().length() == 0) {
+            event.user(user).error(Errors.INVALID_EMAIL);
+        } else{
+            event.user(user);
+
+            UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", false, null, null);
+            event.session(userSession);
+            TokenManager.attachClientSession(userSession, clientSession);
+
+            accessCode.setAction(ClientSessionModel.Action.RECOVER_PASSWORD);
+
+            try {
+                UriBuilder builder = Urls.loginPasswordResetBuilder(uriInfo.getBaseUri());
+                builder.queryParam("key", accessCode.getCode());
+
+                String link = builder.build(realm.getName()).toString();
+                long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
+
+                this.session.getProvider(EmailProvider.class).setRealm(realm).setUser(user).sendPasswordReset(link, expiration);
+
+                event.detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, clientSession.getId()).success();
+            } catch (EmailException e) {
+                event.error(Errors.EMAIL_SEND_FAILED);
+                logger.error("Failed to send password reset email", e);
+                return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.EMAIL_SENT_ERROR)
+                        .setClientSessionCode(accessCode.getCode())
+                        .createErrorPage();
+            }
+
+            createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
+        }
+
+        return Flows.forms(session, realm, client,  uriInfo, headers).setSuccess(Messages.EMAIL_SENT).setClientSessionCode(accessCode.getCode()).createPasswordReset();
+    }
+
+    private String getActionCookie() {
+        Cookie cookie = headers.getCookies().get(ACTION_COOKIE);
+        AuthenticationManager.expireCookie(realm, ACTION_COOKIE, AuthenticationManager.getRealmCookiePath(realm, uriInfo), realm.getSslRequired().isRequired(clientConnection), clientConnection);
+        return cookie != null ? cookie.getValue() : null;
+    }
+
+    public static void createActionCookie(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, String sessionId) {
+        CookieHelper.addCookie(ACTION_COOKIE, sessionId, AuthenticationManager.getRealmCookiePath(realm, uriInfo), null, null, -1, realm.getSslRequired().isRequired(clientConnection), true);
+    }
+
+    private Response redirectOauth(UserModel user, ClientSessionCode accessCode, ClientSessionModel clientSession, UserSessionModel userSession) {
+        return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
+    }
+
+    private void initEvent(ClientSessionModel clientSession) {
+        event.event(EventType.LOGIN).client(clientSession.getClient())
+                .user(clientSession.getUserSession().getUser())
+                .session(clientSession.getUserSession().getId())
+                .detail(Details.CODE_ID, clientSession.getId())
+                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
+                .detail(Details.RESPONSE_TYPE, "code");
+
+        UserSessionModel userSession = clientSession.getUserSession();
+
+        if (userSession != null) {
+            event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
+            event.detail(Details.USERNAME, userSession.getLoginUsername());
+            if (userSession.isRememberMe()) {
+                event.detail(Details.REMEMBER_ME, "true");
+            }
+        }
+    }
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index 9d4325c..daed1e0 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -113,7 +113,6 @@ public class RealmsResource {
         Object endpoint = factory.createProtocolEndpoint(realm, event, authManager);
 
         ResteasyProviderFactory.getInstance().injectProperties(endpoint);
-        //resourceContext.initResource(tokenService);
         return endpoint;
     }
 
@@ -132,8 +131,6 @@ public class RealmsResource {
         AuthenticationManager authManager = new AuthenticationManager(protector);
         LoginActionsService service = new LoginActionsService(realm, authManager, event);
         ResteasyProviderFactory.getInstance().injectProperties(service);
-
-        //resourceContext.initResource(service);
         return service;
     }
 
@@ -147,7 +144,6 @@ public class RealmsResource {
         return service;
     }
 
-
     protected RealmModel locateRealm(String name, RealmManager realmManager) {
         RealmModel realm = realmManager.getRealmByName(name);
         if (realm == null) {
diff --git a/services/src/main/java/org/keycloak/services/validation/Validation.java b/services/src/main/java/org/keycloak/services/validation/Validation.java
index f72a630..1a4392b 100755
--- a/services/src/main/java/org/keycloak/services/validation/Validation.java
+++ b/services/src/main/java/org/keycloak/services/validation/Validation.java
@@ -1,75 +1,90 @@
 package org.keycloak.services.validation;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import javax.ws.rs.core.MultivaluedMap;
+
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.FormMessage;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.messages.Messages;
 
-import javax.ws.rs.core.MultivaluedMap;
-import java.util.List;
-import java.util.regex.Pattern;
-
 public class Validation {
 
+    public static final String FIELD_PASSWORD_CONFIRM = "password-confirm";
+    public static final String FIELD_EMAIL = "email";
+    public static final String FIELD_LAST_NAME = "lastName";
+    public static final String FIELD_FIRST_NAME = "firstName";
+    public static final String FIELD_PASSWORD = "password";
+    public static final String FIELD_USERNAME = "username";
+    
     // Actually allow same emails like angular. See ValidationTest.testEmailValidation()
     private static final Pattern EMAIL_PATTERN = Pattern.compile("[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*");
 
-    public static String validateRegistrationForm(RealmModel realm, MultivaluedMap<String, String> formData, List<String> requiredCredentialTypes) {
-        if (isEmpty(formData.getFirst("firstName"))) {
-            return Messages.MISSING_FIRST_NAME;
-        }
+    public static List<FormMessage> validateRegistrationForm(RealmModel realm, MultivaluedMap<String, String> formData, List<String> requiredCredentialTypes, PasswordPolicy policy) {
+        List<FormMessage> errors = new ArrayList<>();
 
-        if (isEmpty(formData.getFirst("lastName"))) {
-            return Messages.MISSING_LAST_NAME;
+        if (!realm.isRegistrationEmailAsUsername() && isEmpty(formData.getFirst(FIELD_USERNAME))) {
+            addError(errors, FIELD_USERNAME, Messages.MISSING_USERNAME);
         }
 
-        if (isEmpty(formData.getFirst("email"))) {
-            return Messages.MISSING_EMAIL;
+        if (isEmpty(formData.getFirst(FIELD_FIRST_NAME))) {
+            addError(errors, FIELD_FIRST_NAME, Messages.MISSING_FIRST_NAME);
         }
 
-        if (!isEmailValid(formData.getFirst("email"))) {
-            return Messages.INVALID_EMAIL;
+        if (isEmpty(formData.getFirst(FIELD_LAST_NAME))) {
+            addError(errors, FIELD_LAST_NAME, Messages.MISSING_LAST_NAME);
         }
 
-        if (!realm.isRegistrationEmailAsUsername() && isEmpty(formData.getFirst("username"))) {
-            return Messages.MISSING_USERNAME;
+        if (isEmpty(formData.getFirst(FIELD_EMAIL))) {
+            addError(errors, FIELD_EMAIL, Messages.MISSING_EMAIL);
+        } else if (!isEmailValid(formData.getFirst(FIELD_EMAIL))) {
+            addError(errors, FIELD_EMAIL, Messages.INVALID_EMAIL);
         }
 
         if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) {
-            if (isEmpty(formData.getFirst(CredentialRepresentation.PASSWORD))) {
-                return Messages.MISSING_PASSWORD;
-            }
-
-            if (!formData.getFirst("password").equals(formData.getFirst("password-confirm"))) {
-                return Messages.INVALID_PASSWORD_CONFIRM;
+            if (isEmpty(formData.getFirst(FIELD_PASSWORD))) {
+                addError(errors, FIELD_PASSWORD, Messages.MISSING_PASSWORD);
+            } else if (!formData.getFirst(FIELD_PASSWORD).equals(formData.getFirst(FIELD_PASSWORD_CONFIRM))) {
+                addError(errors, FIELD_PASSWORD_CONFIRM, Messages.INVALID_PASSWORD_CONFIRM);
             }
         }
 
-        return null;
+        if (formData.getFirst(FIELD_PASSWORD) != null) {
+            PasswordPolicy.Error err = policy.validate(realm.isRegistrationEmailAsUsername()?formData.getFirst(FIELD_EMAIL):formData.getFirst(FIELD_USERNAME), formData.getFirst(FIELD_PASSWORD));
+            if (err != null)
+                errors.add(new FormMessage(FIELD_PASSWORD, err.getMessage(), err.getParameters()));
+        }
+        
+        return errors;
     }
-
-    public static PasswordPolicy.Error validatePassword(MultivaluedMap<String, String> formData, PasswordPolicy policy) {
-        return policy.validate(formData.getFirst("username"), formData.getFirst("password"));
+    
+    private static void addError(List<FormMessage> errors, String field, String message){
+        errors.add(new FormMessage(field, message));
     }
 
-    public static String validateUpdateProfileForm(MultivaluedMap<String, String> formData) {
-        if (isEmpty(formData.getFirst("firstName"))) {
-            return Messages.MISSING_FIRST_NAME;
-        }
 
-        if (isEmpty(formData.getFirst("lastName"))) {
-            return Messages.MISSING_LAST_NAME;
+    public static List<FormMessage> validateUpdateProfileForm(MultivaluedMap<String, String> formData) {
+        List<FormMessage> errors = new ArrayList<>();
+        
+        if (isEmpty(formData.getFirst(FIELD_FIRST_NAME))) {
+            addError(errors, FIELD_FIRST_NAME, Messages.MISSING_FIRST_NAME);
         }
 
-        if (isEmpty(formData.getFirst("email"))) {
-            return Messages.MISSING_EMAIL;
+        if (isEmpty(formData.getFirst(FIELD_LAST_NAME))) {
+            addError(errors, FIELD_LAST_NAME, Messages.MISSING_LAST_NAME);
         }
 
-        if (!isEmailValid(formData.getFirst("email"))) {
-            return Messages.INVALID_EMAIL;
+        if (isEmpty(formData.getFirst(FIELD_EMAIL))) {
+            addError(errors, FIELD_EMAIL, Messages.MISSING_EMAIL);
+        } else if (!isEmailValid(formData.getFirst(FIELD_EMAIL))) {
+            addError(errors, FIELD_EMAIL, Messages.INVALID_EMAIL);
         }
 
-        return null;
+        return errors;
     }
 
     public static boolean isEmpty(String s) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
index 8c5a2f8..e645ca3 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -50,6 +50,9 @@ import javax.ws.rs.client.ClientBuilder;
 import javax.ws.rs.core.Response;
 import java.util.Map;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -199,6 +202,25 @@ public class LoginTest {
     }
 
     @Test
+    public void loginPromptNone() {
+        driver.navigate().to(oauth.getLoginFormUrl().toString() + "&prompt=none");
+
+        assertFalse(loginPage.isCurrent());
+        assertTrue(appPage.isCurrent());
+
+        loginPage.open();
+        loginPage.login("login-test", "password");
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
+
+        driver.navigate().to(oauth.getLoginFormUrl().toString() + "&prompt=none");
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        events.expectLogin().user(userId).removeDetail(Details.USERNAME).detail(Details.AUTH_METHOD, "sso").assertEvent();
+    }
+
+    @Test
     public void loginNoTimeoutWithLongWait() {
         try {
             loginPage.open();
@@ -264,9 +286,9 @@ public class LoginTest {
 
         try {
             loginPage.open();
-            Assert.assertFalse(loginPage.isRememberMeChecked());
+            assertFalse(loginPage.isRememberMeChecked());
             loginPage.setRememberMe(true);
-            Assert.assertTrue(loginPage.isRememberMeChecked());
+            assertTrue(loginPage.isRememberMeChecked());
             loginPage.login("login-test", "password");
 
             Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
@@ -282,7 +304,7 @@ public class LoginTest {
 
             // Assert rememberMe checked and username/email prefilled
             loginPage.open();
-            Assert.assertTrue(loginPage.isRememberMeChecked());
+            assertTrue(loginPage.isRememberMeChecked());
             Assert.assertEquals("login-test", loginPage.getUsername());
 
             loginPage.setRememberMe(false);