keycloak-aplcache

More design adjustments

10/21/2013 6:04:29 PM

Changes

examples/js/keycloak.js 242(+123 -119)

pom.xml 5(+5 -0)

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

Details

diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/index.html b/admin-ui/src/main/resources/META-INF/resources/admin/index.html
index ab1d359..fbdc853 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/index.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/index.html
@@ -64,8 +64,8 @@
 </div>
 
 
-<div class="alert-container" data-ng-show="notification" data-ng-click="notification = null">
-    <div class="alert alert-{{notification.type}}">{{notification.message}}</div>
+<div class="feedback-aligner" data-ng-show="notification" data-ng-click="notification = null">
+    <div class="alert alert-{{notification.type}}"><strong>{{notification.message}}</strong></div> <!-- Needs to be <div class="feedback success"> to have the original design -->
 </div>
 
 <div id="wrap">
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js
index 8c6f178..b46ca11 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js
@@ -196,6 +196,20 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application, 
         }
     }, true);
 
+    $scope.deleteWebOrigin = function(index) {
+        $scope.application.webOrigins.splice(index, 1);
+    }
+    $scope.addWebOrigin = function() {
+        $scope.application.webOrigins.push($scope.newWebOrigin);
+        $scope.newWebOrigin = "";
+    }
+    $scope.deleteRedirectUri = function(index) {
+        $scope.application.redirectUris.splice(index, 1);
+    }
+    $scope.addRedirectUri = function() {
+        $scope.application.redirectUris.push($scope.newRedirectUri);
+        $scope.newRedirectUri = "";
+    }
 
     $scope.save = function() {
         if ($scope.applicationForm.$valid) {
@@ -208,7 +222,7 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application, 
                     var l = headers().location;
                     var id = l.substring(l.lastIndexOf("/") + 1);
                     $location.url("/realms/" + realm.id + "/applications/" + id);
-                    Notifications.success("Created application");
+                    Notifications.success("The application has been created.");
                 });
             } else {
                 Application.update({
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
index 93a1593..e35f0bc 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
@@ -104,7 +104,7 @@ module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, $ht
                             }
                         }
                         $location.url("/realms/" + id);
-                        Notifications.success("Created realm");
+                        Notifications.success("The realm has been created.");
                     });
                 });
             } else {
@@ -122,7 +122,7 @@ module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, $ht
                         }
                     });
                     $location.url("/realms/" + id);
-                    Notifications.success("Saved changes to realm");
+                    Notifications.success("Your changes have been saved to the realm.");
                 });
             }
         } else {
@@ -300,7 +300,7 @@ module.controller('RoleDetailCtrl', function($scope, realm, role, Role, $locatio
             }, $scope.role, function() {
                 $scope.changed = false;
                 role = angular.copy($scope.role);
-                Notifications.success("Saved changes to role");
+                Notifications.success("Your changes have been saved to the role.");
             });
         }
     };
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/users.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/users.js
index f4e14f9..e925d15 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/users.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/users.js
@@ -151,7 +151,7 @@ module.controller('UserDetailCtrl', function($scope, realm, user, User, $locatio
                 user = angular.copy($scope.user);
 
                 $location.url("/realms/" + realm.id + "/users/" + $scope.user.username);
-                Notifications.success("Created user");
+                Notifications.success("The user has been created.");
             });
         } else {
             User.update({
@@ -160,7 +160,7 @@ module.controller('UserDetailCtrl', function($scope, realm, user, User, $locatio
             }, $scope.user, function () {
                 $scope.changed = false;
                 user = angular.copy($scope.user);
-                Notifications.success("Saved changes to user");
+                Notifications.success("Your changes have been saved to the user.");
             });
 
         }
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-credentials.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-credentials.html
index 0823a47..0f46176 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-credentials.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-credentials.html
@@ -37,7 +37,7 @@
                                        required>
                             </div>
                         </div>
-                        <div class="form-group">
+                        <div class="form-actions">
                             <button type="submit" data-ng-click="changePassword()" class="primary" ng-show="password != null">Save
                             </button>
                         </div>
@@ -53,7 +53,7 @@
                                 </button>
                             </div>
                         </div>
-                        <div class="form-group">
+                        <div class="form-actions">
                             <label></label>
                             <button type="submit" data-ng-click="changeTotp()" class="primary" ng-show="totp != null">Save
                             </button>
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html
index 84e6a14..548b966 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html
@@ -72,6 +72,36 @@
                                        data-ng-model="application.adminUrl">
                             </div>
                         </div>
+                        <div class="form-group">
+                            <label for="newWebOrigin" class="control-label">Web Origin</label>
+                            <div class="controls">
+                                <div ng-repeat="webOrigin in application.webOrigins" class="item-deletable">
+                                    <input  class="input-small" type="text" data-ng-class="{'input-below':!$first}"
+                                            name="webOrigin" id="webOrigin" data-ng-model="webOrigin" readonly />
+                                    <button type="button" data-ng-click="deleteWebOrigin($index)" class="btn-delete">
+                                        Delete</button>
+                                </div>
+                                <input class="input-small" type="text" name="newWebOrigin" id="newWebOrigin"
+                                       placeholder="New Web Origin..." data-ng-model="newWebOrigin"
+                                       data-ng-class="{'input-below':application.webOrigins.length}" />
+                                <button data-ng-click="addWebOrigin()" ng-show="newWebOrigin.length > 0">Add</button>
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <label for="newRedirectUri" class="control-label">Redirect URI</label>
+                            <div class="controls">
+                                <div ng-repeat="redirectUri in application.redirectUris" class="item-deletable">
+                                    <input  class="input-small" type="text" data-ng-class="{'input-below':!$first}"
+                                            name="redirectUri" id="redirectUri" data-ng-model="redirectUri" readonly />
+                                    <button type="button" data-ng-click="deleteRedirectUri($index)" class="btn-delete">
+                                        Delete</button>
+                                </div>
+                                <input class="input-small" type="text" name="newRedirectUri" id="newRedirectUri"
+                                       placeholder="New Redirect URI..." data-ng-model="newRedirectUri"
+                                       data-ng-class="{'input-below':application.redirectUris.length}" />
+                                <button data-ng-click="addRedirectUri()" ng-show="newRedirectUri.length > 0">Add</button>
+                            </div>
+                        </div>
                     </fieldset>
                     <div class="form-actions" data-ng-show="create">
                         <button type="submit" data-ng-click="save()" class="primary">Save
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html
index df80731..d7ccf00 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html
@@ -46,8 +46,10 @@
                             <label for="description">Description </label>
 
                             <div class="controls">
-                                <input type="text" id="description" name="description" data-ng-model="role.description" autofocus
-                                       required>
+                                <textarea rows="5" cols="50" id="description" name="description" data-ng-model="role.description" required></textarea>
+                            
+<!-- Replaced by the textarea above <input type="text" id="description" name="description" data-ng-model="role.description" autofocus
+                                       required> -->
                             </div>
                         </div>
                     </fieldset>
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-credentials.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-credentials.html
index bff58e6..1d4d9ff 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-credentials.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-credentials.html
@@ -21,21 +21,21 @@
                 <form name="realmForm" novalidate>
                     <fieldset class="border-top">
                         <div class="form-group clearfix">
-                            <label for="user" class="control-label">Required User Credentials</label>
+                            <label for="user" class="control-label two-lines">Required User Credentials</label>
 
                             <div class="controls">
                                 <input id="user" type="text" ui-select2="userCredentialOptions" ng-model="realm.requiredCredentials" placeholder="Type a role and enter">
                             </div>
                         </div>
                         <div class="form-group clearfix">
-                            <label for="application" class="control-label">Required Application Credentials</label>
+                            <label for="application" class="control-label two-lines">Required Application Credentials</label>
 
                             <div class="controls">
                                 <input id="application" type="text" ui-select2="userCredentialOptions" ng-model="realm.requiredApplicationCredentials" placeholder="Type a role and enter">
                             </div>
                         </div>
                         <div class="form-group clearfix">
-                            <label for="oauth" class="control-label">Required OAuth Credentials</label>
+                            <label for="oauth" class="control-label two-lines">Required OAuth Credentials</label>
 
                             <div class="controls">
                                 <input id="oauth" type="text" ui-select2="userCredentialOptions" ng-model="realm.requiredOAuthClientCredentials" placeholder="Type a role and enter">
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html
index 0ff5986..69cb23f 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html
@@ -76,6 +76,20 @@
                             </div>
                         </div>
                         <div class="form-group clearfix block">
+                            <label for="accountManagement" class="control-label two-lines">User account management</label>
+                            <div class="onoffswitch">
+                                <input type="checkbox" data-ng-model="realm.accountManagement" class="onoffswitch-checkbox"
+                                       name="accountManagement" id="accountManagement">
+                                <label for="accountManagement" class="onoffswitch-label">
+                                            <span class="onoffswitch-inner">
+                                                <span class="onoffswitch-active">ON</span>
+                                                <span class="onoffswitch-inactive">OFF</span>
+                                            </span>
+                                    <span class="onoffswitch-switch"></span>
+                                </label>
+                            </div>
+                        </div>
+                        <div class="form-group clearfix block">
                             <label for="requireSsl" class="control-label">Require SSL</label>
                             <div class="onoffswitch">
                                 <input type="checkbox" data-ng-model="realm.requireSsl" class="onoffswitch-checkbox" name="requireSsl" id="requireSsl">
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/role-detail.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/role-detail.html
index fc1866b..fc33fc5 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/role-detail.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/role-detail.html
@@ -42,7 +42,6 @@
 
                             <div class="controls">
                                 <textarea rows="5" cols="50" id="description" name="description" data-ng-model="role.description" required></textarea>
-                                <!-- Replaced by the textarea above <input type="text" id="description" name="description" data-ng-model="role.description" required> -->
                             </div>
                         </div>
                     </fieldset>
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.css b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.css
index deac897..fdbc43a 100644
--- a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.css
+++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.css
@@ -131,8 +131,8 @@ body {
 }
 .header.rcue .navbar.primary .nav > li .select-rcue select option {
   background-color: #fff;
-  color: black;
-  padding: 4px 10px;
+  color: #333;
+  padding: 0.36363636363636em 0.90909090909091em;
 }
 .header.rcue .navbar.primary .nav > li a#refresh {
   border: none;
@@ -313,10 +313,6 @@ body {
   top: 70px;
   width: 877.5px;
 }
-/* Page: Realm Users */
-.realm-users caption {
-  display: none;
-}
 table thead tr th {
   font-size: 1.1em;
 }
@@ -332,6 +328,9 @@ table thead tr:first-child th {
 table a:hover {
   color: #0099D3;
 }
+table + .feedback.inline.warning {
+  margin-top: 1em;
+}
 .advanced-search-comp {
   position: relative;
   display: inline-block;
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.less b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.less
index 41c191b..0ce652f 100644
--- a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.less
+++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.less
@@ -68,8 +68,8 @@ body {
                 
                 option {
                     background-color: #fff;
-                    color: black;
-                    padding: 4px 10px;
+                    color: #333;
+                    padding: 0.36363636363636em 0.90909090909091em;
                 }
             }
         }
@@ -376,13 +376,6 @@ body {
     width: 877.5px;
 }
 
-
-/* Page: Realm Users */
-
-.realm-users caption {
-    display: none;
-}
-
 table {
 
     thead tr {
@@ -407,6 +400,10 @@ table {
     a:hover {
         color: #0099D3;
     }
+    
+    + .feedback.inline.warning {
+        margin-top: 1em; 
+    }
 
 }
 
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css
index 8f03892..63caf62 100644
--- a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css
+++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css
@@ -55,6 +55,13 @@ textarea {
   padding: 0.45em 0.545454545454545em;
   height: auto;
 }
+.input-below {
+  clear: both;
+  display: inline-block;
+  margin-left: 10.9090909090909em;
+  margin-top: 0.45454545454545em;
+  padding-left: 3.63636363636364em;
+}
 input[type="button"],
 button,
 a.button {
@@ -654,6 +661,9 @@ input[type="email"].tiny {
   border-bottom: 1px solid #a7d7f1;
   color: #4d5258;
 }
+.select2-container-multi .select2-choices {
+  width: 40em;
+}
 .input-group input + .select-rcue {
   border-radius: 0 2px 2px 0;
   border-left: 0;
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.less b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.less
index e7a6fdf..1643a35 100644
--- a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.less
+++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.less
@@ -56,6 +56,14 @@ textarea {
     height: auto;
 }
 
+.input-below {
+    clear: both;
+    display: inline-block;
+    margin-left: 10.9090909090909em;
+    margin-top: 0.45454545454545em;
+    padding-left: 3.63636363636364em;
+}
+
 input[type="button"],
 button,
 a.button {
@@ -795,6 +803,10 @@ input[type="email"] {
     }
 }
 
+.select2-container-multi .select2-choices  {
+    width: 40em;
+}
+
 .input-group input + .select-rcue {
     border-radius: 0 2px 2px 0;
     border-left: 0;
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/feedback-success-arrow-down.png b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/feedback-success-arrow-down.png
new file mode 100644
index 0000000..4f0d891
Binary files /dev/null and b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/feedback-success-arrow-down.png differ
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/feedback-success-arrow-down.svg b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/feedback-success-arrow-down.svg
new file mode 100644
index 0000000..6c77a90
--- /dev/null
+++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/feedback-success-arrow-down.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="1000px" height="11px" viewBox="0 0 1000 11" enable-background="new 0 0 1000 11" xml:space="preserve">
+<path fill="#E4F1E1" d="M1000,0c0,1.104-0.896,2-2,2H38l-8,9l-8-9H2C0.896,2,0,1.104,0,0"/>
+<path fill="#4B9E39" d="M999,0c0,0.551-0.448,1-1,1H38h-0.449l-0.298,0.335L30,9.495l-7.253-8.159L22.449,1H22H2
+	C1.449,1,1,0.551,1,0 M0,0c0,1.104,0.896,2,2,2h20l8,9l8-9h960c1.104,0,2-0.896,2-2"/>
+</svg>
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/feedback-success-sign.png b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/feedback-success-sign.png
new file mode 100644
index 0000000..182352b
Binary files /dev/null and b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/feedback-success-sign.png differ
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/feedback-success-sign.svg b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/feedback-success-sign.svg
new file mode 100644
index 0000000..c75400c
--- /dev/null
+++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/feedback-success-sign.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="17px" height="17px" viewBox="0 0 17 17" enable-background="new 0 0 17 17" xml:space="preserve">
+<path fill="#58A846" d="M12.524,4.654l-5.472,5.893L4.57,8.022L3.145,9.425l3.222,3.276C6.555,12.893,6.811,13,7.079,13
+	c0.006,0,0.013,0,0.019,0c0.273-0.005,0.535-0.122,0.72-0.325L14,6.003L12.524,4.654z"/>
+</svg>
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/feedback-warning-sign.svg b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/feedback-warning-sign.svg
new file mode 100644
index 0000000..1254ab4
--- /dev/null
+++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/feedback-warning-sign.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" id="svg7384" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="18px" height="17px" viewBox="0 0 18 17" enable-background="new 0 0 18 17" xml:space="preserve">
+<g>
+	<path fill="#EE7700" d="M17.077,12.955L10.745,1.39c-0.518-0.838-1.271-1.343-2.064-1.387L8.572,0C7.773,0,7.057,0.478,6.54,1.364
+		L0.295,12.951c-0.41,0.738-0.391,1.695,0.047,2.439c0.381,0.646,1.002,1.02,1.703,1.02H15.2c0.77,0,1.498-0.436,1.902-1.137
+		C17.521,14.551,17.507,13.701,17.077,12.955z M1.61,13.672L7.847,2.099c0.131-0.224,0.396-0.598,0.75-0.598l0,0
+		c0.295,0.016,0.621,0.27,0.853,0.643l6.32,11.546c0.213,0.369,0.135,0.658,0.033,0.834c-0.133,0.23-0.377,0.387-0.604,0.387H2.045
+		c-0.203,0-0.336-0.154-0.411-0.281C1.492,14.387,1.428,14,1.61,13.672z"/>
+	<path fill="#EE7700" d="M8.732,10.672c0.527,0,1.007-0.486,1-1.014V5.72c0.018-0.544-0.486-1.048-1.031-1.031
+		C8.648,4.696,8.595,4.706,8.544,4.72c-0.459,0.087-0.82,0.533-0.812,1v3.938C7.725,10.186,8.205,10.672,8.732,10.672z"/>
+	<rect x="7.732" y="11.658" fill="#EE7700" width="1.999" height="2"/>
+</g>
+</svg>
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/login-register-email-separator.svg b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/login-register-email-separator.svg
new file mode 100644
index 0000000..ee8c166
--- /dev/null
+++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/login-register-email-separator.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="324px" height="400px" viewBox="0 0 324 400" enable-background="new 0 0 324 400" xml:space="preserve">
+<rect x="6.001" opacity="0.07" fill="#FFFFFF" enable-background="new    " width="0.997" height="190"/>
+<rect x="6" y="209" opacity="0.07" fill="#FFFFFF" enable-background="new    " width="1" height="191"/>
+<g opacity="0.15">
+	<path fill="#FFFFFF" d="M6.501,200.066c0,1.047-0.264,1.864-0.791,2.452S4.454,203.4,3.524,203.4c-0.574,0-1.084-0.135-1.529-0.404
+		c-0.445-0.27-0.789-0.656-1.031-1.16c-0.242-0.504-0.363-1.094-0.363-1.77c0-1.047,0.262-1.862,0.785-2.446
+		c0.523-0.584,1.25-0.876,2.18-0.876c0.898,0,1.612,0.299,2.142,0.896C6.238,198.237,6.501,199.047,6.501,200.066z M1.608,200.066
+		c0,0.821,0.164,1.446,0.492,1.875s0.811,0.645,1.447,0.645c0.636,0,1.12-0.214,1.45-0.643c0.33-0.428,0.495-1.053,0.495-1.877
+		c0-0.816-0.165-1.437-0.495-1.86s-0.817-0.636-1.462-0.636c-0.637,0-1.117,0.209-1.441,0.627
+		C1.77,198.615,1.608,199.238,1.608,200.066z"/>
+	<path fill="#FFFFFF" d="M11.136,196.744c0.285,0,0.541,0.023,0.768,0.07l-0.135,0.902c-0.266-0.059-0.5-0.088-0.703-0.088
+		c-0.52,0-0.964,0.211-1.333,0.633c-0.369,0.422-0.554,0.947-0.554,1.576v3.445H8.206v-6.422h0.803l0.111,1.189h0.047
+		c0.238-0.418,0.525-0.74,0.861-0.967C10.364,196.855,10.733,196.744,11.136,196.744z"/>
+</g>
+</svg>
diff --git a/core/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java b/core/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java
index 71b58d7..0fde587 100755
--- a/core/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java
+++ b/core/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java
@@ -22,7 +22,14 @@ import java.net.URI;
 public class JaxrsOAuthClient extends AbstractOAuthClient {
     protected static final Logger logger = Logger.getLogger(JaxrsOAuthClient.class);
     public Response redirect(UriInfo uriInfo, String redirectUri) {
+        return redirect(uriInfo, redirectUri, null);
+    }
+
+    public Response redirect(UriInfo uriInfo, String redirectUri, String path) {
         String state = getStateCode();
+        if (path != null) {
+            state += "#" + path;
+        }
 
         URI url = UriBuilder.fromUri(authUrl)
                 .queryParam("client_id", clientId)
@@ -58,7 +65,7 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
         return uriInfo.getQueryParameters().getFirst("code");
     }
 
-    public void checkStateCookie(UriInfo uriInfo, HttpHeaders headers) {
+    public String checkStateCookie(UriInfo uriInfo, HttpHeaders headers) {
         Cookie stateCookie = headers.getCookies().get(stateCookieName);
         if (stateCookie == null) throw new BadRequestException("state cookie not set");
         String state = uriInfo.getQueryParameters().getFirst("state");
@@ -66,5 +73,10 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
         if (!state.equals(stateCookie.getValue())) {
             throw new BadRequestException("state parameter invalid");
         }
+        if (state.indexOf('#') != -1) {
+            return state.substring(state.indexOf('#') + 1);
+        } else {
+            return null;
+        }
     }
 }
diff --git a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
index 7b7fa9d..878092e 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
@@ -21,6 +21,7 @@ public class ApplicationRepresentation {
     protected List<UserRoleMappingRepresentation> roleMappings;
     protected List<ScopeMappingRepresentation> scopeMappings;
     protected List<String> redirectUris;
+    protected List<String> webOrigins;
 
     public String getSelf() {
         return self;
@@ -155,4 +156,12 @@ public class ApplicationRepresentation {
     public void setRedirectUris(List<String> redirectUris) {
         this.redirectUris = redirectUris;
     }
+
+    public List<String> getWebOrigins() {
+        return webOrigins;
+    }
+
+    public void setWebOrigins(List<String> webOrigins) {
+        this.webOrigins = webOrigins;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index 14d7d50..45d47d8 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -16,6 +16,7 @@ public class RealmRepresentation {
     protected Integer accessCodeLifespan;
     protected Integer accessCodeLifespanUserAction;
     protected Boolean enabled;
+    protected Boolean accountManagement;
     protected Boolean sslNotRequired;
     protected Boolean cookieLoginAllowed;
     protected Boolean registrationAllowed;
@@ -101,6 +102,14 @@ public class RealmRepresentation {
         this.enabled = enabled;
     }
 
+    public Boolean isAccountManagement() {
+        return accountManagement;
+    }
+
+    public void setAccountManagement(Boolean accountManagement) {
+        this.accountManagement = accountManagement;
+    }
+
     public Boolean isSslNotRequired() {
         return sslNotRequired;
     }
diff --git a/examples/js/index.html b/examples/js/index.html
index bfe60fe..4a7b8d2 100644
--- a/examples/js/index.html
+++ b/examples/js/index.html
@@ -10,13 +10,14 @@
 			clientId : 'test-app',
 			clientSecret : 'password',
 			baseUrl : 'http://localhost:8081/auth-server',
-			realm : 'test'
+			realm : 'test',
+            redirectUri : 'http://localhost/js'
 		});
 
 		if (keycloak.authenticated) {
 			document.write('User: ' + keycloak.user);
 		} else {
-			document.write('<a href="#" id="login" onclick="keycloak.login()">Login</a>');
+			document.write('<a href="#" id="login" onclick="keycloak.login(location.hash)">Login</a>');
 		}
 	</script>
 

examples/js/keycloak.js 242(+123 -119)

diff --git a/examples/js/keycloak.js b/examples/js/keycloak.js
index dfb6ebb..cdd9374 100644
--- a/examples/js/keycloak.js
+++ b/examples/js/keycloak.js
@@ -1,120 +1,124 @@
-window.keycloak = (function() {
-	var kc = {};
-	var config = null;
-
-	kc.init = function(c) {
-		config = c;
-
-		var token = getTokenFromCode();
-		if (token) {
-			var t = parseToken(token);
-			kc.user = t.prn;
-			kc.authenticated = true;
-		} else {
-			kc.authenticated = false;
-		}
-	}
-
-	kc.login = function() {
-		var clientId = encodeURIComponent(config.clientId);
-		var redirectUri = encodeURIComponent(window.location.href);
-		var state = encodeURIComponent(createUUID());
-		var realm = encodeURIComponent(config.realm);
-		var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/login?response_type=code&client_id=' + clientId + '&redirect_uri=' + redirectUri
-				+ '&state=' + state;
-		window.location.href = url;
-	}
-
-	return kc;
-
-	function parseToken(token) {
-		var t = base64Decode(token.split('.')[1]);
-		return JSON.parse(t);
-	}
-
-	function getTokenFromCode() {
-		var code = getQueryParam('code');
-		if (code) {
-			window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname);
-			
-			var clientId = encodeURIComponent(config.clientId);
-			var clientSecret = encodeURIComponent(config.clientSecret);
-			var realm = encodeURIComponent(config.realm);
-
-			var params = 'code=' + code + '&client_id=' + config.clientId + '&password=' + config.clientSecret;
-			var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/access/codes'
-
-			var http = new XMLHttpRequest();
-			http.open('POST', url, false);
-			http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
-
-			http.send(params);
-			if (http.status == 200) {
-				return JSON.parse(http.responseText)['access_token'];
-			}
-		}
-		return undefined;
-	}
-
-	function getQueryParam(name) {
-		var params = window.location.search.substring(1).split('&');
-		for ( var i = 0; i < params.length; i++) {
-			var p = params[i].split('=');
-			if (decodeURIComponent(p[0]) == name) {
-				return p[1];
-			}
-		}
-	}
-
-	function createUUID() {
-		var s = [];
-		var hexDigits = '0123456789abcdef';
-		for ( var i = 0; i < 36; i++) {
-			s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
-		}
-		s[14] = '4';
-		s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
-		s[8] = s[13] = s[18] = s[23] = '-';
-		var uuid = s.join('');
-		return uuid;
-	}
-
-	function base64Decode (data) {
-		  var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
-		  var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
-		    ac = 0,
-		    dec = "",
-		    tmp_arr = [];
-
-		  if (!data) {
-		    return data;
-		  }
-
-		  data += '';
-
-		  do {
-		    h1 = b64.indexOf(data.charAt(i++));
-		    h2 = b64.indexOf(data.charAt(i++));
-		    h3 = b64.indexOf(data.charAt(i++));
-		    h4 = b64.indexOf(data.charAt(i++));
-
-		    bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
-
-		    o1 = bits >> 16 & 0xff;
-		    o2 = bits >> 8 & 0xff;
-		    o3 = bits & 0xff;
-
-		    if (h3 == 64) {
-		      tmp_arr[ac++] = String.fromCharCode(o1);
-		    } else if (h4 == 64) {
-		      tmp_arr[ac++] = String.fromCharCode(o1, o2);
-		    } else {
-		      tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
-		    }
-		  } while (i < data.length);
-
-		  dec = tmp_arr.join('');
-
-		  return dec;
-		}
+window.keycloak = (function () {
+    var kc = {};
+    var config = {
+        baseUrl: null,
+        clientId: null,
+        clientSecret: null,
+        realm: null,
+        redirectUri: null
+    };
+
+    kc.init = function (c) {
+        for (var prop in config) {
+            if (c[prop]) {
+                config[prop] = c[prop];
+            }
+
+            if (!config[prop]) {
+                throw new Error(prop + 'not defined');
+            }
+        }
+
+        processCallback();
+    }
+
+    kc.login = function () {
+        window.location.href = getLoginUrl();
+    }
+
+    return kc;
+
+    function getLoginUrl(fragment) {
+        var state = createUUID();
+        if (fragment) {
+            state += '#' + fragment;
+        }
+        sessionStorage.state = state;
+        var url = config.baseUrl + '/rest/realms/' + encodeURIComponent(config.realm) + '/tokens/login?response_type=code&client_id='
+            + encodeURIComponent(config.clientId) + '&redirect_uri=' + encodeURIComponent(config.redirectUri) + '&state=' + encodeURIComponent(state);
+        return url;
+    }
+
+    function parseToken(token) {
+        return JSON.parse(atob(token.split('.')[1]));
+    }
+
+    function processCallback() {
+        var code = getQueryParam('code');
+        var error = getQueryParam('error');
+        var state = getQueryParam('state');
+
+        if (!(code || error)) {
+            return false;
+        }
+
+        if (state != sessionStorage.state) {
+            console.error('Invalid state');
+            return true;
+        }
+
+        if (code) {
+            console.info('Received code');
+
+            var clientId = encodeURIComponent(config.clientId);
+            var clientSecret = encodeURIComponent(config.clientSecret);
+            var realm = encodeURIComponent(config.realm);
+
+            var params = 'code=' + code + '&client_id=' + clientId + '&password=' + clientSecret;
+            var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/access/codes'
+
+            var http = new XMLHttpRequest();
+            http.open('POST', url, false);
+            http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+
+            http.send(params);
+            if (http.status == 200) {
+                kc.token = JSON.parse(http.responseText)['access_token'];
+                kc.tokenParsed = parseToken(kc.token);
+                kc.authenticated = true;
+                kc.user = kc.tokenParsed.prn;
+
+                console.info('Authenticated');
+            }
+
+            updateLocation(state);
+            return true;
+        } else if (error) {
+            console.info('Error ' + error);
+            updateLocation(state);
+            return true;
+        }
+    }
+
+    function updateLocation(state) {
+        var fragment = '';
+        if (state && state.indexOf('#') != -1) {
+            fragment = state.substr(state.indexOf('#'));
+        }
+
+        window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname + fragment);
+    }
+
+    function getQueryParam(name) {
+        var params = window.location.search.substring(1).split('&');
+        for (var i = 0; i < params.length; i++) {
+            var p = params[i].split('=');
+            if (decodeURIComponent(p[0]) == name) {
+                return p[1];
+            }
+        }
+    }
+
+    function createUUID() {
+        var s = [];
+        var hexDigits = '0123456789abcdef';
+        for (var i = 0; i < 36; i++) {
+            s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
+        }
+        s[14] = '4';
+        s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
+        s[8] = s[13] = s[18] = s[23] = '-';
+        var uuid = s.join('');
+        return uuid;
+    }
 })();
\ No newline at end of file
diff --git a/examples/js/testrealm.json b/examples/js/testrealm.json
index 824fe14..38225c6 100755
--- a/examples/js/testrealm.json
+++ b/examples/js/testrealm.json
@@ -8,6 +8,7 @@
     "sslNotRequired": true,
     "cookieLoginAllowed": true,
     "registrationAllowed": true,
+    "accountManagement": true,
     "resetPasswordAllowed": true,
     "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
     "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
@@ -48,6 +49,7 @@
             "enabled": true,
             "adminUrl": "http://localhost:8081/app/logout",
             "useRealmMappings": true,
+            "webOrigins": [ "http://localhost", "http://localhost:8000", "http://localhost:8080" ],
             "credentials": [
                 {
                     "type": "password",
diff --git a/examples/js-google/index.html b/examples/js-google/index.html
new file mode 100644
index 0000000..7a72120
--- /dev/null
+++ b/examples/js-google/index.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="keycloak.js"></script>
+</head>
+<body>
+	<script>
+		keycloak.init({
+			clientId : '57572475438.apps.googleusercontent.com',
+			clientSecret : 'xyfsPS9maRTz5fj0pOxf0zjD'
+		});
+
+		if (keycloak.authenticated) {
+            document.write('<h2>Token</h2><pre>' + keycloak.token + '</pre>');
+            document.write('<h2>Token info</h2><pre>' + JSON.stringify(keycloak.tokenInfo, undefined, 4) + '</pre>');
+            document.write('<h2>Profile</h2><pre>' + JSON.stringify(keycloak.profile(true), undefined, 4) + '</pre>');		
+            document.write('<h2>Contacts</h2><pre>' + keycloak.contacts(true) + '</pre>');		
+        } else {
+			document.write('<a href="#" id="login" onclick="keycloak.login()">Login</a>');
+		}
+	</script>
+
+</body>
+</html>
diff --git a/examples/js-google/keycloak.js b/examples/js-google/keycloak.js
new file mode 100644
index 0000000..172557d
--- /dev/null
+++ b/examples/js-google/keycloak.js
@@ -0,0 +1,139 @@
+window.keycloak = (function () {
+    var kc = {};
+    var config = {
+        clientId: null,
+        clientSecret: null
+    };
+
+    kc.init = function (c) {
+        for (var prop in config) {
+            if (c[prop]) {
+                config[prop] = c[prop];
+            }
+
+            if (!config[prop]) {
+                throw new Error(prop + ' not defined');
+            }
+        }
+
+        loadToken();
+
+        if (kc.token) {
+            kc.user = kc.tokenInfo.user_id;
+            kc.authenticated = true;
+        } else {
+            kc.authenticated = false;
+            kc.user = null;
+        }
+    }
+
+    kc.login = function () {
+        var clientId = encodeURIComponent(config.clientId);
+        var redirectUri = encodeURIComponent(window.location.href);
+        var state = encodeURIComponent(createUUID());
+        var scope = encodeURIComponent('https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/plus.login');
+        var url = 'https://accounts.google.com/o/oauth2/auth?response_type=token&client_id=' + clientId + '&redirect_uri=' + redirectUri
+            + '&state=' + state + '&scope=' + scope;
+
+        sessionStorage.state = state;
+
+        window.location.href = url;
+    }
+
+    function parseToken(token) {
+        return JSON.parse(atob(token.split('.')[1]));
+    }
+
+    kc.profile = function(header) {
+        var url = 'https://www.googleapis.com/oauth2/v1/userinfo'
+
+        if (!header) {
+            url = url + '?access_token=' + kc.token;
+        }
+
+         var http = new XMLHttpRequest();
+         http.open('GET', url, false);
+        if (header) {
+        http.setRequestHeader('Authorization', 'Bearer ' + kc.token);
+        }       
+
+            http.send();
+            if (http.status == 200) {
+                return JSON.parse(http.responseText);
+            }
+    }
+
+    kc.contacts = function(header) {
+        var url = 'https://www.googleapis.com/plus/v1/people/me';
+
+        if (!header) {
+            url = url + '?access_token=' + kc.token;
+        }
+
+         var http = new XMLHttpRequest();
+         http.open('GET', url, false);
+        if (header) {
+        http.setRequestHeader('Authorization', 'Bearer ' + kc.token);
+        }       
+
+            http.send();
+            if (http.status == 200) {
+                return http.responseText;
+            }
+    }
+
+    return kc;
+
+    function loadToken() {
+        var params = {}
+        var queryString = location.hash.substring(1)
+        var regex = /([^&=]+)=([^&]*)/g, m;
+        while (m = regex.exec(queryString)) {
+            params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
+        }
+
+        var token = params['access_token'];
+        var state = params['state'];
+
+        if (token && state === sessionStorage.state) {
+            window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname);
+
+                kc.token = token;
+
+            var url = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' + token;
+
+            var http = new XMLHttpRequest();
+            http.open('GET', url, false);
+
+            http.send();
+            if (http.status == 200) {
+                kc.tokenInfo = JSON.parse(http.responseText);
+            }
+        }
+        return undefined;
+    }
+
+    function getQueryParam(name) {
+    console.debug(window.location.hash);
+        var params = window.location.hash.substring(1).split('&');
+        for (var i = 0; i < params.length; i++) {
+            var p = params[i].split('=');
+            if (decodeURIComponent(p[0]) == name) {
+                return p[1];
+            }
+        }
+    }
+
+    function createUUID() {
+        var s = [];
+        var hexDigits = '0123456789abcdef';
+        for (var i = 0; i < 36; i++) {
+            s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
+        }
+        s[14] = '4';
+        s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
+        s[8] = s[13] = s[18] = s[23] = '-';
+        var uuid = s.join('');
+        return uuid;
+    }
+})();
diff --git a/examples/js-google/keycloak.js.orig b/examples/js-google/keycloak.js.orig
new file mode 100644
index 0000000..439d2af
--- /dev/null
+++ b/examples/js-google/keycloak.js.orig
@@ -0,0 +1,222 @@
+<<<<<<< Updated upstream
+window.keycloak = (function() {
+	var kc = {};
+	var config = null;
+
+	kc.init = function(c) {
+		config = c;
+
+		var token = getTokenFromCode();
+		if (token) {
+			var t = parseToken(token);
+			kc.user = t.prn;
+			kc.authenticated = true;
+		} else {
+			kc.authenticated = false;
+		}
+	}
+
+	kc.login = function() {
+		var clientId = encodeURIComponent(config.clientId);
+		var redirectUri = encodeURIComponent(window.location.href);
+		var state = encodeURIComponent(createUUID());
+		var realm = encodeURIComponent(config.realm);
+		var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/login?response_type=code&client_id=' + clientId + '&redirect_uri=' + redirectUri
+				+ '&state=' + state;
+		window.location.href = url;
+	}
+
+	return kc;
+
+	function parseToken(token) {
+		return JSON.parse(atob(token.split('.')[1]));
+	}
+
+	function getTokenFromCode() {
+		var code = getQueryParam('code');
+		if (code) {
+			window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname);
+
+			var clientId = encodeURIComponent(config.clientId);
+			var clientSecret = encodeURIComponent(config.clientSecret);
+			var realm = encodeURIComponent(config.realm);
+
+			var params = 'code=' + code + '&client_id=' + config.clientId + '&password=' + config.clientSecret;
+			var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/access/codes'
+
+			var http = new XMLHttpRequest();
+			http.open('POST', url, false);
+			http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+
+			http.send(params);
+			if (http.status == 200) {
+				return JSON.parse(http.responseText)['access_token'];
+			}
+		}
+		return undefined;
+	}
+
+	function getQueryParam(name) {
+		var params = window.location.search.substring(1).split('&');
+		for ( var i = 0; i < params.length; i++) {
+			var p = params[i].split('=');
+			if (decodeURIComponent(p[0]) == name) {
+				return p[1];
+			}
+		}
+	}
+
+	function createUUID() {
+		var s = [];
+		var hexDigits = '0123456789abcdef';
+		for ( var i = 0; i < 36; i++) {
+			s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
+		}
+		s[14] = '4';
+		s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
+		s[8] = s[13] = s[18] = s[23] = '-';
+		var uuid = s.join('');
+		return uuid;
+	}
+=======
+window.keycloak = (function () {
+    var kc = {};
+    var config = {
+        baseUrl : null,
+        clientId : null,
+        clientSecret: null,
+        realm: null
+    };
+
+    kc.init = function (c) {
+        for (var prop in config) {
+            if (c[prop]) {
+                config[prop] = c[prop];
+            }
+
+            if (!config[prop]) {
+                throw new Error(prop + 'not defined');
+            }
+        }
+
+        var token = getTokenFromCode();
+        if (token) {
+            var t = parseToken(token);
+            kc.user = t.prn;
+            kc.authenticated = true;
+        } else {
+            kc.authenticated = false;
+        }
+    }
+
+    kc.login = function () {
+        var clientId = encodeURIComponent(config.clientId);
+        var redirectUri = encodeURIComponent(window.location.href);
+        var realm = encodeURIComponent(config.realm);
+        var state = encodeURIComponent(createUUID());
+        var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/login?response_type=code&client_id=' + clientId + '&redirect_uri=' + redirectUri
+            + '&state=' + state;
+
+        sessionStorage.state = state;
+
+        window.location.href = url;
+    }
+
+    return kc;
+
+    function parseToken(token) {
+        var t = base64Decode(token.split('.')[1]);
+        return JSON.parse(t);
+    }
+
+    function getTokenFromCode() {
+        var code = getQueryParam('code');
+        var state = getQueryParam('state');
+
+        if (code) {
+            if (state && state === sessionStorage.state) {
+                window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname);
+
+                var clientId = encodeURIComponent(config.clientId);
+                var clientSecret = encodeURIComponent(config.clientSecret);
+                var realm = encodeURIComponent(config.realm);
+
+                var params = 'code=' + code + '&client_id=' + clientId + '&password=' + clientSecret;
+                var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/access/codes'
+
+                var http = new XMLHttpRequest();
+                http.open('POST', url, false);
+                http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+
+                http.send(params);
+                if (http.status == 200) {
+                    return JSON.parse(http.responseText)['access_token'];
+                }
+            }
+        }
+        return undefined;
+    }
+
+    function getQueryParam(name) {
+        var params = window.location.search.substring(1).split('&');
+        for (var i = 0; i < params.length; i++) {
+            var p = params[i].split('=');
+            if (decodeURIComponent(p[0]) == name) {
+                return p[1];
+            }
+        }
+    }
+
+    function createUUID() {
+        var s = [];
+        var hexDigits = '0123456789abcdef';
+        for (var i = 0; i < 36; i++) {
+            s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
+        }
+        s[14] = '4';
+        s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
+        s[8] = s[13] = s[18] = s[23] = '-';
+        var uuid = s.join('');
+        return uuid;
+    }
+
+    function base64Decode(data) {
+        var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+        var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
+            ac = 0,
+            dec = "",
+            tmp_arr = [];
+
+        if (!data) {
+            return data;
+        }
+
+        data += '';
+
+        do {
+            h1 = b64.indexOf(data.charAt(i++));
+            h2 = b64.indexOf(data.charAt(i++));
+            h3 = b64.indexOf(data.charAt(i++));
+            h4 = b64.indexOf(data.charAt(i++));
+
+            bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
+
+            o1 = bits >> 16 & 0xff;
+            o2 = bits >> 8 & 0xff;
+            o3 = bits & 0xff;
+
+            if (h3 == 64) {
+                tmp_arr[ac++] = String.fromCharCode(o1);
+            } else if (h4 == 64) {
+                tmp_arr[ac++] = String.fromCharCode(o1, o2);
+            } else {
+                tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
+            }
+        } while (i < data.length);
+
+        dec = tmp_arr.join('');
+
+        return dec;
+    }
+>>>>>>> Stashed changes
+})();
\ No newline at end of file
diff --git a/examples/js-google/kinvey.html b/examples/js-google/kinvey.html
new file mode 100644
index 0000000..9e1324c
--- /dev/null
+++ b/examples/js-google/kinvey.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="http://code.jquery.com/jquery-2.0.3.js"></script>
+</head>
+<body>
+
+    <script>
+        $.ajax('https://baas.kinvey.com/appdata/kid_PVD-jo1HqO');
+    </script>
+
+</body>
+</html>
diff --git a/examples/js-google/testrealm.json b/examples/js-google/testrealm.json
new file mode 100755
index 0000000..2468f48
--- /dev/null
+++ b/examples/js-google/testrealm.json
@@ -0,0 +1,60 @@
+{
+    "id": "test",
+    "realm": "test",
+    "enabled": true,
+    "tokenLifespan": 300,
+    "accessCodeLifespan": 10,
+    "accessCodeLifespanUserAction": 600,
+    "sslNotRequired": true,
+    "cookieLoginAllowed": true,
+    "registrationAllowed": true,
+    "resetPasswordAllowed": true,
+    "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
+    "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+    "requiredCredentials": [ "password" ],
+    "requiredApplicationCredentials": [ "password" ],
+    "requiredOAuthClientCredentials": [ "password" ],
+    "defaultRoles": [ "user" ],
+    "users" : [
+        {
+            "username" : "test-user@localhost",
+            "enabled": true,
+            "email" : "test-user@localhost",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ]
+        }
+    ],
+    "roles": [
+        {
+            "name": "user",
+            "description": "Have User privileges"
+        },
+        {
+            "name": "admin",
+            "description": "Have Administrator privileges"
+        }
+    ],
+    "roleMappings": [
+        {
+            "username": "test-user@localhost",
+            "roles": ["user"]
+        }
+    ],
+    "applications": [
+        {
+            "name": "test-app",
+            "enabled": true,
+            "adminUrl": "http://localhost:8081/app/logout",
+            "useRealmMappings": true,
+            "webOrigins": [ "http://localhost", "http://localhost:8000", "http://localhost:8080" ],
+            "credentials": [
+                {
+                    "type": "password",
+                    "value": "password"
+                }
+            ]
+        }
+    ]
+}
diff --git a/forms/src/main/java/org/keycloak/forms/TotpBean.java b/forms/src/main/java/org/keycloak/forms/TotpBean.java
index 9919ff4..a283c2e 100644
--- a/forms/src/main/java/org/keycloak/forms/TotpBean.java
+++ b/forms/src/main/java/org/keycloak/forms/TotpBean.java
@@ -78,7 +78,7 @@ public class TotpBean {
 
     public String getTotpSecretQrCodeUrl() throws UnsupportedEncodingException {
         String contents = URLEncoder.encode("otpauth://totp/keycloak?secret=" + totpSecretEncoded, "utf-8");
-        return contextUrl + "/forms/qrcode" + "?size=246x246&contents=" + contents;
+        return contextUrl + "/rest/qrcode" + "?size=246x246&contents=" + contents;
     }
 
     public UserBean getUser() {
diff --git a/forms/src/main/java/org/keycloak/forms/UrlBean.java b/forms/src/main/java/org/keycloak/forms/UrlBean.java
index f527d38..8d25c3d 100755
--- a/forms/src/main/java/org/keycloak/forms/UrlBean.java
+++ b/forms/src/main/java/org/keycloak/forms/UrlBean.java
@@ -124,6 +124,10 @@ public class UrlBean {
         return Urls.accountTotpRemove(baseURI, realm.getId()).toString();
     }
 
+    public String getLogoutUrl() {
+        return Urls.accountLogout(baseURI, realm.getId()).toString();
+    }
+
     public String getLoginPasswordResetUrl() {
         return Urls.loginPasswordReset(baseURI, realm.getId()).toString();
     }
diff --git a/forms/src/main/java/org/keycloak/service/FormServiceImpl.java b/forms/src/main/java/org/keycloak/service/FormServiceImpl.java
index 9489448..64624e4 100644
--- a/forms/src/main/java/org/keycloak/service/FormServiceImpl.java
+++ b/forms/src/main/java/org/keycloak/service/FormServiceImpl.java
@@ -32,7 +32,7 @@ import freemarker.template.Configuration;
 import freemarker.template.Template;
 import freemarker.template.TemplateException;
 import org.jboss.resteasy.logging.Logger;
-import org.keycloak.forms.ErrorBean;
+import org.keycloak.forms.MessageBean;
 import org.keycloak.forms.LoginBean;
 import org.keycloak.forms.OAuthGrantBean;
 import org.keycloak.forms.RealmBean;
@@ -69,8 +69,7 @@ public class FormServiceImpl implements FormService {
         commandMap.put(Pages.TOTP, new CommandTotp());
         commandMap.put(Pages.LOGIN_CONFIG_TOTP, new CommandTotp());
         commandMap.put(Pages.LOGIN_TOTP, new CommandLoginTotp());
-        commandMap.put(Pages.LOGIN_VERIFY_EMAIL, new CommandLoginTotp());
-        commandMap.put(Pages.ERROR, new CommandError());
+        commandMap.put(Pages.LOGIN_VERIFY_EMAIL, new CommandVerifyEmail());
         commandMap.put(Pages.OAUTH_GRANT, new CommandOAuthGrant());
     }
 
@@ -82,8 +81,8 @@ public class FormServiceImpl implements FormService {
 
         Map<String, Object> attributes = new HashMap<String, Object>();
 
-        if (dataBean.getError() != null){
-            attributes.put("message", new ErrorBean(dataBean.getError(), dataBean.getErrorType()));
+        if (dataBean.getMessage() != null){
+            attributes.put("message", new MessageBean(dataBean.getMessage(), dataBean.getMessageType()));
         }
 
         RealmBean realm = new RealmBean(dataBean.getRealm());
@@ -161,9 +160,6 @@ public class FormServiceImpl implements FormService {
 
     private class CommandLoginTotp implements Command {
         public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
-            if (dataBean.getError() != null){
-                attributes.put("error", new ErrorBean(dataBean.getError()));
-            }
 
             RealmBean realm = new RealmBean(dataBean.getRealm());
 
@@ -206,10 +202,6 @@ public class FormServiceImpl implements FormService {
 
     private class CommandLogin implements Command {
         public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
-            if (dataBean.getError() != null){
-                attributes.put("error", new ErrorBean(dataBean.getError()));
-            }
-
             RealmBean realm = new RealmBean(dataBean.getRealm());
 
             attributes.put("realm", realm);
@@ -230,9 +222,6 @@ public class FormServiceImpl implements FormService {
 
     private class CommandRegister implements Command {
         public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
-            if (dataBean.getError() != null){
-                attributes.put("error", new ErrorBean(dataBean.getError()));
-            }
 
             RealmBean realm = new RealmBean(dataBean.getRealm());
 
@@ -252,14 +241,6 @@ public class FormServiceImpl implements FormService {
         }
     }
 
-    private class CommandError implements Command {
-        public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
-            if (dataBean.getError() != null){
-                attributes.put("error", new ErrorBean(dataBean.getError()));
-            }
-        }
-    }
-
     private class CommandOAuthGrant implements Command {
         public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
 
@@ -274,6 +255,20 @@ public class FormServiceImpl implements FormService {
         }
     }
 
+    private class CommandVerifyEmail implements Command {
+        public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
+
+            RealmBean realm = new RealmBean(dataBean.getRealm());
+
+            attributes.put("realm", realm);
+
+            UrlBean url = new UrlBean(realm, dataBean.getBaseURI());
+            url.setSocialRegistration(dataBean.getSocialRegistration());
+
+            attributes.put("url", url);
+        }
+    }
+
     private interface Command {
         public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean);
     }
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/login-register.css b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/login-register.css
index 8eacd72..eca69a2 100644
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/login-register.css
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/login-register.css
@@ -291,9 +291,6 @@ a.zocial:before {
 .rcue-login-register.reset .background-area .section.app-form {
   width: 43.2em;
 }
-.rcue-login-register.reset .feedback {
-  left: 35.7em;
-}
 
 .rcue-login-register.oauth .form-actions {
     margin-bottom: 0;
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/error.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/error.ftl
index 659fce1..fd3fc1e 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/error.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/error.ftl
@@ -12,7 +12,7 @@
     <#elseif section = "form">
 
         <p class="instruction">Something happened and we could not process your request.</p>
-        <p id="error-summary" class="instruction second">${error.summary}</p>
+        <p id="error-summary" class="instruction second">${message.summary}</p>
 
     <#elseif section = "info" >
 
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-config-totp.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-config-totp.ftl
index 2b1c1c5..97ff4ad 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-config-totp.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-config-totp.ftl
@@ -8,11 +8,6 @@
 
     Google Authenticator Setup
 
-    <#elseif section = "feedback">
-    <div class="feedback warning show">
-        <p><strong>Your account is not enabled because you need to set up the Google Authenticator.</strong><br>Please follow the steps below.</p>
-    </div>
-
     <#elseif section = "form">
 
     <div id="form">
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-reset-password.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-reset-password.ftl
index ad80199..af308ac 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-reset-password.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-reset-password.ftl
@@ -11,15 +11,6 @@
     <#elseif section = "form">
 
     <div id="form">
-        <#if message?has_content>
-            <#if message.success>
-                <div class="feedback success bottom-left show"><p><strong>${rb.getString('successHeader')}</strong> ${rb.getString(message.summary)}</p></div>
-            </#if>
-            <#if message.error>
-                <div class="feedback error bottom-left show"><p><strong>${rb.getString('errorHeader')}</strong><br/>${rb.getString(message.summary)}</p></div>
-            </#if>
-        </#if>
-
         <p class="instruction">${rb.getString('emailInstruction')}</p>
         <form action="${url.loginPasswordResetUrl}" method="post">
             <div>
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-verify-email.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-verify-email.ftl
index cff75f1..0832666 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-verify-email.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-verify-email.ftl
@@ -8,11 +8,6 @@
 
     Email verification
 
-    <#elseif section = "feedback">
-    <div class="feedback warning show">
-        <p><strong>Your account is not enabled because you need to verify your email.</strong><br>Please follow the steps below.</p>
-    </div>
-
     <#elseif section = "form">
 
     <div class="app-form">
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.ftl
index de023e1..d64a3c6 100644
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.ftl
@@ -31,10 +31,10 @@
             <div class="form-area ${(realm.social)?string('social','')} clearfix">
                 <div class="section app-form">
                     <h3>Application login area</h3>
-                    <#if error?has_content>
+                    <#if message?has_content && message.error>
                         <div class="feedback error bottom-left show">
                             <p>
-                                <strong id="loginError">${rb.getString(error.summary)}</strong><br/>${rb.getString('emailErrorInfo')}
+                                <strong id="loginError">${rb.getString(message.summary)}</strong><br/>${rb.getString('emailErrorInfo')}
                             </p>
                         </div>
                     </#if>
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login-action.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login-action.ftl
index c2920cf..303aeeb 100644
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login-action.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login-action.ftl
@@ -17,7 +17,11 @@
 
 <body class="rcue-login-register ${bodyClass}">
     <div class="feedback-aligner">
-        <#nested "feedback">
+        <#if message?has_content && message.warning>
+        <div class="feedback warning show">
+            <p><strong>${rb.getString('actionWarningHeader')} ${rb.getString(message.summary)}</strong><br/>${rb.getString('actionFollow')}</p>
+        </div>
+        </#if>
     </div>
     <#if (template.themeConfig.logo)?has_content>
         <h1>
@@ -33,18 +37,26 @@
         <div class="background-area">
             <div class="form-area clearfix">
                 <div class="section app-form">
+                    <#if !isErrorPage && message?has_content>
+                        <#if message.error>
+                            <div class="feedback error bottom-left show">
+                                <p>
+                                    <strong id="loginError">${rb.getString(message.summary)}</strong><br/>${rb.getString('emailErrorInfo')}
+                                </p>
+                            </div>
+                        <#elseif message.success>
+                            <div class="feedback success bottom-left show">
+                                <p>
+                                    <strong>${rb.getString('successHeader')}</strong> ${rb.getString(message.summary)}
+                                </p>
+                            </div>
+                        </#if>
+                    </#if>
+
                     <h3>Application login area</h3>
                     <#nested "form">
                 </div>
 
-                <#if !isErrorPage && error?has_content>
-                    <div class="feedback error bottom-left show">
-                        <p>
-                            <strong id="loginError">${rb.getString(error.summary)}</strong>
-                        </p>
-                    </div>
-                </#if>
-
                 <div class="section info-area">
                     <h3>Info area</h3>
                     <#nested "info">
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.ftl
index 18ab4d5..19a95ee 100644
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.ftl
@@ -54,6 +54,9 @@
                     <a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class="icon-user">Icon: user</span>
                     ${user.firstName!''} ${user.lastName!''}</a>
                 </li>
+                <li>
+                    <a href="${url.logoutUrl}">Logout</a>
+                </li>
             </ul>
         </div>
     </div>
diff --git a/forms/src/main/resources/org/keycloak/forms/messages.properties b/forms/src/main/resources/org/keycloak/forms/messages.properties
index 03943dc..8fb4f12 100644
--- a/forms/src/main/resources/org/keycloak/forms/messages.properties
+++ b/forms/src/main/resources/org/keycloak/forms/messages.properties
@@ -31,6 +31,7 @@ missingLastName=Please specify last name
 missingEmail=Please specify email
 missingUsername=Please specify username
 missingPassword=Please specify password
+notMatchPassword=Passwords don't match
 missingTotp=Please specify authenticator code
 
 invalidPasswordExisting=Invalid existing password
@@ -43,6 +44,12 @@ successTotpRemoved=Google authenticator removed.
 usernameExists=Username already exists
 
 error=A system error has occured, contact admin
+actionWarningHeader=Your account is not enabled.
+actionTotpWarning=You need to set up the Google Authenticator to activate your account.
+actionProfileWarning=You need to update your user profile to activate your account.
+actionPasswordWarning=You need to change your password to activate your account.
+actionEmailWarning=You need to verify your email address to activate your account.
+actionFollow=Please follow the steps below.
 
 successHeader=Success!
 errorHeader=Error!
diff --git a/model/api/src/main/java/org/keycloak/models/Constants.java b/model/api/src/main/java/org/keycloak/models/Constants.java
index 52db21f..b0af34c 100755
--- a/model/api/src/main/java/org/keycloak/models/Constants.java
+++ b/model/api/src/main/java/org/keycloak/models/Constants.java
@@ -11,4 +11,6 @@ public interface Constants {
     String APPLICATION_ROLE = "KEYCLOAK_APPLICATION";
     String IDENTITY_REQUESTER_ROLE = "KEYCLOAK_IDENTITY_REQUESTER";
     String WILDCARD_ROLE = "*";
+
+    String ACCOUNT_MANAGEMENT_APPLICATION = "Account Management";
 }
diff --git a/model/api/src/main/java/org/keycloak/models/UserModel.java b/model/api/src/main/java/org/keycloak/models/UserModel.java
index 8598ae7..7e33185 100755
--- a/model/api/src/main/java/org/keycloak/models/UserModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserModel.java
@@ -35,6 +35,14 @@ public interface UserModel {
 
     void removeRequiredAction(RequiredAction action);
 
+    Set<String> getWebOrigins();
+
+    void setWebOrigins(Set<String> webOrigins);
+
+    void addWebOrigin(String webOrigin);
+
+    void removeWebOrigin(String webOrigin);
+
     Set<String> getRedirectUris();
 
     void setRedirectUris(Set<String> redirectUris);
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/UserAdapter.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/UserAdapter.java
index 98894ce..e44c92a 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/UserAdapter.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/UserAdapter.java
@@ -22,6 +22,7 @@ public class UserAdapter implements UserModel {
     private static final String REQUIRED_ACTIONS_ATTR = "requiredActions";
 
     private static final String REDIRECT_URIS = "redirectUris";
+    private static final String WEB_ORIGINS = "webOrigins";
 
     protected User user;
     protected IdentityManager idm;
@@ -162,6 +163,26 @@ public class UserAdapter implements UserModel {
     }
 
     @Override
+    public Set<String> getWebOrigins() {
+        return getAttributeSet(WEB_ORIGINS);
+    }
+
+    @Override
+    public void setWebOrigins(Set<String> webOrigins) {
+        setAttributeSet(WEB_ORIGINS, webOrigins);
+    }
+
+    @Override
+    public void addWebOrigin(String webOrigin) {
+        addToAttributeSet(WEB_ORIGINS, webOrigin);
+    }
+
+    @Override
+    public void removeWebOrigin(String webOrigin) {
+        removeFromAttributeSet(WEB_ORIGINS, webOrigin);
+    }
+
+    @Override
     public boolean isTotp() {
         Attribute<Boolean> a = user.getAttribute(KEYCLOAK_TOTP_ATTR);
         return a != null ? a.getValue() : false;

pom.xml 5(+5 -0)

diff --git a/pom.xml b/pom.xml
index 61503c5..da29152 100755
--- a/pom.xml
+++ b/pom.xml
@@ -193,6 +193,11 @@
                 <version>${jboss.logging.version}</version>
             </dependency>
             <dependency>
+                <groupId>log4j</groupId>
+                <artifactId>log4j</artifactId>
+                <version>1.2.17</version>
+            </dependency>
+            <dependency>
                 <groupId>junit</groupId>
                 <artifactId>junit</artifactId>
                 <version>4.11</version>

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

diff --git a/services/pom.xml b/services/pom.xml
index 187d7be..52d2ab8 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -166,6 +166,10 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>javase</artifactId>
+        </dependency>
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <scope>test</scope>
diff --git a/services/src/main/java/org/keycloak/services/FormService.java b/services/src/main/java/org/keycloak/services/FormService.java
index 6e58138..70e4c0f 100755
--- a/services/src/main/java/org/keycloak/services/FormService.java
+++ b/services/src/main/java/org/keycloak/services/FormService.java
@@ -44,9 +44,9 @@ public interface FormService {
 
         private RealmModel realm;
         private UserModel userModel;
-        private String error;
+        private String message;
 
-        private FormFlows.ErrorType errorType;
+        private FormFlows.MessageType messageType;
 
         private MultivaluedMap<String, String> formData;
         private URI baseURI;
@@ -81,11 +81,11 @@ public interface FormService {
 
         private String contextPath;
 
-        public FormServiceDataBean(RealmModel realm, UserModel userModel, MultivaluedMap<String, String> formData, String error){
+        public FormServiceDataBean(RealmModel realm, UserModel userModel, MultivaluedMap<String, String> formData, String message){
             this.realm = realm;
             this.userModel = userModel;
             this.formData = formData;
-            this.error = error;
+            this.message = message;
         }
 
         public URI getBaseURI() {
@@ -96,12 +96,12 @@ public interface FormService {
             this.baseURI = baseURI;
         }
 
-        public String getError() {
-            return error;
+        public String getMessage() {
+            return message;
         }
 
-        public void setError(String error) {
-            this.error = error;
+        public void setMessage(String message) {
+            this.message = message;
         }
 
         public MultivaluedMap<String, String> getFormData() {
@@ -128,12 +128,12 @@ public interface FormService {
             this.userModel = userModel;
         }
 
-        public FormFlows.ErrorType getErrorType() {
-            return errorType;
+        public FormFlows.MessageType getMessageType() {
+            return messageType;
         }
 
-        public void setErrorType(FormFlows.ErrorType errorType) {
-            this.errorType = errorType;
+        public void setMessageType(FormFlows.MessageType messageType) {
+            this.messageType = messageType;
         }
 
         /* OAuth Part */
diff --git a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
index 93c2959..9b89cf4 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
@@ -42,6 +42,11 @@ public class ApplicationManager {
                 resourceUser.addRedirectUri(redirectUri);
             }
         }
+        if (resourceRep.getWebOrigins() != null) {
+            for (String webOrigin : resourceRep.getWebOrigins()) {
+                resourceUser.addWebOrigin(webOrigin);
+            }
+        }
 
         realm.grantRole(resourceUser, loginRole);
 
@@ -97,6 +102,11 @@ public class ApplicationManager {
         if (redirectUris != null) {
             resource.getApplicationUser().setRedirectUris(new HashSet<String>(redirectUris));
         }
+
+        List<String> webOrigins = rep.getWebOrigins();
+        if (webOrigins != null) {
+            resource.getApplicationUser().setWebOrigins(new HashSet<String>(webOrigins));
+        }
     }
 
     public ApplicationRepresentation toRepresentation(ApplicationModel applicationModel) {
@@ -113,6 +123,11 @@ public class ApplicationManager {
             rep.setRedirectUris(new LinkedList<String>(redirectUris));
         }
 
+        Set<String> webOrigins = applicationModel.getApplicationUser().getWebOrigins();
+        if (webOrigins != null) {
+            rep.setWebOrigins(new LinkedList<String>(webOrigins));
+        }
+
         return rep;
 
     }
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 925076a..1797bc0 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -13,6 +13,7 @@ import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.services.resources.AccountService;
 import org.keycloak.services.resources.RealmsResource;
 import org.keycloak.services.resources.SaasService;
 
@@ -61,6 +62,11 @@ public class AuthenticationManager {
         return createLoginCookie(realm, user, cookieName, cookiePath);
     }
 
+    public NewCookie createAccountIdentityCookie(RealmModel realm, UserModel user, URI uri) {
+        String cookieName = AccountService.ACCOUNT_IDENTITY_COOKIE;
+        String cookiePath = uri.getPath();
+        return createLoginCookie(realm, user, cookieName, cookiePath);
+    }
 
     protected NewCookie createLoginCookie(RealmModel realm, UserModel user, String cookieName, String cookiePath) {
         SkeletonKeyToken identityToken = createIdentityToken(realm, user.getLoginName());
@@ -99,6 +105,11 @@ public class AuthenticationManager {
         expireCookie(SaasService.SAAS_IDENTITY_COOKIE, cookiePath);
     }
 
+    public void expireAccountIdentityCookie(URI uri) {
+        String cookiePath = uri.getPath();
+        expireCookie(AccountService.ACCOUNT_IDENTITY_COOKIE, cookiePath);
+    }
+
     public void expireCookie(String cookieName, String path) {
         HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class);
         if (response == null) {
@@ -120,6 +131,11 @@ public class AuthenticationManager {
         return authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
     }
 
+    public UserModel authenticateAccountIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
+        String cookieName = AccountService.ACCOUNT_IDENTITY_COOKIE;
+        return authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
+    }
+
     public UserModel authenticateSaasIdentity(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
         UserModel user = authenticateSaasIdentityCookie(realm, uriInfo, headers);
         if (user != null) return user;
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index af6d4db..bae0cc2 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -90,6 +90,36 @@ public class RealmManager {
         if (rep.getDefaultRoles() != null) {
             realm.updateDefaultRoles(rep.getDefaultRoles());
         }
+
+        if (rep.isAccountManagement()) {
+            enableAccountManagement(realm);
+        } else {
+            disableAccountManagement(realm);
+        }
+    }
+
+    private void enableAccountManagement(RealmModel realm) {
+        ApplicationModel application = realm.getApplicationById(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
+        if (application == null) {
+            application = realm.addApplication(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
+
+            UserCredentialModel password = new UserCredentialModel();
+            password.setType(UserCredentialModel.PASSWORD);
+            password.setValue(UUID.randomUUID().toString()); // just a random password as we'll never access it
+
+            realm.updateCredential(application.getApplicationUser(), password);
+
+            RoleModel applicationRole = realm.getRole(Constants.APPLICATION_ROLE);
+            realm.grantRole(application.getApplicationUser(), applicationRole);
+        }
+        application.setEnabled(true);
+    }
+
+    private void disableAccountManagement(RealmModel realm) {
+        ApplicationModel application = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
+        if (application != null) {
+            application.setEnabled(false); // TODO Should we delete the application instead?
+        }
     }
 
     public RealmModel importRealm(RealmRepresentation rep, UserModel realmCreator) {
@@ -214,6 +244,10 @@ public class RealmManager {
                 }
             }
         }
+
+        if (rep.isAccountManagement() != null && rep.isAccountManagement()) {
+            enableAccountManagement(newRealm);
+        }
     }
 
     public void createRole(RealmModel newRealm, RoleRepresentation roleRep) {
@@ -370,6 +404,9 @@ public class RealmManager {
         rep.setAccessCodeLifespan(realm.getAccessCodeLifespan());
         rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
 
+        ApplicationModel accountManagementApplication = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
+        rep.setAccountManagement(accountManagementApplication != null && accountManagementApplication.isEnabled());
+
         List<RoleModel> defaultRoles = realm.getDefaultRoles();
         if (defaultRoles.size() > 0) {
             String[] d = new String[defaultRoles.size()];
diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
index c3de829..96d0c10 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
@@ -37,16 +37,20 @@ public class ResourceAdminManager {
     }
 
     protected boolean logoutResource(RealmModel realm, ApplicationModel resource, String user, ResteasyClient client) {
-        LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), System.currentTimeMillis() / 1000 + 30, resource.getName(), user);
-        String token = new TokenManager().encodeToken(realm, adminAction);
-        Form form = new Form();
-        form.param("token", token);
         String managementUrl = resource.getManagementUrl();
-        logger.info("logout user: " + user + " resource: " + resource.getName() + " url" + managementUrl);
-        Response response = client.target(managementUrl).queryParam("action", "logout").request().post(Entity.form(form));
-        boolean success = response.getStatus() == 204;
-        response.close();
-        return success;
+        if (managementUrl != null) {
+            LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), System.currentTimeMillis() / 1000 + 30, resource.getName(), user);
+            String token = new TokenManager().encodeToken(realm, adminAction);
+            Form form = new Form();
+            form.param("token", token);
+            logger.info("logout user: " + user + " resource: " + resource.getName() + " url" + managementUrl);
+            Response response = client.target(managementUrl).queryParam("action", "logout").request().post(Entity.form(form));
+            boolean success = response.getStatus() == 204;
+            response.close();
+            return success;
+        } else {
+            return false;
+        }
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java
index 3b7a760..2c659fb 100644
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -44,6 +44,8 @@ public class Messages {
 
     public static final String MISSING_PASSWORD = "missingPassword";
 
+    public static final String NOTMATCH_PASSWORD = "notMatchPassword";
+
     public static final String MISSING_USERNAME = "missingUsername";
 
     public static final String MISSING_TOTP = "missingTotp";
@@ -52,6 +54,14 @@ public class Messages {
 
     public static final String USERNAME_EXISTS = "usernameExists";
 
+    public static final String ACTION_WARN_TOTP = "actionTotpWarning";
+
+    public static final String ACTION_WARN_PROFILE = "actionProfileWarning";
+
+    public static final String ACTION_WARN_PASSWD = "actionPasswordWarning";
+
+    public static final String ACTION_WARN_EMAIL = "actionEmailWarning";
+
     public static final String ERROR = "error";
 
 }
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 7f54e52..2a54323 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -21,37 +21,35 @@
  */
 package org.keycloak.services.resources;
 
+import java.net.URI;
 import java.util.HashSet;
 import java.util.Set;
 
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.core.Context;
-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.*;
+import javax.ws.rs.core.*;
 import javax.ws.rs.core.Response.Status;
-import javax.ws.rs.core.UriInfo;
 import javax.ws.rs.ext.Providers;
 
+import org.jboss.resteasy.annotations.cache.NoCache;
 import org.jboss.resteasy.jose.jws.JWSInput;
 import org.jboss.resteasy.jose.jws.crypto.RSAProvider;
+import org.jboss.resteasy.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.AbstractOAuthClient;
+import org.keycloak.jaxrs.JaxrsOAuthClient;
+import org.keycloak.models.*;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.email.EmailSender;
 import org.keycloak.services.managers.AccessCodeEntry;
 import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.messages.Messages;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserCredentialModel;
-import org.keycloak.models.UserModel;
 import org.keycloak.models.UserModel.RequiredAction;
 import org.keycloak.services.resources.flows.Flows;
 import org.keycloak.services.resources.flows.FormFlows;
+import org.keycloak.services.resources.flows.Pages;
+import org.keycloak.services.resources.flows.Urls;
 import org.keycloak.services.validation.Validation;
 import org.picketlink.idm.credential.util.TimeBasedOTP;
 
@@ -60,6 +58,10 @@ import org.picketlink.idm.credential.util.TimeBasedOTP;
  */
 public class AccountService {
 
+    private static final Logger logger = Logger.getLogger(AccountService.class);
+
+    public static final String ACCOUNT_IDENTITY_COOKIE = "KEYCLOAK_ACCOUNT_IDENTITY";
+
     private RealmModel realm;
 
     @Context
@@ -72,37 +74,64 @@ public class AccountService {
     private UriInfo uriInfo;
 
     @Context
-    protected Providers providers;
+    private Providers providers;
+
+    private AuthenticationManager authManager = new AuthenticationManager();
 
-    protected AuthenticationManager authManager = new AuthenticationManager();
+    private ApplicationModel application;
 
     private TokenManager tokenManager;
 
-    public AccountService(RealmModel realm, TokenManager tokenManager) {
+    public AccountService(RealmModel realm, ApplicationModel application, TokenManager tokenManager) {
         this.realm = realm;
+        this.application = application;
         this.tokenManager = tokenManager;
     }
 
-    @Path("access")
-    @GET
-    public Response accessPage() {
-        UserModel user = getUserFromAuthManager();
+    private Response forwardToPage(String path, String template) {
+        UserModel user = getUser(false);
         if (user != null) {
-            return Flows.forms(realm, request, uriInfo).setUser(user).forwardToAccess();
+            return Flows.forms(realm, request, uriInfo).setUser(user).forwardToForm(template);
         } else {
-            return Response.status(Status.FORBIDDEN).build();
+            return login(path);
         }
     }
 
     @Path("")
+    @GET
+    public Response accountPage() {
+        return forwardToPage(null, Pages.ACCOUNT);
+    }
+
+    @Path("social")
+    @GET
+    public Response socialPage() {
+        return forwardToPage("social", Pages.SOCIAL);
+    }
+
+    @Path("totp")
+    @GET
+    public Response totpPage() {
+        return forwardToPage("totp", Pages.TOTP);
+    }
+
+    @Path("password")
+    @GET
+    public Response passwordPage() {
+        return forwardToPage("password", Pages.PASSWORD);
+    }
+
+    @Path("access")
+    @GET
+    public Response accessPage() {
+        return forwardToPage("access", Pages.ACCESS);
+    }
+
+    @Path("")
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response processAccountUpdate(final MultivaluedMap<String, String> formData) {
-        UserModel user = getUserFromAuthManager();
-        if (user == null) {
-            return Response.status(Status.FORBIDDEN).build();
-        }
-
+        UserModel user = getUser(true);
         user.setFirstName(formData.getFirst("firstName"));
         user.setLastName(formData.getFirst("lastName"));
         user.setEmail(formData.getFirst("email"));
@@ -113,9 +142,9 @@ public class AccountService {
     @Path("totp-remove")
     @GET
     public Response processTotpRemove() {
-        UserModel user = getUserFromAuthManager();
+        UserModel user = getUser(true);
         user.setTotp(false);
-        return Flows.forms(realm, request, uriInfo).setError("successTotpRemoved").setErrorType(FormFlows.ErrorType.SUCCESS)
+        return Flows.forms(realm, request, uriInfo).setError("successTotpRemoved").setErrorType(FormFlows.MessageType.SUCCESS)
                 .setUser(user).forwardToTotp();
     }
 
@@ -123,36 +152,26 @@ public class AccountService {
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response processTotpUpdate(final MultivaluedMap<String, String> formData) {
-        UserModel user = getUserFromAuthManager();
-        if (user == null) {
-            return Response.status(Status.FORBIDDEN).build();
-        }
-
-        FormFlows forms = Flows.forms(realm, request, uriInfo);
+        UserModel user = getUser(true);
 
         String totp = formData.getFirst("totp");
         String totpSecret = formData.getFirst("totpSecret");
 
-        String error = null;
-
+        FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
         if (Validation.isEmpty(totp)) {
-            error = Messages.MISSING_TOTP;
+            return forms.setError(Messages.MISSING_TOTP).forwardToTotp();
         } else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
-            error = Messages.INVALID_TOTP;
-        }
-
-        if (error != null) {
-            return forms.setError(error).setUser(user).forwardToTotp();
+            return forms.setError(Messages.INVALID_TOTP).forwardToTotp();
         }
 
         UserCredentialModel credentials = new UserCredentialModel();
         credentials.setType(CredentialRepresentation.TOTP);
-        credentials.setValue(formData.getFirst("totpSecret"));
+        credentials.setValue(totpSecret);
         realm.updateCredential(user, credentials);
 
         user.setTotp(true);
 
-        return Flows.forms(realm, request, uriInfo).setError("successTotp").setErrorType(FormFlows.ErrorType.SUCCESS)
+        return Flows.forms(realm, request, uriInfo).setError("successTotp").setErrorType(FormFlows.MessageType.SUCCESS)
                 .setUser(user).forwardToTotp();
     }
 
@@ -160,10 +179,7 @@ public class AccountService {
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response processPasswordUpdate(final MultivaluedMap<String, String> formData) {
-        UserModel user = getUserFromAuthManager();
-        if (user == null) {
-            return Response.status(Status.FORBIDDEN).build();
-        }
+        UserModel user = getUser(true);
 
         FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
 
@@ -172,18 +188,17 @@ public class AccountService {
         String passwordConfirm = formData.getFirst("password-confirm");
 
         if (Validation.isEmpty(passwordNew)) {
-            forms.setError(Messages.MISSING_PASSWORD).forwardToPassword();
+            return forms.setError(Messages.MISSING_PASSWORD).forwardToPassword();
         } else if (!passwordNew.equals(passwordConfirm)) {
-            forms.setError(Messages.INVALID_PASSWORD_CONFIRM).forwardToPassword();
+            return forms.setError(Messages.INVALID_PASSWORD_CONFIRM).forwardToPassword();
         }
 
         if (Validation.isEmpty(password)) {
-            forms.setError(Messages.MISSING_PASSWORD).forwardToPassword();
+            return forms.setError(Messages.MISSING_PASSWORD).forwardToPassword();
         } else if (!realm.validatePassword(user, password)) {
-            forms.setError(Messages.INVALID_PASSWORD_EXISTING).forwardToPassword();
+            return forms.setError(Messages.INVALID_PASSWORD_EXISTING).forwardToPassword();
         }
 
-
         UserCredentialModel credentials = new UserCredentialModel();
         credentials.setType(CredentialRepresentation.PASSWORD);
         credentials.setValue(passwordNew);
@@ -193,50 +208,112 @@ public class AccountService {
         return Flows.forms(realm, request, uriInfo).setUser(user).forwardToPassword();
     }
 
-    @Path("")
+    @Path("login-redirect")
     @GET
-    public Response accountPage() {
-        UserModel user = getUserFromAuthManager();
-        if (user != null) {
-            return Flows.forms(realm, request, uriInfo).setUser(user).forwardToAccount();
-        } else {
-            return Response.status(Status.FORBIDDEN).build();
+    public Response loginRedirect(@QueryParam("code") String code,
+                                  @QueryParam("state") String state,
+                                  @QueryParam("error") String error,
+                                  @Context HttpHeaders headers) {
+        try {
+            if (error != null) {
+                logger.debug("error from oauth");
+                throw new ForbiddenException("error");
+            }
+            if (!realm.isEnabled()) {
+                logger.debug("realm not enabled");
+                throw new ForbiddenException();
+            }
+            UserModel client = application.getApplicationUser();
+            if (!client.isEnabled() || !application.isEnabled()) {
+                logger.debug("account management app not enabled");
+                throw new ForbiddenException();
+            }
+            if (code == null) {
+                logger.debug("code not specified");
+                throw new BadRequestException();
+            }
+            if (state == null) {
+                logger.debug("state not specified");
+                throw new BadRequestException();
+            }
+            String path = new JaxrsOAuthClient().checkStateCookie(uriInfo, headers);
+
+            JWSInput input = new JWSInput(code, providers);
+            boolean verifiedCode = false;
+            try {
+                verifiedCode = RSAProvider.verify(input, realm.getPublicKey());
+            } catch (Exception ignored) {
+                logger.debug("Failed to verify signature", ignored);
+            }
+            if (!verifiedCode) {
+                logger.debug("unverified access code");
+                throw new BadRequestException();
+            }
+            String key = input.readContent(String.class);
+            AccessCodeEntry accessCode = tokenManager.pullAccessCode(key);
+            if (accessCode == null) {
+                logger.debug("bad access code");
+                throw new BadRequestException();
+            }
+            if (accessCode.isExpired()) {
+                logger.debug("access code expired");
+                throw new BadRequestException();
+            }
+            if (!accessCode.getToken().isActive()) {
+                logger.debug("access token expired");
+                throw new BadRequestException();
+            }
+            if (!accessCode.getRealm().getId().equals(realm.getId())) {
+                logger.debug("bad realm");
+                throw new BadRequestException();
+
+            }
+            if (!client.getLoginName().equals(accessCode.getClient().getLoginName())) {
+                logger.debug("bad client");
+                throw new BadRequestException();
+            }
+
+            UriBuilder redirectBuilder = Urls.accountBase(uriInfo.getBaseUri());
+            if (path != null) {
+                redirectBuilder.path(path);
+            }
+            URI redirectUri = redirectBuilder.build(realm.getId());
+
+            NewCookie cookie = authManager.createAccountIdentityCookie(realm, accessCode.getUser(), Urls.accountBase(uriInfo.getBaseUri()).build(realm.getId()));
+            return Response.status(302).cookie(cookie).location(redirectUri).build();
+        } finally {
+            authManager.expireCookie(AbstractOAuthClient.OAUTH_TOKEN_REQUEST_STATE, uriInfo.getAbsolutePath().getPath());
         }
     }
 
-    @Path("social")
+    @Path("logout")
     @GET
-    public Response socialPage() {
-        UserModel user = getUserFromAuthManager();
-        if (user != null) {
-            return Flows.forms(realm, request, uriInfo).setUser(user).forwardToSocial();
-        } else {
-            return Response.status(Status.FORBIDDEN).build();
-        }
+    public Response logout() {
+        // TODO Should use single-sign out via TokenService
+        URI baseUri = Urls.accountBase(uriInfo.getBaseUri()).build(realm.getId());
+        authManager.expireIdentityCookie(realm, uriInfo);
+        authManager.expireAccountIdentityCookie(baseUri);
+        return Response.status(302).location(baseUri).build();
     }
 
-    @Path("totp")
-    @GET
-    public Response totpPage() {
-        UserModel user = getUserFromAuthManager();
-        if (user != null) {
-            return Flows.forms(realm, request, uriInfo).setUser(user).forwardToTotp();
-        } else {
-            return Response.status(Status.FORBIDDEN).build();
-        }
-    }
+    private Response login(String path) {
+        JaxrsOAuthClient oauth = new JaxrsOAuthClient();
+        String authUrl = Urls.realmLoginPage(uriInfo.getBaseUri(), realm.getId()).toString();
+        oauth.setAuthUrl(authUrl);
 
-    @Path("password")
-    @GET
-    public Response passwordPage() {
-        UserModel user = getUserFromAuthManager();
-        if (user == null) {
-            return Response.status(Status.FORBIDDEN).build();
-        }
-        return Flows.forms(realm, request, uriInfo).setUser(user).forwardToPassword();
+        oauth.setClientId(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
+
+        URI accountUri = Urls.accountPageBuilder(uriInfo.getBaseUri()).path(AccountService.class, "loginRedirect").build(realm.getId());
+
+        oauth.setStateCookiePath(accountUri.getPath());
+        return oauth.redirect(uriInfo, accountUri.toString(), path);
     }
 
-    private UserModel getUserFromAuthManager() {
-        return authManager.authenticateIdentityCookie(realm, uriInfo, headers);
+    private UserModel getUser(boolean required) {
+        UserModel user = authManager.authenticateAccountIdentityCookie(realm, uriInfo, headers);
+        if (user == null && required) {
+            throw new ForbiddenException();
+        }
+        return user;
     }
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/Cors.java b/services/src/main/java/org/keycloak/services/resources/Cors.java
new file mode 100644
index 0000000..8c28d6f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/Cors.java
@@ -0,0 +1,43 @@
+package org.keycloak.services.resources;
+
+import java.util.Set;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+
+import org.jboss.resteasy.spi.HttpRequest;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class Cors {
+
+    private HttpRequest request;
+    private ResponseBuilder response;
+    private Set<String> allowedOrigins;
+
+    public Cors(HttpRequest request, ResponseBuilder response) {
+        this.request = request;
+        this.response = response;
+    }
+
+    public static Cors add(HttpRequest request, ResponseBuilder response) {
+        return new Cors(request, response);
+    }
+
+    public Cors allowedOrigins(Set<String> allowedOrigins) {
+        this.allowedOrigins = allowedOrigins;
+        return this;
+    }
+
+    public Response build() {
+        String origin = request.getHttpHeaders().getHeaderString("Origin");
+        if (origin == null || allowedOrigins == null || (!allowedOrigins.contains(origin))) {
+            return response.build();
+        }
+
+        response.header("Access-Control-Allow-Origin", origin);
+        return response.build();
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
index c4fe4a6..fe924fb 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
@@ -34,6 +34,7 @@ import org.keycloak.services.managers.AccessCodeEntry;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserModel.RequiredAction;
+import org.keycloak.services.messages.Messages;
 import org.picketlink.idm.model.sample.Realm;
 
 import javax.imageio.spi.ServiceRegistry;
@@ -58,8 +59,8 @@ public class FormFlows {
     // TODO refactor/rename "error" to "message" everywhere where it makes sense
     private String error;
 
-    public static enum ErrorType {SUCCESS, WARNING, ERROR};
-    private ErrorType errorType;
+    public static enum MessageType {SUCCESS, WARNING, ERROR};
+    private MessageType messageType = MessageType.ERROR;
 
     private MultivaluedMap<String, String> formData;
 
@@ -79,16 +80,17 @@ public class FormFlows {
     }
 
     public Response forwardToAction(RequiredAction action) {
+
         switch (action) {
             case CONFIGURE_TOTP:
-                return forwardToForm(Pages.LOGIN_CONFIG_TOTP);
+                return forwardToActionForm(Pages.LOGIN_CONFIG_TOTP, Messages.ACTION_WARN_TOTP);
             case UPDATE_PROFILE:
-                return forwardToForm(Pages.LOGIN_UPDATE_PROFILE);
+                return forwardToActionForm(Pages.LOGIN_UPDATE_PROFILE, Messages.ACTION_WARN_PROFILE);
             case UPDATE_PASSWORD:
-                return forwardToForm(Pages.LOGIN_UPDATE_PASSWORD);
+                return forwardToActionForm(Pages.LOGIN_UPDATE_PASSWORD, Messages.ACTION_WARN_PASSWD);
             case VERIFY_EMAIL:
                 new EmailSender().sendEmailVerification(userModel, realm, accessCode, uriInfo);
-                return forwardToForm(Pages.LOGIN_VERIFY_EMAIL);
+                return forwardToActionForm(Pages.LOGIN_VERIFY_EMAIL, Messages.ACTION_WARN_EMAIL);
             default:
                 return Response.serverError().build();
         }
@@ -103,7 +105,6 @@ public class FormFlows {
     }
 
     private Response forwardToForm(String template, FormService.FormServiceDataBean formDataBean) {
-        formDataBean.setErrorType(errorType == null ? ErrorType.ERROR : errorType);
 
         // Getting URI needed by form processing service
         ResteasyUriInfo uriInfo = request.getUri();
@@ -140,11 +141,24 @@ public class FormFlows {
         return Response.status(200).entity("form provider not found").build();
     }
 
-    private Response forwardToForm(String template) {
+    public Response forwardToForm(String template) {
 
         FormService.FormServiceDataBean formDataBean = new FormService.FormServiceDataBean(realm, userModel, formData, error);
+        formDataBean.setMessageType(messageType);
+
         return forwardToForm(template, formDataBean);
+    }
+
+    private Response forwardToActionForm(String template, String warningSummary) {
+
+        // If no other message is set, notify user about required action in the warning window
+        // so it's clear that this is a req. action form not a login form
+        if (error == null){
+            messageType = MessageType.WARNING;
+            error = warningSummary;
+        }
 
+        return forwardToForm(template);
     }
 
     public Response forwardToLogin() {
@@ -202,8 +216,8 @@ public class FormFlows {
         return this;
     }
 
-    public FormFlows setErrorType(ErrorType errorType) {
-        this.errorType = errorType;
+    public FormFlows setErrorType(MessageType errorType) {
+        this.messageType = errorType;
         return this;
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
index 200b746..b9f457a 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
@@ -35,12 +35,16 @@ public class Urls {
         return accountBase(baseUri).path(AccountService.class, "accessPage").build(realmId);
     }
 
-    private static UriBuilder accountBase(URI baseUri) {
+    public static UriBuilder accountBase(URI baseUri) {
         return realmBase(baseUri).path(RealmsResource.class, "getAccountService");
     }
 
     public static URI accountPage(URI baseUri, String realmId) {
-        return accountBase(baseUri).path(AccountService.class, "accountPage").build(realmId);
+        return accountPageBuilder(baseUri).build(realmId);
+    }
+
+    public static UriBuilder accountPageBuilder(URI baseUri) {
+        return accountBase(baseUri).path(AccountService.class, "accountPage");
     }
 
     public static URI accountPasswordPage(URI baseUri, String realmId) {
@@ -59,6 +63,10 @@ public class Urls {
         return accountBase(baseUri).path(AccountService.class, "processTotpRemove").build(realmId);
     }
 
+    public static URI accountLogout(URI baseUri, String realmId) {
+        return accountBase(baseUri).path(AccountService.class, "logout").build(realmId);
+    }
+
     public static URI loginActionUpdatePassword(URI baseUri, String realmId) {
         return requiredActionsBase(baseUri).path(RequiredActionsService.class, "updatePassword").build(realmId);
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 7095eab..fba3aa9 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -50,6 +50,7 @@ public class KeycloakApplication extends Application {
         singletons.add(new SaasService(tokenManager));
         singletons.add(new SocialResource(tokenManager, new SocialRequestManager()));
         classes.add(SkeletonKeyContextResolver.class);
+        classes.add(QRCodeResource.class);
     }
 
     protected KeycloakSessionFactory createSessionFactory() {
diff --git a/services/src/main/java/org/keycloak/services/resources/QRCodeResource.java b/services/src/main/java/org/keycloak/services/resources/QRCodeResource.java
new file mode 100644
index 0000000..9d02111
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/QRCodeResource.java
@@ -0,0 +1,52 @@
+package org.keycloak.services.resources;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.WriterException;
+import com.google.zxing.client.j2se.MatrixToImageWriter;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+
+import javax.servlet.ServletException;
+import javax.ws.rs.*;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.StreamingOutput;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+@Path("/qrcode")
+public class QRCodeResource {
+
+    @GET
+    @Produces("image/png")
+    public Response createQrCode(@QueryParam("contents") String contents, @QueryParam("size") String size) throws ServletException, IOException, WriterException {
+        int width = 256;
+        int height = 256;
+
+        if (size != null) {
+            String[] s = size.split("x");
+            width = Integer.parseInt(s[0]);
+            height = Integer.parseInt(s[1]);
+        }
+
+        if (contents == null) {
+            return Response.status(Response.Status.BAD_REQUEST).build();
+        }
+
+        QRCodeWriter writer = new QRCodeWriter();
+        final BitMatrix bitMatrix = writer.encode(contents, BarcodeFormat.QR_CODE, width, height);
+
+        StreamingOutput stream = new StreamingOutput() {
+            @Override
+            public void write(OutputStream os) throws IOException,
+                    WebApplicationException {
+                MatrixToImageWriter.writeToStream(bitMatrix, "png", os);
+            }
+        };
+
+        return Response.ok(stream).build();
+    }
+
+}
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 9914622..54fd5a5 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -1,6 +1,8 @@
 package org.keycloak.services.resources;
 
 import org.jboss.resteasy.logging.Logger;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.Constants;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.TokenManager;
 import org.keycloak.models.KeycloakSession;
@@ -66,7 +68,14 @@ public class RealmsResource {
             logger.debug("realm not found");
             throw new NotFoundException();
         }
-        AccountService accountService = new AccountService(realm, tokenManager);
+
+        ApplicationModel application = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
+        if (application == null || !application.isEnabled()) {
+            logger.debug("account management not enabled");
+            throw new NotFoundException();
+        }
+
+        AccountService accountService = new AccountService(realm, application, tokenManager);
         resourceContext.initResource(accountService);
         return accountService;
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
index 1517b79..eeed370 100755
--- a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
@@ -88,6 +88,12 @@ public class RequiredActionsService {
         }
 
         UserModel user = getUser(accessCode);
+
+        String error = Validation.validateUpdateProfileForm(formData);
+        if (error != null) {
+            return Flows.forms(realm, request, uriInfo).setError(error).forwardToAction(RequiredAction.UPDATE_PROFILE);
+        }
+
         user.setFirstName(formData.getFirst("firstName"));
         user.setLastName(formData.getFirst("lastName"));
         user.setEmail(formData.getFirst("email"));
@@ -121,7 +127,7 @@ public class RequiredActionsService {
 
         UserCredentialModel credentials = new UserCredentialModel();
         credentials.setType(CredentialRepresentation.TOTP);
-        credentials.setValue(formData.getFirst("totpSecret"));
+        credentials.setValue(totpSecret);
         realm.updateCredential(user, credentials);
 
         user.setTotp(true);
@@ -146,15 +152,14 @@ public class RequiredActionsService {
 
         UserModel user = getUser(accessCode);
 
-        String password = formData.getFirst("password");
         String passwordNew = formData.getFirst("password-new");
         String passwordConfirm = formData.getFirst("password-confirm");
 
         FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
         if (Validation.isEmpty(passwordNew)) {
-            forms.setError(Messages.MISSING_PASSWORD).forwardToAction(RequiredAction.UPDATE_PASSWORD);
+            return forms.setError(Messages.MISSING_PASSWORD).forwardToAction(RequiredAction.UPDATE_PASSWORD);
         } else if (!passwordNew.equals(passwordConfirm)) {
-            forms.setError(Messages.MISSING_PASSWORD).forwardToAction(RequiredAction.UPDATE_PASSWORD);
+            return forms.setError(Messages.NOTMATCH_PASSWORD).forwardToAction(RequiredAction.UPDATE_PASSWORD);
         }
 
         UserCredentialModel credentials = new UserCredentialModel();
@@ -257,7 +262,7 @@ public class RequiredActionsService {
 
         new EmailSender().sendPasswordReset(user, realm, accessCode, uriInfo);
 
-        return Flows.forms(realm, request, uriInfo).setError("emailSent").setErrorType(FormFlows.ErrorType.SUCCESS)
+        return Flows.forms(realm, request, uriInfo).setError("emailSent").setErrorType(FormFlows.MessageType.SUCCESS)
                 .forwardToPasswordReset();
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index d72e80d..7296d8e 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -415,7 +415,8 @@ public class TokenService {
         }
         logger.info("accessRequest SUCCESS");
         AccessTokenResponse res = accessTokenResponse(realm.getPrivateKey(), accessCode.getToken());
-        return Response.ok(res).build();
+
+        return Cors.add(request, Response.ok(res)).allowedOrigins(client.getWebOrigins()).build();
     }
 
     protected AccessTokenResponse accessTokenResponse(PrivateKey privateKey, SkeletonKeyToken token) {
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 5a71f0d..c652849 100755
--- a/services/src/main/java/org/keycloak/services/validation/Validation.java
+++ b/services/src/main/java/org/keycloak/services/validation/Validation.java
@@ -38,6 +38,22 @@ public class Validation {
         return null;
     }
 
+    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;
+        }
+
+        if (isEmpty(formData.getFirst("email"))) {
+            return Messages.MISSING_EMAIL;
+        }
+
+        return null;
+    }
+
     public static boolean isEmpty(String s) {
         return s == null || s.length() == 0;
     }
diff --git a/services/src/test/java/org/keycloak/test/ApplicationModelTest.java b/services/src/test/java/org/keycloak/test/ApplicationModelTest.java
index 4466079..57c3ba5 100644
--- a/services/src/test/java/org/keycloak/test/ApplicationModelTest.java
+++ b/services/src/test/java/org/keycloak/test/ApplicationModelTest.java
@@ -1,23 +1,20 @@
 package org.keycloak.test;
 
-import java.util.Iterator;
-import java.util.List;
-
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
-import org.keycloak.models.ApplicationModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.KeycloakSessionFactory;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.RoleModel;
-import org.keycloak.models.UserModel;
+import org.keycloak.models.*;
 import org.keycloak.representations.idm.ApplicationRepresentation;
 import org.keycloak.services.managers.ApplicationManager;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.resources.KeycloakApplication;
 
+import java.util.Iterator;
+import java.util.List;
+
+import static org.junit.Assert.assertNotNull;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -49,6 +46,9 @@ public class ApplicationModelTest extends AbstractKeycloakServerTest {
         application.getApplicationUser().addRedirectUri("redirect-1");
         application.getApplicationUser().addRedirectUri("redirect-2");
 
+        application.getApplicationUser().addWebOrigin("origin-1");
+        application.getApplicationUser().addWebOrigin("origin-2");
+
         application.updateApplication();
     }
 
@@ -85,6 +85,7 @@ public class ApplicationModelTest extends AbstractKeycloakServerTest {
         UserModel euser = expected.getApplicationUser();
 
         Assert.assertTrue(euser.getRedirectUris().containsAll(auser.getRedirectUris()));
+        Assert.assertTrue(euser.getWebOrigins().containsAll(auser.getWebOrigins()));
     }
 
     public static void assertEquals(List<RoleModel> expected, List<RoleModel> actual) {
diff --git a/services/src/test/java/org/keycloak/test/UserModelTest.java b/services/src/test/java/org/keycloak/test/UserModelTest.java
index 9922596..9029511 100644
--- a/services/src/test/java/org/keycloak/test/UserModelTest.java
+++ b/services/src/test/java/org/keycloak/test/UserModelTest.java
@@ -53,10 +53,33 @@ public class UserModelTest extends AbstractKeycloakServerTest {
         user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
         user.addRequiredAction(RequiredAction.UPDATE_PASSWORD);
 
+        user.addWebOrigin("origin-1");
+        user.addWebOrigin("origin-2");
+
         UserModel persisted = manager.getRealm(realm.getId()).getUser("user");
 
         assertEquals(user, persisted);
     }
+    
+    @Test
+    public void webOriginSetTest() {
+        RealmModel realm = manager.createRealm("original");
+        UserModel user = realm.addUser("user");
+
+        Assert.assertTrue(user.getWebOrigins().isEmpty());
+
+        user.addWebOrigin("origin-1");
+        Assert.assertEquals(1, user.getWebOrigins().size());
+
+        user.addWebOrigin("origin-2");
+        Assert.assertEquals(2, user.getWebOrigins().size());
+
+        user.removeWebOrigin("origin-2");
+        Assert.assertEquals(1, user.getWebOrigins().size());
+
+        user.removeWebOrigin("origin-1");
+        Assert.assertTrue(user.getWebOrigins().isEmpty());
+    }
 
     @Test
     public void testUserRequiredActions() throws Exception {
@@ -102,7 +125,7 @@ public class UserModelTest extends AbstractKeycloakServerTest {
         Assert.assertEquals(expected.getLastName(), actual.getLastName());
         Assert.assertArrayEquals(expected.getRedirectUris().toArray(), actual.getRedirectUris().toArray());
         Assert.assertArrayEquals(expected.getRequiredActions().toArray(), actual.getRequiredActions().toArray());
-
+        Assert.assertArrayEquals(expected.getWebOrigins().toArray(), actual.getWebOrigins().toArray());
     }
 
     public static void assertEquals(List<RoleModel> expected, List<RoleModel> actual) {
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index 3614016..0e121c7 100644
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -102,6 +102,10 @@
             <artifactId>picketlink-config</artifactId>
         </dependency>
         <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.jboss.resteasy</groupId>
             <artifactId>resteasy-jaxrs</artifactId>
             <exclusions>
diff --git a/testsuite/integration/src/main/resources/log4j.properties b/testsuite/integration/src/main/resources/log4j.properties
new file mode 100644
index 0000000..b3c1c5c
--- /dev/null
+++ b/testsuite/integration/src/main/resources/log4j.properties
@@ -0,0 +1,5 @@
+log4j.rootLogger=debug, stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] %m%n
\ No newline at end of file
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
index 08a2bfa..c567e99 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
@@ -44,8 +44,8 @@ import org.openqa.selenium.WebDriver;
  */
 public class RequiredActionUpdateProfileTest {
 
-    @ClassRule
-    public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakSetup() {
+    @Rule
+    public KeycloakRule keycloakRule = new KeycloakRule(new KeycloakSetup() {
 
         @Override
         public void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm) {
@@ -83,4 +83,50 @@ public class RequiredActionUpdateProfileTest {
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
     }
 
+    @Test
+    public void updateProfileMissingFirstName() {
+        loginPage.open();
+
+        loginPage.login("test-user@localhost", "password");
+
+        updateProfilePage.assertCurrent();
+
+        updateProfilePage.update("", "New last", "new@email.com");
+
+        updateProfilePage.assertCurrent();
+
+        Assert.assertEquals("Please specify first name", updateProfilePage.getError());
+    }
+
+    @Test
+    public void updateProfileMissingLastName() {
+        loginPage.open();
+
+        loginPage.login("test-user@localhost", "password");
+
+        updateProfilePage.assertCurrent();
+
+        updateProfilePage.update("New first", "", "new@email.com");
+
+        updateProfilePage.assertCurrent();
+
+        Assert.assertEquals("Please specify last name", updateProfilePage.getError());
+    }
+
+    @Test
+    public void updateProfileMissingEmail() {
+        loginPage.open();
+
+        loginPage.login("test-user@localhost", "password");
+
+        updateProfilePage.assertCurrent();
+
+        updateProfilePage.update("New first", "New last", "");
+
+        updateProfilePage.assertCurrent();
+
+        Assert.assertEquals("Please specify email", updateProfilePage.getError());
+    }
+
+
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java
index 3d62d55..4f8e75a 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java
@@ -21,11 +21,7 @@
  */
 package org.keycloak.testsuite.forms;
 
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
+import org.junit.*;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.models.RealmModel;
@@ -97,13 +93,12 @@ public class AccountTest {
 
     @Test
     public void changePassword() {
-        loginPage.open();
+        changePasswordPage.open();
         loginPage.login("test-user@localhost", "password");
 
-        changePasswordPage.open();
         changePasswordPage.changePassword("password", "new-password", "new-password");
 
-        oauth.openLogout();
+        changePasswordPage.logout();
 
         loginPage.open();
         loginPage.login("test-user@localhost", "password");
@@ -118,10 +113,8 @@ public class AccountTest {
 
     @Test
     public void changeProfile() {
-        loginPage.open();
-        loginPage.login("test-user@localhost", "password");
-
         profilePage.open();
+        loginPage.login("test-user@localhost", "password");
 
         Assert.assertEquals("", profilePage.getFirstName());
         Assert.assertEquals("", profilePage.getLastName());
@@ -136,10 +129,8 @@ public class AccountTest {
 
     @Test
     public void setupTotp() {
-        loginPage.open();
-        loginPage.login("test-user@localhost", "password");
-
         totpPage.open();
+        loginPage.login("test-user@localhost", "password");
 
         Assert.assertTrue(totpPage.isCurrent());
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 9b5ca0f..396f3d9 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -123,7 +123,7 @@ public class ResetPasswordTest {
         resetPasswordPage.assertCurrent();
 
         Assert.assertNotEquals("Success!", resetPasswordPage.getMessage());
-        Assert.assertEquals("Error!", resetPasswordPage.getMessage());
+        Assert.assertEquals("Invalid username or email.", resetPasswordPage.getMessage());
     }
 
     @Test
@@ -138,7 +138,7 @@ public class ResetPasswordTest {
         resetPasswordPage.assertCurrent();
 
         Assert.assertNotEquals("Success!", resetPasswordPage.getMessage());
-        Assert.assertEquals("Error!", resetPasswordPage.getMessage());
+        Assert.assertEquals("Invalid username or email.", resetPasswordPage.getMessage());
     }
 
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
index b53e88f..0a94649 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
@@ -28,7 +28,7 @@ import org.openqa.selenium.support.FindBy;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class AccountPasswordPage extends Page {
+public class AccountPasswordPage extends AbstractAccountPage {
 
     private static String PATH = Constants.AUTH_SERVER_ROOT + "/rest/realms/test/account/password";
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java
index 11cc502..171e6ef 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java
@@ -28,7 +28,7 @@ import org.openqa.selenium.support.FindBy;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class AccountTotpPage extends Page {
+public class AccountTotpPage extends AbstractAccountPage {
 
     private static String PATH = Constants.AUTH_SERVER_ROOT + "/rest/realms/test/account/totp";
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
index c890f2f..7c99e1b 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
@@ -28,7 +28,7 @@ import org.openqa.selenium.support.FindBy;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class AccountUpdateProfilePage extends Page {
+public class AccountUpdateProfilePage extends AbstractAccountPage {
 
     private static String PATH = Constants.AUTH_SERVER_ROOT + "/rest/realms/test/account";
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java
index 4270640..3faa197 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java
@@ -25,7 +25,7 @@ package org.keycloak.testsuite.pages;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class AppPage extends Page {
+public class AppPage extends AbstractPage {
 
     private String baseUrl = "http://localhost:8081/app";
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java
index bfe3201..cc06da5 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java
@@ -23,14 +23,13 @@ package org.keycloak.testsuite.pages;
 
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.rule.WebResource;
-import org.openqa.selenium.By;
 import org.openqa.selenium.WebElement;
 import org.openqa.selenium.support.FindBy;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class ErrorPage extends Page {
+public class ErrorPage extends AbstractPage {
 
     @WebResource
     protected OAuthClient oauth;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java
index f2e2300..5b1613a 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java
@@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class LoginConfigTotpPage extends Page {
+public class LoginConfigTotpPage extends AbstractPage {
 
     @FindBy(id = "totpSecret")
     private WebElement totpSecret;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
index d099690..28e87cf 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
@@ -30,7 +30,7 @@ import org.openqa.selenium.support.FindBy;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class LoginPage extends Page {
+public class LoginPage extends AbstractPage {
 
     @WebResource
     protected OAuthClient oauth;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java
index 45fc30a..fc62c3c 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java
@@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class LoginPasswordResetPage extends Page {
+public class LoginPasswordResetPage extends AbstractPage {
 
     @FindBy(id = "username")
     private WebElement usernameInput;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java
index 100b891..be5df8c 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java
@@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class LoginPasswordUpdatePage extends Page {
+public class LoginPasswordUpdatePage extends AbstractPage {
 
     @FindBy(id = "password-new")
     private WebElement newPasswordInput;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java
index 67b3801..2a7ed57 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java
@@ -28,7 +28,7 @@ import org.openqa.selenium.support.FindBy;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class LoginTotpPage extends Page {
+public class LoginTotpPage extends AbstractPage {
 
     @FindBy(id = "totp")
     private WebElement totpInput;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java
index 9a4b454..d25959d 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java
@@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class LoginUpdateProfilePage extends Page {
+public class LoginUpdateProfilePage extends AbstractPage {
 
     @FindBy(id = "firstName")
     private WebElement firstNameInput;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/OAuthGrantPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/OAuthGrantPage.java
index 8749a91..7fdbe06 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/OAuthGrantPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/OAuthGrantPage.java
@@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class OAuthGrantPage extends Page {
+public class OAuthGrantPage extends AbstractPage {
 
     @FindBy(css = "input[name=\"accept\"]")
     private WebElement acceptButton;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
index 7ff4d7f..c1b52c5 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
@@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class RegisterPage extends Page {
+public class RegisterPage extends AbstractPage {
 
     @FindBy(id = "firstName")
     private WebElement firstNameInput;
@@ -85,6 +85,22 @@ public class RegisterPage extends Page {
         return loginErrorMessage != null ? loginErrorMessage.getText() : null;
     }
 
+    public String getFirstName() {
+        return firstNameInput.getAttribute("value");
+    }
+
+    public String getLastName() {
+        return lastNameInput.getAttribute("value");
+    }
+
+    public String getEmail() {
+        return emailInput.getAttribute("value");
+    }
+
+    public String getUsername() {
+        return usernameInput.getAttribute("value");
+    }
+
     public boolean isCurrent() {
         return driver.getTitle().equals("Register with test");
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java
index 5e8c24c..cfcfbb4 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java
@@ -29,7 +29,7 @@ import org.openqa.selenium.support.FindBy;
 /**
  * @author <a href="mailto:vrockai@redhat.com">Viliam Rockai</a>
  */
-public class VerifyEmailPage extends Page {
+public class VerifyEmailPage extends AbstractPage {
 
     @WebResource
     protected OAuthClient oauth;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java
index d9f789d..88301c5 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java
@@ -25,7 +25,7 @@ import java.lang.reflect.Field;
 
 import org.junit.rules.ExternalResource;
 import org.keycloak.testsuite.OAuthClient;
-import org.keycloak.testsuite.pages.Page;
+import org.keycloak.testsuite.pages.AbstractPage;
 import org.openqa.selenium.WebDriver;
 import org.openqa.selenium.chrome.ChromeDriver;
 import org.openqa.selenium.firefox.FirefoxDriver;
@@ -78,7 +78,7 @@ public class WebRule extends ExternalResource {
                     Class<?> type = f.getType();
                     if (type.equals(WebDriver.class)) {
                         set(f, o, driver);
-                    } else if (Page.class.isAssignableFrom(type)) {
+                    } else if (AbstractPage.class.isAssignableFrom(type)) {
                         set(f, o, getPage(f.getType()));
                     } else if (type.equals(OAuthClient.class)) {
                         set(f, o, oauth);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java
index f4cc01c..056af5c 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java
@@ -35,6 +35,7 @@ import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
 import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.RegisterPage;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
 import org.keycloak.testsuite.rule.WebResource;
@@ -69,6 +70,9 @@ public class SocialLoginTest {
     protected LoginPage loginPage;
 
     @WebResource
+    protected RegisterPage registerPage;
+
+    @WebResource
     protected OAuthClient oauth;
 
     @BeforeClass
@@ -97,4 +101,41 @@ public class SocialLoginTest {
         Assert.assertTrue(token.getRealmAccess().isUserInRole("user"));
     }
 
+    @Test
+    public void registerRequired() {
+        keycloakRule.configure(new KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.setAutomaticRegistrationAfterSocialLogin(false);
+            }
+        });
+
+        try {
+            loginPage.open();
+
+            loginPage.clickSocial("dummy");
+
+            driver.findElement(By.id("username")).sendKeys("dummy-user-reg");
+            driver.findElement(By.id("submit")).click();
+
+            registerPage.isCurrent();
+
+            Assert.assertEquals("", registerPage.getFirstName());
+            Assert.assertEquals("", registerPage.getLastName());
+            Assert.assertEquals("dummy-user-reg@dummy-social", registerPage.getEmail());
+            Assert.assertEquals("dummy-user-reg", registerPage.getUsername());
+
+            registerPage.register("Dummy", "User", "dummy-user-reg@dummy-social", "dummy-user-reg", "password", "password");
+
+            Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        } finally {
+            keycloakRule.configure(new KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    appRealm.setAutomaticRegistrationAfterSocialLogin(true);
+                }
+            });
+        }
+    }
+
 }
diff --git a/testsuite/integration/src/test/resources/testrealm.json b/testsuite/integration/src/test/resources/testrealm.json
index 941c5db..161f11e 100755
--- a/testsuite/integration/src/test/resources/testrealm.json
+++ b/testsuite/integration/src/test/resources/testrealm.json
@@ -8,6 +8,7 @@
     "sslNotRequired": true,
     "cookieLoginAllowed": true,
     "registrationAllowed": true,
+    "accountManagement": true,
     "resetPasswordAllowed": true,
     "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
     "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",