keycloak-memoizeit

Started adding totp flow

8/20/2013 1:15:10 PM

Details

diff --git a/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json b/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json
index f92e34c..910d488 100755
--- a/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json
+++ b/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json
@@ -8,7 +8,7 @@
     "registrationAllowed": 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" ],
+    "requiredCredentials": [ "password", "totp" ],
     "requiredApplicationCredentials": [ "password" ],
     "requiredOAuthClientCredentials": [ "password" ],
     "defaultRoles": [ "user" ],
diff --git a/forms/src/main/java/org/keycloak/forms/LoginBean.java b/forms/src/main/java/org/keycloak/forms/LoginBean.java
index aa02c92..a028886 100644
--- a/forms/src/main/java/org/keycloak/forms/LoginBean.java
+++ b/forms/src/main/java/org/keycloak/forms/LoginBean.java
@@ -47,6 +47,8 @@ public class LoginBean {
 
     private String username;
 
+    private String password;
+
     private List<RequiredCredential> requiredCredentials;
 
     @PostConstruct
@@ -58,6 +60,7 @@ public class LoginBean {
         MultivaluedMap<String, String> formData = (MultivaluedMap<String, String>) request.getAttribute(FormFlows.DATA);
         if (formData != null) {
             username = formData.getFirst("username");
+            password = formData.getFirst("password");
         }
 
         requiredCredentials = new LinkedList<RequiredCredential>();
@@ -72,6 +75,10 @@ public class LoginBean {
         return username;
     }
 
+    public String getPassword() {
+        return password;
+    }
+
     public List<RequiredCredential> getRequiredCredentials() {
         return requiredCredentials;
     }
diff --git a/forms/src/main/java/org/keycloak/forms/TotpBean.java b/forms/src/main/java/org/keycloak/forms/TotpBean.java
index 3ca4ddb..b0a412b 100644
--- a/forms/src/main/java/org/keycloak/forms/TotpBean.java
+++ b/forms/src/main/java/org/keycloak/forms/TotpBean.java
@@ -28,6 +28,7 @@ import java.util.Random;
 import javax.annotation.PostConstruct;
 import javax.faces.application.FacesMessage;
 import javax.faces.bean.ManagedBean;
+import javax.faces.bean.ManagedProperty;
 import javax.faces.bean.RequestScoped;
 import javax.faces.context.FacesContext;
 
@@ -40,12 +41,14 @@ import org.picketlink.common.util.Base32;
 @RequestScoped
 public class TotpBean {
 
+    @ManagedProperty(value = "#{user}")
+    private UserBean user;
+
     private String totpSecret;
     private String totpSecretEncoded;
 
     @PostConstruct
     public void init() {
-
         FacesContext facesContext = FacesContext.getCurrentInstance();
         FacesMessage facesMessage = new FacesMessage("This is a message");
         facesContext.addMessage(null, facesMessage);
@@ -65,6 +68,10 @@ public class TotpBean {
         return sb.toString();
     }
 
+    public boolean isEnabled() {
+        return "ENABLED".equals(user.getUser().getAttribute("KEYCLOAK_TOTP"));
+    }
+
     public String getTotpSecret() {
         return totpSecret;
     }
@@ -86,5 +93,13 @@ public class TotpBean {
         return contextPath + "/forms/qrcode" + "?size=200x200&contents=" + contents;
     }
 
+    public UserBean getUser() {
+        return user;
+    }
+
+    public void setUser(UserBean user) {
+        this.user = user;
+    }
+
 }
 
diff --git a/forms/src/main/java/org/keycloak/forms/UserBean.java b/forms/src/main/java/org/keycloak/forms/UserBean.java
index b1b11bc..0be17b3 100644
--- a/forms/src/main/java/org/keycloak/forms/UserBean.java
+++ b/forms/src/main/java/org/keycloak/forms/UserBean.java
@@ -21,13 +21,14 @@
  */
 package org.keycloak.forms;
 
-import java.util.Map;
-import java.util.Map.Entry;
-
 import javax.annotation.PostConstruct;
 import javax.faces.bean.ManagedBean;
 import javax.faces.bean.RequestScoped;
 import javax.faces.context.FacesContext;
+import javax.servlet.http.HttpServletRequest;
+
+import org.keycloak.services.models.UserModel;
+import org.keycloak.services.resources.flows.FormFlows;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -36,17 +37,34 @@ import javax.faces.context.FacesContext;
 @RequestScoped
 public class UserBean {
 
+    private UserModel user;
+
     @PostConstruct
     public void init() {
         FacesContext ctx = FacesContext.getCurrentInstance();
-        Map<String, Object> map = ctx.getExternalContext().getRequestCookieMap();
-        for (Entry<String, Object> c : map.entrySet()) {
-            System.out.println(c.getKey());
-        }
+        HttpServletRequest request = (HttpServletRequest) ctx.getExternalContext().getRequest();
+
+        user = (UserModel) request.getAttribute(FormFlows.USER);
+    }
+
+    public String getFirstName() {
+        return user.getFirstName();
+    }
+
+    public String getLastName() {
+        return user.getLastName();
+    }
+
+    public String getUsername() {
+        return user.getLoginName();
+    }
+
+    public String getEmail() {
+        return user.getEmail();
     }
 
-    public boolean isLoggedIn() {
-        return false;
+    UserModel getUser() {
+        return user;
     }
 
 }
diff --git a/forms/src/main/resources/META-INF/resources/forms/login-totp.xhtml b/forms/src/main/resources/META-INF/resources/forms/login-totp.xhtml
new file mode 100644
index 0000000..a757b27
--- /dev/null
+++ b/forms/src/main/resources/META-INF/resources/forms/login-totp.xhtml
@@ -0,0 +1,2 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<ui:include xmlns:ui="http://java.sun.com/jsf/facelets" src="theme/#{template.theme}/login-totp.xhtml" />
\ No newline at end of file
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/account.xhtml b/forms/src/main/resources/META-INF/resources/forms/theme/default/account.xhtml
index cbab6e8..07e0ca4 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/account.xhtml
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/account.xhtml
@@ -5,18 +5,22 @@
 	<ui:define name="header">Edit Account</ui:define>
 	
 	<ui:define name="content">
-		<form action="#" method="post">
+		<form action="#{url.accountUrl}" method="post">
 			<div>
-            	<label for="name">#{messages.fullName}</label>
-                <input type="text" id="name" name="name" value="#{forms.formData['name']}" />
+            	<label for="firstName">#{messages.firstName}</label>
+                <input type="text" id="firstName" name="firstName" value="#{user.firstName}" />
+			</div>
+			<div>
+            	<label for="lastName">#{messages.lastName}</label>
+                <input type="text" id="lastName" name="lastName" value="#{user.lastName}" />
 			</div>
             <div>
             	<label for="email">#{messages.email}</label>
-				<input type="text" id="email" name="email" value="#{forms.formData['email']}" />
+				<input type="text" id="email" name="email" value="#{user.email}" />
 			</div>
 			<div>
 				<label for="username">#{messages.username}</label>
-				<input type="text" id="username" name="username" value="#{forms.formData['username']}" />
+				<input type="text" id="username" name="username" value="#{user.username}" disabled="true" />
 			</div>
 
 			<input type="button" value="Cancel" />
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-totp.xhtml b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-totp.xhtml
new file mode 100755
index 0000000..1deed99
--- /dev/null
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-totp.xhtml
@@ -0,0 +1,31 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<ui:composition xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets"
+	xmlns:c="http://java.sun.com/jstl/core" template="template-login.xhtml">
+
+	<ui:define name="header">Log in to <strong>#{realm.name}</strong></ui:define>
+	
+	<ui:define name="form">
+		<form action="#{url.loginAction}" method="post">
+			<input id="username" name="username" value="#{login.username}" type="hidden" />
+			<input id="password" name="password" value="#{login.password}" type="hidden" />
+			
+			<div>
+				<label for="totp">#{messages.authenticatorCode}</label>
+				<input id="totp" name="totp" type="text" />
+			</div>
+
+			<div class="aside-btn">
+				<!-- <input type="checkbox" id="remember" /><label for="remember">Remember Username</label> -->
+				<!-- <p>Forgot <a href="#">Username</a> or <a href="#">Password</a>?</p> -->
+			</div>
+
+			<input type="submit" value="Log In" />
+		</form>
+	</ui:define>
+	
+	<ui:define name="info">
+		<h:panelGroup rendered="#{realm.registrationAllowed}">
+			<p>#{messages.noAccount} <a href="#{url.registrationUrl}">#{messages.register}</a>.</p>
+		</h:panelGroup>	
+	</ui:define>
+</ui:composition>
\ No newline at end of file
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/password.xhtml b/forms/src/main/resources/META-INF/resources/forms/theme/default/password.xhtml
index f94a005..05384b1 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/password.xhtml
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/password.xhtml
@@ -5,14 +5,14 @@
 	<ui:define name="header">Change Password</ui:define>
 	
 	<ui:define name="content">
-		<form action="#" method="post">
+		<form action="#{url.passwordUrl}" method="post">
             <div>
             	<label for="password">#{messages.password}</label>
 				<input type="password" id="password" name="password" />
 			</div>
             <div>
-            	<label for="password">#{messages.passwordNew}</label>
-				<input type="passwordNew" id="passwordNew" name="passwordNew" />
+            	<label for="password-new">#{messages.passwordNew}</label>
+				<input type="password" id="password-new" name="password-new" />
 			</div>
             <div>
             	<label for="password-confirm">#{messages.passwordConfirm}</label>
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.xhtml b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.xhtml
index 09d546f..acbe73c 100644
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.xhtml
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.xhtml
@@ -32,6 +32,10 @@ body {
 		<h1>
 			<ui:insert name="header" />
 		</h1>
+				
+		<h:panelGroup rendered="#{not empty error.summary}">
+			<div class="alert alert-danger">#{messages[error.summary]}</div>
+		</h:panelGroup>
 
 		<ui:insert name="content" />
 	</div>
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/totp.xhtml b/forms/src/main/resources/META-INF/resources/forms/theme/default/totp.xhtml
index 790b173..c3b04d1 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/totp.xhtml
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/totp.xhtml
@@ -6,7 +6,11 @@
 
 	<ui:define name="content">
 	<h:messages globalOnly="true" />
-	
+		<h:panelGroup rendered="#{totp.enabled}">
+			Google Authenticator enabled	
+		</h:panelGroup>
+
+		<h:panelGroup rendered="#{not totp.enabled}">	
 		<h2>To setup Google Authenticator</h2>
 
 		<ol>
@@ -16,11 +20,11 @@
 			</li>
 			<li>Enter a one-time password provided by Google Authenticator and click Save to finish the setup
 
-				<form action="#" method="post">
+				<form action="#{url.totpUrl}" method="post">
 					<div>
 						<label for="totp">#{messages.authenticatorCode}</label>
 						<input type="text" id="totp" name="totp" />
-						<input type="hidden" id="totpSecret" name="totpSecret" value="{forms.totpSecret}" />
+						<input type="hidden" id="totpSecret" name="totpSecret" value="#{totp.totpSecret}" />
 					</div>
 
 					<input type="button" value="Cancel" />
@@ -28,5 +32,6 @@
 				</form>
 			</li>
 		</ol>
+		</h:panelGroup>
 	</ui:define>
 </ui:composition>
\ No newline at end of file
diff --git a/forms/src/main/resources/org/keycloak/forms/messages.properties b/forms/src/main/resources/org/keycloak/forms/messages.properties
index 7997683..50e21b6 100644
--- a/forms/src/main/resources/org/keycloak/forms/messages.properties
+++ b/forms/src/main/resources/org/keycloak/forms/messages.properties
@@ -11,6 +11,8 @@ poweredByKeycloak=Powered by Keycloak
 
 username=Username
 fullName=Full name
+firstName=First name
+lastName=Last name
 email=Email
 password=Password
 passwordConfirm=Confirm Password
@@ -29,6 +31,7 @@ missingUsername=Please specify username
 missingPassword=Please specify password
 missingTotp=Please specify authenticator code
 
+invalidPasswordExisting=Invalid existing password
 invalidPasswordConfirm=Password confirmation doesn't match
 invalidTotp=Invalid authenticator code
 
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 7ecc27a..728df77 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -211,6 +211,10 @@ public class AuthenticationManager {
             requiredCredentials = realm.getRequiredOAuthClientCredentials();
         } else {
             requiredCredentials = realm.getRequiredCredentials();
+
+            if (!types.contains(CredentialRepresentation.TOTP) && "ENABLED".equals(user.getAttribute("KEYCLOAK_TOTP"))) {
+                types.add(CredentialRepresentation.TOTP);
+            }
         }
         for (RequiredCredentialModel credential : requiredCredentials) {
             types.add(credential.getType());
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 60ea232..c6165a9 100644
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -30,6 +30,8 @@ public class Messages {
 
     public static final String INVALID_PASSWORD = "invalidPassword";
 
+    public static final String INVALID_PASSWORD_EXISTING = "invalidPasswordExisting";
+
     public static final String INVALID_PASSWORD_CONFIRM = "invalidPasswordConfirm";
 
     public static final String INVALID_USER = "invalidUser";
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 72862eb..30428af 100644
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -21,14 +21,29 @@
  */
 package org.keycloak.services.resources;
 
+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.core.UriInfo;
+import javax.ws.rs.core.Response.Status;
 
 import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.messages.Messages;
 import org.keycloak.services.models.RealmModel;
+import org.keycloak.services.models.UserCredentialModel;
+import org.keycloak.services.models.UserModel;
 import org.keycloak.services.resources.flows.Flows;
+import org.keycloak.services.resources.flows.FormFlows;
+import org.keycloak.services.validation.Validation;
+import org.picketlink.idm.credential.util.TimeBasedOTP;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -40,6 +55,14 @@ public class AccountService {
     @Context
     private HttpRequest request;
 
+    @Context
+    protected HttpHeaders headers;
+
+    @Context
+    private UriInfo uriInfo;
+
+    protected AuthenticationManager authManager = new AuthenticationManager();
+
     public AccountService(RealmModel realm) {
         this.realm = realm;
     }
@@ -49,7 +72,122 @@ public class AccountService {
     public Response accessPage() {
         return new Transaction<Response>() {
             protected Response callImpl() {
-                return Flows.forms(realm, request).forwardToAccess();
+                UserModel user = authManager.authenticateIdentityCookie(realm, uriInfo, headers);
+                if (user != null) {
+                    return Flows.forms(realm, request).setUser(user).forwardToAccess();
+                } else {
+                    return Response.status(Status.FORBIDDEN).build();
+                }
+            }
+        }.call();
+    }
+
+    @Path("")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response processAccountUpdate(final MultivaluedMap<String, String> formData) {
+        return new Transaction<Response>() {
+            protected Response callImpl() {
+                UserModel user = authManager.authenticateIdentityCookie(realm, uriInfo, headers);
+                if (user != null) {
+                    user.setFirstName(formData.getFirst("firstName"));
+                    user.setLastName(formData.getFirst("lastName"));
+                    user.setEmail(formData.getFirst("email"));
+
+                    return Flows.forms(realm, request).setUser(user).forwardToAccount();
+                } else {
+                    return Response.status(Status.FORBIDDEN).build();
+                }
+            }
+        }.call();
+    }
+
+    @Path("totp")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response processTotpUpdate(final MultivaluedMap<String, String> formData) {
+        return new Transaction<Response>() {
+            protected Response callImpl() {
+                UserModel user = authManager.authenticateIdentityCookie(realm, uriInfo, headers);
+                if (user != null) {
+                    FormFlows forms = Flows.forms(realm, request);
+
+                    String totp = formData.getFirst("totp");
+                    String totpSecret = formData.getFirst("totpSecret");
+
+                    String error = null;
+
+                    if (Validation.isEmpty(totp)) {
+                        error = Messages.MISSING_TOTP;
+                    } else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
+                        error = Messages.INVALID_TOTP;
+                    }
+                    
+                    if (error != null) {
+                        return forms.setError(error).forwardToTotp();
+                    }
+
+                    UserCredentialModel credentials = new UserCredentialModel();
+                    credentials.setType(CredentialRepresentation.TOTP);
+                    credentials.setValue(formData.getFirst("totpSecret"));
+                    realm.updateCredential(user, credentials);
+
+                    if (!user.isEnabled() && "REQUIRED".equals(user.getAttribute("KEYCLOAK_TOTP"))) {
+                        user.setEnabled(true);
+                    }
+
+                    user.setAttribute("KEYCLOAK_TOTP", "ENABLED");
+
+                    return Flows.forms(realm, request).setUser(user).forwardToTotp();
+                } else {
+                    return Response.status(Status.FORBIDDEN).build();
+                }
+            }
+        }.call();
+    }
+
+    @Path("password")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response processPasswordUpdate(final MultivaluedMap<String, String> formData) {
+        return new Transaction<Response>() {
+            protected Response callImpl() {
+                UserModel user = authManager.authenticateIdentityCookie(realm, uriInfo, headers);
+                if (user != null) {
+                    FormFlows forms = Flows.forms(realm, request).setUser(user);
+
+                    String password = formData.getFirst("password");
+                    String passwordNew = formData.getFirst("password-new");
+                    String passwordConfirm = formData.getFirst("password-confirm");
+
+                    String error = null;
+
+                    if (Validation.isEmpty(password)) {
+                        error = Messages.MISSING_PASSWORD;
+                    } else if (Validation.isEmpty(passwordNew)) {
+                        error = Messages.MISSING_PASSWORD;
+                    } else if (!passwordNew.equals(passwordConfirm)) {
+                        error = Messages.INVALID_PASSWORD_CONFIRM;
+                    } else if (!realm.validatePassword(user, password)) {
+                        error = Messages.INVALID_PASSWORD_EXISTING;
+                    }
+
+                    if (error != null) {
+                        return forms.setError(error).forwardToPassword();
+                    }
+
+                    UserCredentialModel credentials = new UserCredentialModel();
+                    credentials.setType(CredentialRepresentation.PASSWORD);
+                    credentials.setValue(passwordNew);
+
+                    realm.updateCredential(user, credentials);
+
+                    authManager.expireIdentityCookie(realm, uriInfo);
+
+                    return Flows.forms(realm, request).setUser(user).forwardToPassword();
+                } else {
+                    return Response.status(Status.FORBIDDEN).build();
+                }
             }
         }.call();
     }
@@ -59,7 +197,12 @@ public class AccountService {
     public Response accountPage() {
         return new Transaction<Response>() {
             protected Response callImpl() {
-                return Flows.forms(realm, request).forwardToAccount();
+                UserModel user = authManager.authenticateIdentityCookie(realm, uriInfo, headers);
+                if (user != null) {
+                    return Flows.forms(realm, request).setUser(user).forwardToAccount();
+                } else {
+                    return Response.status(Status.FORBIDDEN).build();
+                }
             }
         }.call();
     }
@@ -69,7 +212,12 @@ public class AccountService {
     public Response socialPage() {
         return new Transaction<Response>() {
             protected Response callImpl() {
-                return Flows.forms(realm, request).forwardToSocial();
+                UserModel user = authManager.authenticateIdentityCookie(realm, uriInfo, headers);
+                if (user != null) {
+                    return Flows.forms(realm, request).setUser(user).forwardToSocial();
+                } else {
+                    return Response.status(Status.FORBIDDEN).build();
+                }
             }
         }.call();
     }
@@ -79,7 +227,12 @@ public class AccountService {
     public Response totpPage() {
         return new Transaction<Response>() {
             protected Response callImpl() {
-                return Flows.forms(realm, request).forwardToTotp();
+                UserModel user = authManager.authenticateIdentityCookie(realm, uriInfo, headers);
+                if (user != null) {
+                    return Flows.forms(realm, request).setUser(user).forwardToTotp();
+                } else {
+                    return Response.status(Status.FORBIDDEN).build();
+                }
             }
         }.call();
     }
@@ -89,7 +242,12 @@ public class AccountService {
     public Response passwordPage() {
         return new Transaction<Response>() {
             protected Response callImpl() {
-                return Flows.forms(realm, request).forwardToPassword();
+                UserModel user = authManager.authenticateIdentityCookie(realm, uriInfo, headers);
+                if (user != null) {
+                    return Flows.forms(realm, request).setUser(user).forwardToPassword();
+                } else {
+                    return Response.status(Status.FORBIDDEN).build();
+                }
             }
         }.call();
     }
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 64817d8..3804b44 100644
--- a/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
@@ -26,6 +26,7 @@ import javax.ws.rs.core.Response;
 
 import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.services.models.RealmModel;
+import org.keycloak.services.models.UserModel;
 import org.picketlink.idm.model.sample.Realm;
 
 /**
@@ -36,6 +37,7 @@ public class FormFlows {
     public static final String DATA = "KEYCLOAK_FORMS_DATA";
     public static final String ERROR_MESSAGE = "KEYCLOAK_FORMS_ERROR_MESSAGE";
     public static final String REALM = Realm.class.getName();
+    public static final String USER = UserModel.class.getName();
 
     private String error;
     private MultivaluedMap<String, String> formData;
@@ -43,6 +45,7 @@ public class FormFlows {
     private RealmModel realm;
 
     private HttpRequest request;
+    private UserModel userModel;
 
     FormFlows(RealmModel realm, HttpRequest request) {
         this.realm = realm;
@@ -68,6 +71,10 @@ public class FormFlows {
             request.setAttribute(DATA, formData);
         }
 
+        if (userModel != null) {
+            request.setAttribute(USER, userModel);
+        }
+
         request.forward(form);
         return null;
     }
@@ -76,6 +83,10 @@ public class FormFlows {
         return forwardToForm(Pages.LOGIN);
     }
 
+    public Response forwardToLoginTotp() {
+        return forwardToForm(Pages.LOGIN_TOTP);
+    }
+
     public Response forwardToPassword() {
         return forwardToForm(Pages.PASSWORD);
     }
@@ -97,6 +108,11 @@ public class FormFlows {
         return this;
     }
 
+    public FormFlows setUser(UserModel userModel) {
+        this.userModel = userModel;
+        return this;
+    }
+
     public FormFlows setFormData(MultivaluedMap<String, String> formData) {
         this.formData = formData;
         return this;
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Pages.java b/services/src/main/java/org/keycloak/services/resources/flows/Pages.java
index e46a6f8..806ed63 100644
--- a/services/src/main/java/org/keycloak/services/resources/flows/Pages.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/Pages.java
@@ -32,6 +32,8 @@ public class Pages {
 
     public final static String LOGIN = "/forms/login.xhtml";
 
+    public final static String LOGIN_TOTP = "/forms/login-totp.xhtml";
+
     public final static String OAUTH_GRANT = "/saas/oauthGrantForm.jsp";
 
     public final static String PASSWORD = "/forms/password.xhtml";
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 a257bf3..cb63744 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -212,9 +212,21 @@ public class TokenService {
                     return Flows.forms(realm, request).setError(Messages.INVALID_USER).setFormData(formData)
                             .forwardToLogin();
                 }
+
                 if (!user.isEnabled()) {
                     return oauth.forwardToSecurityFailure("Your account is not enabled.");
                 }
+
+                if ("ENABLED".equals(user.getAttribute("KEYCLOAK_TOTP")) && Validation.isEmpty(formData.getFirst("totp"))) {
+                    return Flows.forms(realm, request).setFormData(formData).forwardToLoginTotp();
+                } else {
+                    for (RequiredCredentialModel c : realm.getRequiredCredentials()) {
+                        if (c.getType().equals(CredentialRepresentation.TOTP)) {
+                            return Flows.forms(realm, request).forwardToTotp();
+                        }
+                    }
+                }
+
                 boolean authenticated = authManager.authenticateForm(realm, user, formData);
                 if (!authenticated) {
                     logger.error("Authentication failed");
@@ -308,13 +320,6 @@ public class TokenService {
                     realm.updateCredential(user, credentials);
                 }
 
-                if (requiredCredentialTypes.contains(CredentialRepresentation.TOTP)) {
-                    UserCredentialModel credentials = new UserCredentialModel();
-                    credentials.setType(CredentialRepresentation.TOTP);
-                    credentials.setValue(formData.getFirst("totpSecret"));
-                    realm.updateCredential(user, credentials);
-                }
-
                 for (RoleModel role : realm.getDefaultRoles()) {
                     realm.grantRole(user, role);
                 }
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 c54304c..f8d85bc 100644
--- a/services/src/main/java/org/keycloak/services/validation/Validation.java
+++ b/services/src/main/java/org/keycloak/services/validation/Validation.java
@@ -6,7 +6,6 @@ import javax.ws.rs.core.MultivaluedMap;
 
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.messages.Messages;
-import org.picketlink.idm.credential.util.TimeBasedOTP;
 
 public class Validation {
 
@@ -33,18 +32,6 @@ public class Validation {
             }
         }
 
-        if (requiredCredentialTypes.contains(CredentialRepresentation.TOTP)) {
-            if (isEmpty(formData.getFirst("totp"))) {
-                return Messages.MISSING_TOTP;
-            }
-
-            boolean validTotp = new TimeBasedOTP().validate(formData.getFirst("totp"), formData.getFirst("totpSecret")
-                    .getBytes());
-            if (!validTotp) {
-                return Messages.INVALID_TOTP;
-            }
-        }
-
         return null;
     }