keycloak-memoizeit

hotp

8/5/2015 9:39:47 PM

Changes

Details

diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.5.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.5.0.xml
index 4f33cf7..301b76f 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.5.0.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.5.0.xml
@@ -10,5 +10,34 @@
         <delete tableName="USER_SESSION"/>
 
         <dropColumn tableName="AUTHENTICATION_EXECUTION" columnName="USER_SETUP_ALLOWED"/>
+        <addColumn tableName="CREDENTIAL">
+            <column name="COUNTER" type="INT" defaultValueNumeric="0">
+                <constraints nullable="true"/>
+            </column>
+            <column name="DIGITS" type="INT" defaultValueNumeric="6">
+                <constraints nullable="true"/>
+            </column>
+            <column name="ALGORITHM" type="VARCHAR(36)" defaultValue="HmacSHA1">
+                <constraints nullable="true"/>
+            </column>
+        </addColumn>
+        <addColumn tableName="REALM">
+            <column name="OTP_POLICY_COUNTER" type="INT" defaultValueNumeric="0">
+                <constraints nullable="true"/>
+            </column>
+            <column name="OTP_POLICY_WINDOW" type="INT" defaultValueNumeric="1">
+                <constraints nullable="true"/>
+            </column>
+            <column name="OTP_POLICY_DIGITS" type="INT" defaultValueNumeric="6">
+                <constraints nullable="true"/>
+            </column>
+            <column name="OTP_POLICY_ALG" type="VARCHAR(36)" defaultValue="HmacSHA1">
+                <constraints nullable="true"/>
+            </column>
+            <column name="OTP_POLICY_TYPE" type="VARCHAR(36)" defaultValue="totp">
+                <constraints nullable="true"/>
+            </column>
+        </addColumn>
+
     </changeSet>
 </databaseChangeLog>
diff --git a/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java
index 4b2ad4e..cf81d81 100755
--- a/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java
@@ -9,6 +9,7 @@ public class CredentialRepresentation {
     public static final String PASSWORD = "password";
     public static final String PASSWORD_TOKEN = "password-token";
     public static final String TOTP = "totp";
+    public static final String HOTP = "hotp";
     public static final String CLIENT_CERT = "cert";
     public static final String KERBEROS = "kerberos";
 
@@ -22,6 +23,10 @@ public class CredentialRepresentation {
     protected String hashedSaltedValue;
     protected String salt;
     protected Integer hashIterations;
+    protected Integer counter;
+    private String algorithm;
+    private Integer digits;
+
     // only used when updating a credential.  Might set required action
     protected boolean temporary;
 
@@ -80,4 +85,28 @@ public class CredentialRepresentation {
     public void setTemporary(boolean temporary) {
         this.temporary = temporary;
     }
+
+    public Integer getCounter() {
+        return counter;
+    }
+
+    public void setCounter(Integer counter) {
+        this.counter = counter;
+    }
+
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    public void setAlgorithm(String algorithm) {
+        this.algorithm = algorithm;
+    }
+
+    public Integer getDigits() {
+        return digits;
+    }
+
+    public void setDigits(Integer digits) {
+        this.digits = digits;
+    }
 }
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 ceb22a8..ee412a5 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -49,6 +49,12 @@ public class RealmRepresentation {
     @Deprecated
     protected Set<String> requiredCredentials;
     protected String passwordPolicy;
+    protected String otpPolicyType;
+    protected String otpPolicyAlgorithm;
+    protected Integer otpPolicyInitialCounter;
+    protected Integer otpPolicyDigits;
+    protected Integer otpPolicyLookAheadWindow;
+
     protected List<UserRepresentation> users;
     protected List<ScopeMappingRepresentation> scopeMappings;
     protected Map<String, List<ScopeMappingRepresentation>> clientScopeMappings;
@@ -653,4 +659,44 @@ public class RealmRepresentation {
     public void setRequiredActions(List<RequiredActionProviderRepresentation> requiredActions) {
         this.requiredActions = requiredActions;
     }
+
+    public String getOtpPolicyType() {
+        return otpPolicyType;
+    }
+
+    public void setOtpPolicyType(String otpPolicyType) {
+        this.otpPolicyType = otpPolicyType;
+    }
+
+    public String getOtpPolicyAlgorithm() {
+        return otpPolicyAlgorithm;
+    }
+
+    public void setOtpPolicyAlgorithm(String otpPolicyAlgorithm) {
+        this.otpPolicyAlgorithm = otpPolicyAlgorithm;
+    }
+
+    public Integer getOtpPolicyInitialCounter() {
+        return otpPolicyInitialCounter;
+    }
+
+    public void setOtpPolicyInitialCounter(Integer otpPolicyInitialCounter) {
+        this.otpPolicyInitialCounter = otpPolicyInitialCounter;
+    }
+
+    public Integer getOtpPolicyDigits() {
+        return otpPolicyDigits;
+    }
+
+    public void setOtpPolicyDigits(Integer otpPolicyDigits) {
+        this.otpPolicyDigits = otpPolicyDigits;
+    }
+
+    public Integer getOtpPolicyLookAheadWindow() {
+        return otpPolicyLookAheadWindow;
+    }
+
+    public void setOtpPolicyLookAheadWindow(Integer otpPolicyLookAheadWindow) {
+        this.otpPolicyLookAheadWindow = otpPolicyLookAheadWindow;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
index ea20afc..d724fd7 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
@@ -150,15 +150,6 @@ public class UserRepresentation {
         this.credentials = credentials;
     }
 
-    public UserRepresentation credential(String type, String value) {
-        if (this.credentials == null) credentials = new ArrayList<CredentialRepresentation>();
-        CredentialRepresentation cred = new CredentialRepresentation();
-        cred.setType(type);
-        cred.setValue(value);
-        credentials.add(cred);
-        return this;
-    }
-
     public List<String> getRequiredActions() {
         return requiredActions;
     }
diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
index 601ddaf..5db42fc 100755
--- a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
+++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
@@ -313,6 +313,9 @@ public class ExportUtils {
         credRep.setHashedSaltedValue(userCred.getValue());
         if (userCred.getSalt() != null) credRep.setSalt(Base64.encodeBytes(userCred.getSalt()));
         credRep.setHashIterations(userCred.getHashIterations());
+        credRep.setCounter(userCred.getCounter());
+        credRep.setAlgorithm(userCred.getAlgorithm());
+        credRep.setDigits(userCred.getDigits());
         return credRep;
     }
 
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
index 1ce00c2..35081ce 100755
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
@@ -174,7 +174,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
 
         switch (page) {
             case TOTP:
-                attributes.put("totp", new TotpBean(realm, user, baseUri));
+                attributes.put("totp", new TotpBean(session, realm, user, baseUri));
                 break;
             case FEDERATED_IDENTITY:
                 attributes.put("federatedIdentity", new AccountFederatedIdentityBean(session, realm, user, uriInfo.getBaseUri(), stateChecker));
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/TotpBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/TotpBean.java
index 33542e0..b7d4df3 100755
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/TotpBean.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/TotpBean.java
@@ -21,6 +21,7 @@
  */
 package org.keycloak.account.freemarker.model;
 
+import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.Base32;
@@ -41,14 +42,16 @@ public class TotpBean {
     private final boolean enabled;
     private final String contextUrl;
     private final String realmName;
+    private final String keyUri;
 
-    public TotpBean(RealmModel realm, UserModel user, URI baseUri) {
+    public TotpBean(KeycloakSession session, RealmModel realm, UserModel user, URI baseUri) {
         this.realmName = realm.getName();
-        this.enabled = user.isTotp();
+        this.enabled = session.users().configuredForCredentialType(realm.getOTPPolicy().getType(), realm, user);
         this.contextUrl = baseUri.getPath();
 
         this.totpSecret = randomString(20);
         this.totpSecretEncoded = Base32.encode(totpSecret.getBytes());
+        this.keyUri = realm.getOTPPolicy().getKeyURI(realm, this.totpSecret);
     }
 
     private static String randomString(int length) {
@@ -89,7 +92,7 @@ public class TotpBean {
     }
 
     public String getTotpSecretQrCodeUrl() throws UnsupportedEncodingException {
-        String contents = URLEncoder.encode("otpauth://totp/" + realmName + "?secret=" + totpSecretEncoded, "utf-8");
+        String contents = URLEncoder.encode(keyUri, "utf-8");
         return contextUrl + "qrcode" + "?size=246x246&contents=" + contents;
     }
 
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
index fbaefd1..878f13d 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -1162,6 +1162,18 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'RealmPasswordPolicyCtrl'
         })
+        .when('/realms/:realm/authentication/otp-policy', {
+            templateUrl : resourceUrl + '/partials/otp-policy.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                serverInfo : function(ServerInfo) {
+                    return ServerInfo.delay;
+                }
+            },
+            controller : 'RealmOtpPolicyCtrl'
+        })
         .when('/realms/:realm/authentication/config/:provider/:config', {
             templateUrl : resourceUrl + '/partials/authenticator-config.html',
             resolve : {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index c3adc08..0ed6fa4 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -372,6 +372,10 @@ module.controller('RealmLoginSettingsCtrl', function($scope, Current, Realm, rea
     genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/login-settings");
 });
 
+module.controller('RealmOtpPolicyCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
+    genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/authentication/otp-policy");
+});
+
 module.controller('RealmThemeCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
     genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/theme-settings");
 
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html
new file mode 100755
index 0000000..90e76b5
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html
@@ -0,0 +1,73 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+    <h1>Authentication</h1>
+
+    <kc-tabs-authentication></kc-tabs-authentication>
+
+    <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
+        <div class="form-group">
+            <label for="type" class="col-md-2 control-label">OTP Type</label>
+            <div class="col-md-2">
+                <div>
+                    <select id="type" ng-model="realm.otpPolicyType" class="form-control">
+                        <option value="totp">Time Based</option>
+                        <option value="hotp">Counter Based</option>
+                    </select>
+                </div>
+            </div>
+            <kc-tooltip>totp is Time-Based One Time Password.  'hotp' is a counter base one time password in which the server keeps a counter to hash against.</kc-tooltip>
+        </div>
+        <div class="form-group">
+            <label for="alg" class="col-md-2 control-label">OTP Hash Algorithm</label>
+            <div class="col-md-2">
+                <div>
+                    <select id="alg" ng-model="realm.otpPolicyAlgorithm" class="form-control">
+                        <option value="HmacSHA1">SHA1</option>
+                        <option value="HmacSHA256">SHA256</option>
+                        <option value="HmacSHA512">SHA512</option>
+                    </select>
+                </div>
+            </div>
+            <kc-tooltip>What hashing algorithm should be used to generate the OTP.</kc-tooltip>
+        </div>
+
+        <div class="form-group">
+            <label class="col-md-2 control-label" for="digits">Number of Digits</label>
+            <div class="col-md-2">
+                <div>
+                    <select id="digits" ng-model="realm.otpPolicyDigits" class="form-control">
+                        <option value="6">6</option>
+                        <option value="8">8</option>
+                    </select>
+                </div>
+            </div>
+            <kc-tooltip>How many digits should the OTP have?</kc-tooltip>
+        </div>
+
+        <div class="form-group">
+            <label class="col-md-2 control-label" for="lookAhead">Look ahead window</label>
+            <div class="col-md-6">
+                <input class="form-control" type="text" id="lookAhead" name="lookAhead" data-ng-model="realm.otpPolicyLookAheadWindow" autofocus>
+            </div>
+            <kc-tooltip>How far ahead should the server look just in case the token generator and server are out of time sync or counter sync?</kc-tooltip>
+        </div>
+
+        <div class="form-group" data-ng-show="realm.otpPolicyType == 'hotp'">
+            <label class="col-md-2 control-label" for="counter">Initial Counter</label>
+            <div class="col-md-6">
+                <input class="form-control" type="text" id="counter" name="counter" data-ng-model="realm.otpPolicyInitialCounter" autofocus>
+            </div>
+            <kc-tooltip>What should the initial counter value be?</kc-tooltip>
+        </div>
+
+        <div class="form-group" data-ng-show="access.manageRealm">
+            <div class="col-md-12">
+                <button kc-save data-ng-disabled="!changed">Save</button>
+                <button kc-reset data-ng-disabled="!changed">Cancel</button>
+            </div>
+        </div>
+    </form>
+
+</div>
+
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-authentication.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-authentication.html
index c9e2cbb..214a9ad 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-authentication.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-authentication.html
@@ -2,4 +2,5 @@
     <li ng-class="{active: path[3] == 'flows'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/authentication/flows">Flows</a></li>
     <li ng-class="{active: path[3] == 'required-actions'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/authentication/required-actions">Required Actions</a></li>
     <li ng-class="{active: path[3] == 'password-policy'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/authentication/password-policy">Password Policy</a></li>
+    <li ng-class="{active: path[3] == 'otp-policy'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/authentication/otp-policy">OTP Policy</a></li>
 </ul>
\ No newline at end of file
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/TotpBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/TotpBean.java
index 7ac5864..6f89167 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/TotpBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/TotpBean.java
@@ -24,11 +24,11 @@ package org.keycloak.login.freemarker.model;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.Base32;
+import org.keycloak.models.utils.HmacOTP;
 
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URLEncoder;
-import java.util.Random;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -40,25 +40,16 @@ public class TotpBean {
     private final boolean enabled;
     private final String contextUrl;
     private final String realmName;
+    private final String keyUri;
 
     public TotpBean(RealmModel realm, UserModel user, URI baseUri) {
         this.realmName = realm.getName();
-        this.enabled = user.isTotp();
+        this.enabled = user.isOtpEnabled();
         this.contextUrl = baseUri.getPath();
         
-        this.totpSecret = randomString(20);
+        this.totpSecret = HmacOTP.generateSecret(20);
         this.totpSecretEncoded = Base32.encode(totpSecret.getBytes());
-    }
-
-    private static String randomString(int length) {
-        String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW1234567890";
-        Random r = new Random();
-        StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < length; i++) {
-            char c = chars.charAt(r.nextInt(chars.length()));
-            sb.append(c);
-        }
-        return sb.toString();
+        this.keyUri = realm.getOTPPolicy().getKeyURI(realm, this.totpSecret);
     }
 
     public boolean isEnabled() {
@@ -81,7 +72,7 @@ public class TotpBean {
     }
 
     public String getTotpSecretQrCodeUrl() throws UnsupportedEncodingException {
-        String contents = URLEncoder.encode("otpauth://totp/" + realmName + "?secret=" + totpSecretEncoded, "utf-8");
+        String contents = URLEncoder.encode(keyUri, "utf-8");
         return contextUrl + "qrcode" + "?size=246x246&contents=" + contents;
     }
 
diff --git a/model/api/src/main/java/org/keycloak/migration/MigrationModel.java b/model/api/src/main/java/org/keycloak/migration/MigrationModel.java
index f0d0ea4..792822e 100755
--- a/model/api/src/main/java/org/keycloak/migration/MigrationModel.java
+++ b/model/api/src/main/java/org/keycloak/migration/MigrationModel.java
@@ -11,7 +11,7 @@ public interface MigrationModel {
     /**
      * Must have the form of major.minor.micro as the version is parsed and numbers are compared
      */
-    public static final String LATEST_VERSION = "1.4.0";
+    public static final String LATEST_VERSION = "1.5.0";
 
     String getStoredVersion();
     void setStoredVersion(String version);
diff --git a/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java b/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java
index 05289b2..09239af 100755
--- a/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java
+++ b/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java
@@ -3,6 +3,7 @@ package org.keycloak.migration;
 import org.jboss.logging.Logger;
 import org.keycloak.migration.migrators.MigrateTo1_3_0;
 import org.keycloak.migration.migrators.MigrateTo1_4_0;
+import org.keycloak.migration.migrators.MigrateTo1_5_0;
 import org.keycloak.migration.migrators.MigrationTo1_2_0_CR1;
 import org.keycloak.models.KeycloakSession;
 
@@ -40,6 +41,12 @@ public class MigrationModelManager {
             }
             new MigrateTo1_4_0().migrate(session);
         }
+        if (stored == null || stored.lessThan(MigrateTo1_5_0.VERSION)) {
+            if (stored != null) {
+                logger.debug("Migrating older model to 1.5.0 updates");
+            }
+            new MigrateTo1_4_0().migrate(session);
+        }
 
         model.setStoredVersion(MigrationModel.LATEST_VERSION);
     }
diff --git a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_5_0.java b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_5_0.java
new file mode 100755
index 0000000..2b35150
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_5_0.java
@@ -0,0 +1,31 @@
+package org.keycloak.migration.migrators;
+
+import org.keycloak.migration.ModelVersion;
+import org.keycloak.models.ImpersonationConstants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.OTPPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.DefaultAuthenticationFlows;
+import org.keycloak.models.utils.DefaultRequiredActions;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class MigrateTo1_5_0 {
+    public static final ModelVersion VERSION = new ModelVersion("1.5.0");
+
+    public void migrate(KeycloakSession session) {
+        List<RealmModel> realms = session.realms().getRealms();
+        for (RealmModel realm : realms) {
+           realm.setOTPPolicy(OTPPolicy.DEFAULT_POLICY);
+        }
+
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/CredentialEntity.java b/model/api/src/main/java/org/keycloak/models/entities/CredentialEntity.java
index 08f8f90..699d04a 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/CredentialEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/CredentialEntity.java
@@ -13,7 +13,10 @@ public class CredentialEntity {
     private int hashIterations;
     private Long createdDate;
     private UserEntity user;
-    
+    private int counter;
+    private String algorithm;
+    private int digits;
+
 
     public String getId() {
         return id;
@@ -78,5 +81,28 @@ public class CredentialEntity {
     public void setUser(UserEntity user) {
         this.user = user;
     }
-    
+
+    public int getCounter() {
+        return counter;
+    }
+
+    public void setCounter(int counter) {
+        this.counter = counter;
+    }
+
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    public void setAlgorithm(String algorithm) {
+        this.algorithm = algorithm;
+    }
+
+    public int getDigits() {
+        return digits;
+    }
+
+    public void setDigits(int digits) {
+        this.digits = digits;
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index c8bcecd..199d612 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -19,6 +19,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
     private boolean verifyEmail;
     private boolean resetPasswordAllowed;
     private String passwordPolicy;
+
+    protected String otpPolicyType;
+    protected String otpPolicyAlgorithm;
+    protected int otpPolicyInitialCounter;
+    protected int otpPolicyDigits;
+    protected int otpPolicyLookAheadWindow;
+
+
     private boolean editUsernameAllowed;
     //--- brute force settings
     private boolean bruteForceProtected;
@@ -509,6 +517,46 @@ public class RealmEntity extends AbstractIdentifiableEntity {
     public void setRequiredActionProviders(List<RequiredActionProviderEntity> requiredActionProviders) {
         this.requiredActionProviders = requiredActionProviders;
     }
+
+    public String getOtpPolicyType() {
+        return otpPolicyType;
+    }
+
+    public void setOtpPolicyType(String otpPolicyType) {
+        this.otpPolicyType = otpPolicyType;
+    }
+
+    public String getOtpPolicyAlgorithm() {
+        return otpPolicyAlgorithm;
+    }
+
+    public void setOtpPolicyAlgorithm(String otpPolicyAlgorithm) {
+        this.otpPolicyAlgorithm = otpPolicyAlgorithm;
+    }
+
+    public int getOtpPolicyInitialCounter() {
+        return otpPolicyInitialCounter;
+    }
+
+    public void setOtpPolicyInitialCounter(int otpPolicyInitialCounter) {
+        this.otpPolicyInitialCounter = otpPolicyInitialCounter;
+    }
+
+    public int getOtpPolicyDigits() {
+        return otpPolicyDigits;
+    }
+
+    public void setOtpPolicyDigits(int otpPolicyDigits) {
+        this.otpPolicyDigits = otpPolicyDigits;
+    }
+
+    public int getOtpPolicyLookAheadWindow() {
+        return otpPolicyLookAheadWindow;
+    }
+
+    public void setOtpPolicyLookAheadWindow(int otpPolicyLookAheadWindow) {
+        this.otpPolicyLookAheadWindow = otpPolicyLookAheadWindow;
+    }
 }
 
 
diff --git a/model/api/src/main/java/org/keycloak/models/OTPPolicy.java b/model/api/src/main/java/org/keycloak/models/OTPPolicy.java
new file mode 100755
index 0000000..4e70335
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/OTPPolicy.java
@@ -0,0 +1,92 @@
+package org.keycloak.models;
+
+import org.keycloak.models.utils.Base32;
+import org.keycloak.models.utils.HmacOTP;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class OTPPolicy {
+
+
+    protected String type;
+    protected String algorithm;
+    protected int initialCounter;
+    protected int digits;
+    protected int lookAheadWindow;
+
+    private static final Map<String, String> algToKeyUriAlg = new HashMap<>();
+
+    static {
+        algToKeyUriAlg.put(HmacOTP.HMAC_SHA1, "SHA1");
+        algToKeyUriAlg.put(HmacOTP.HMAC_SHA256, "SHA256");
+        algToKeyUriAlg.put(HmacOTP.HMAC_SHA512, "SHA512");
+    }
+
+    public OTPPolicy() {
+    }
+
+    public OTPPolicy(String type, String algorithm, int initialCounter, int digits, int lookAheadWindow) {
+        this.type = type;
+        this.algorithm = algorithm;
+        this.initialCounter = initialCounter;
+        this.digits = digits;
+        this.lookAheadWindow = lookAheadWindow;
+    }
+
+    public static OTPPolicy DEFAULT_POLICY = new OTPPolicy(UserCredentialModel.TOTP, HmacOTP.HMAC_SHA1, 0, 6, 1);
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    public void setAlgorithm(String algorithm) {
+        this.algorithm = algorithm;
+    }
+
+    public int getInitialCounter() {
+        return initialCounter;
+    }
+
+    public void setInitialCounter(int initialCounter) {
+        this.initialCounter = initialCounter;
+    }
+
+    public int getDigits() {
+        return digits;
+    }
+
+    public void setDigits(int digits) {
+        this.digits = digits;
+    }
+
+    public int getLookAheadWindow() {
+        return lookAheadWindow;
+    }
+
+    public void setLookAheadWindow(int lookAheadWindow) {
+        this.lookAheadWindow = lookAheadWindow;
+    }
+
+    public String getKeyURI(RealmModel realm, String secret) {
+
+        String uri = "otpauth://" + type + "/" + realm.getName() + "?secret=" + Base32.encode(secret.getBytes()) + "&digits=" + digits + "&algorithm=" + algToKeyUriAlg.get(algorithm);
+        if (type.equals(UserCredentialModel.HOTP)) {
+            uri += "&counter=" + initialCounter;
+        }
+        return uri;
+
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index d5c7faf..a9b81a6 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -148,6 +148,9 @@ public interface RealmModel extends RoleContainerModel {
 
     void setPasswordPolicy(PasswordPolicy policy);
 
+    OTPPolicy getOTPPolicy();
+    void setOTPPolicy(OTPPolicy policy);
+
     RoleModel getRoleById(String id);
 
     List<String> getDefaultRoles();
diff --git a/model/api/src/main/java/org/keycloak/models/UserCredentialModel.java b/model/api/src/main/java/org/keycloak/models/UserCredentialModel.java
index 97f6b34..7e2bfb4 100755
--- a/model/api/src/main/java/org/keycloak/models/UserCredentialModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserCredentialModel.java
@@ -14,6 +14,7 @@ public class UserCredentialModel {
     // Secret is same as password but it is not hashed
     public static final String SECRET = "secret";
     public static final String TOTP = "totp";
+    public static final String HOTP = "hotp";
     public static final String CLIENT_CERT = "cert";
     public static final String KERBEROS = "kerberos";
 
@@ -44,6 +45,12 @@ public class UserCredentialModel {
         return model;
     }
 
+    public static UserCredentialModel otp(String type, String key) {
+        if (type.equals(HOTP)) return hotp(key);
+        if (type.equals(TOTP)) return totp(key);
+        throw new RuntimeException("Unknown OTP type");
+    }
+
     public static UserCredentialModel totp(String key) {
         UserCredentialModel model = new UserCredentialModel();
         model.setType(TOTP);
@@ -51,6 +58,13 @@ public class UserCredentialModel {
         return model;
     }
 
+    public static UserCredentialModel hotp(String key) {
+        UserCredentialModel model = new UserCredentialModel();
+        model.setType(HOTP);
+        model.setValue(key);
+        return model;
+    }
+
     public static UserCredentialModel kerberos(String token) {
         UserCredentialModel model = new UserCredentialModel();
         model.setType(KERBEROS);
@@ -65,6 +79,10 @@ public class UserCredentialModel {
         return model;
     }
 
+    public static boolean isOtp(String type) {
+        return TOTP.equals(type) || HOTP.equals(type);
+    }
+
 
     public String getType() {
         return type;
diff --git a/model/api/src/main/java/org/keycloak/models/UserCredentialValueModel.java b/model/api/src/main/java/org/keycloak/models/UserCredentialValueModel.java
index 988f6a6..42fc4ca 100755
--- a/model/api/src/main/java/org/keycloak/models/UserCredentialValueModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserCredentialValueModel.java
@@ -16,6 +16,12 @@ public class UserCredentialValueModel implements Serializable {
     private int hashIterations;
     private Long createdDate;
 
+    // otp stuff
+    private int counter;
+    private String algorithm;
+    private int digits;
+
+
     public String getType() {
         return type;
     }
@@ -63,5 +69,28 @@ public class UserCredentialValueModel implements Serializable {
     public void setCreatedDate(Long createdDate) {
         this.createdDate = createdDate;
     }
-    
+
+    public int getCounter() {
+        return counter;
+    }
+
+    public void setCounter(int counter) {
+        this.counter = counter;
+    }
+
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    public void setAlgorithm(String algorithm) {
+        this.algorithm = algorithm;
+    }
+
+    public int getDigits() {
+        return digits;
+    }
+
+    public void setDigits(int digits) {
+        this.digits = digits;
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
index ee18d79..c17c016 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -411,9 +411,23 @@ public class UserFederationManager implements UserProvider {
             Set<String> supportedCredentialTypes = link.getSupportedCredentialTypes(user);
             if (supportedCredentialTypes.contains(type)) return true;
         }
+        if (UserCredentialModel.isOtp(type)) {
+            if (!user.isOtpEnabled()) return false;
+        }
+
         List<UserCredentialValueModel> creds = user.getCredentialsDirectly();
         for (UserCredentialValueModel cred : creds) {
-            if (cred.getType().equals(type)) return true;
+            if (cred.getType().equals(type)) {
+                if (UserCredentialModel.isOtp(type)) {
+                    OTPPolicy otpPolicy = realm.getOTPPolicy();
+                    if (!cred.getAlgorithm().equals(otpPolicy.getAlgorithm())
+                        || cred.getDigits() != otpPolicy.getDigits()) {
+                        return false;
+                    }
+
+                }
+                return true;
+            }
         }
         return false;
     }
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 94c2ffc..3282e61 100755
--- a/model/api/src/main/java/org/keycloak/models/UserModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserModel.java
@@ -30,7 +30,7 @@ public interface UserModel {
 
     boolean isEnabled();
 
-    boolean isTotp();
+    boolean isOtpEnabled();
 
     void setEnabled(boolean enabled);
 
@@ -86,7 +86,7 @@ public interface UserModel {
 
     void setEmailVerified(boolean verified);
 
-    void setTotp(boolean totp);
+    void setOtpEnabled(boolean totp);
 
     void updateCredential(UserCredentialModel cred);
 
diff --git a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
index cefd304..b2eaf72 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
@@ -2,6 +2,7 @@ package org.keycloak.models.utils;
 
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.models.OTPPolicy;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserCredentialModel;
@@ -84,11 +85,43 @@ public class CredentialValidation {
         }
     }
 
+    public static boolean validHOTP(RealmModel realm, UserModel user, String otp) {
+        UserCredentialValueModel passwordCred = null;
+        OTPPolicy policy = realm.getOTPPolicy();
+        HmacOTP validator = new HmacOTP(policy.getDigits(), policy.getAlgorithm(), policy.getLookAheadWindow());
+        for (UserCredentialValueModel cred : user.getCredentialsDirectly()) {
+            if (cred.getType().equals(UserCredentialModel.HOTP)) {
+                int counter = validator.validateHOTP(otp, cred.getValue(), cred.getCounter());
+                if (counter < 0) return false;
+                cred.setCounter(counter);
+                user.updateCredentialDirectly(cred);
+                return true;
+            }
+        }
+        return false;
+
+    }
+
+    public static boolean validOTP(RealmModel realm, String token, String secret) {
+        OTPPolicy policy = realm.getOTPPolicy();
+        if (policy.getType().equals(UserCredentialModel.TOTP)) {
+            TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), 30, policy.getLookAheadWindow());
+            return validator.validateTOTP(token, secret.getBytes());
+        } else {
+            HmacOTP validator = new HmacOTP(policy.getDigits(), policy.getAlgorithm(), policy.getLookAheadWindow());
+            int c = validator.validateHOTP(token, secret, policy.getInitialCounter());
+            return c > -1;
+        }
+
+    }
+
     public static boolean validTOTP(RealmModel realm, UserModel user, String otp) {
         UserCredentialValueModel passwordCred = null;
+        OTPPolicy policy = realm.getOTPPolicy();
+        TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), 30, policy.getLookAheadWindow());
         for (UserCredentialValueModel cred : user.getCredentialsDirectly()) {
             if (cred.getType().equals(UserCredentialModel.TOTP)) {
-                if (new TimeBasedOTP().validate(otp, cred.getValue().getBytes())) {
+                if (validator.validateTOTP(otp, cred.getValue().getBytes())) {
                     return true;
                 }
             }
@@ -149,6 +182,10 @@ public class CredentialValidation {
             if (!validTOTP(realm, user, credential.getValue())) {
                 return false;
             }
+        } else if (credential.getType().equals(UserCredentialModel.HOTP)) {
+            if (!validHOTP(realm, user, credential.getValue())) {
+                return false;
+            }
         } else if (credential.getType().equals(UserCredentialModel.SECRET)) {
             if (!validSecret(realm, user, credential.getValue())) {
                 return false;
diff --git a/model/api/src/main/java/org/keycloak/models/utils/HmacOTP.java b/model/api/src/main/java/org/keycloak/models/utils/HmacOTP.java
new file mode 100755
index 0000000..7770755
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/HmacOTP.java
@@ -0,0 +1,164 @@
+package org.keycloak.models.utils;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.math.BigInteger;
+import java.util.Random;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class HmacOTP {
+    public static final String HMAC_SHA1 = "HmacSHA1";
+    public static final String HMAC_SHA256 = "HmacSHA256";
+    public static final String HMAC_SHA512 = "HmacSHA512";
+    public static final String DEFAULT_ALGORITHM = HMAC_SHA1;
+    public static final int DEFAULT_NUMBER_DIGITS = 6;
+    // 0 1 2 3 4 5 6 7 8
+    private static final int[] DIGITS_POWER = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
+    protected final String algorithm;
+    protected final int numberDigits;
+    protected final int lookAheadWindow;
+
+    public HmacOTP(int numberDigits, String algorithm, int delayWindow) {
+        this.numberDigits = numberDigits;
+        this.algorithm = algorithm;
+        this.lookAheadWindow = delayWindow;
+    }
+
+    public static String generateSecret(int length) {
+        String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW1234567890";
+        Random r = new Random();
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < length; i++) {
+            char c = chars.charAt(r.nextInt(chars.length()));
+            sb.append(c);
+        }
+        return sb.toString();
+    }
+
+    public String generateHOTP(String key, int counter) {
+        String steps = Integer.toHexString(counter).toUpperCase();
+
+        // Just get a 16 digit string
+        while (steps.length() < 16)
+            steps = "0" + steps;
+
+        return generateOTP(key, steps, numberDigits, algorithm);
+
+    }
+
+    /**
+     *
+     * @param token
+     * @param key
+     * @param counter
+     * @return -1 if not a match.  A positive number means successful validation.  This positive number is also the new value of the counter
+     */
+    public int validateHOTP(String token, String key, int counter) {
+
+        int newCounter = counter;
+        for (newCounter = counter; newCounter < counter + lookAheadWindow; newCounter++) {
+            String candidate = generateHOTP(key, counter);
+            if (candidate.equals(token)) {
+                return newCounter + 1;
+            }
+
+        }
+        return -1;
+    }
+
+    /**
+     * This method generates an OTP value for the given set of parameters.
+     *
+     * @param key          the shared secret, HEX encoded
+     * @param counter         a value that reflects a time
+     * @param returnDigits number of digits to return
+     * @param crypto       the crypto function to use
+     * @return A numeric String in base 10 that includes return digits
+     * @throws java.security.GeneralSecurityException
+     *
+     */
+    public String generateOTP(String key, String counter, int returnDigits, String crypto) {
+        String result = null;
+        byte[] hash;
+
+        // Using the counter
+        // First 8 bytes are for the movingFactor
+        // Complaint with base RFC 4226 (HOTP)
+        while (counter.length() < 16)
+            counter = "0" + counter;
+
+        // Get the HEX in a Byte[]
+        byte[] msg = hexStr2Bytes(counter);
+
+        // Adding one byte to get the right conversion
+        // byte[] k = hexStr2Bytes(key);
+        byte[] k = key.getBytes();
+
+        hash = hmac_sha1(crypto, k, msg);
+
+        // put selected bytes into result int
+        int offset = hash[hash.length - 1] & 0xf;
+
+        int binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16) | ((hash[offset + 2] & 0xff) << 8)
+                | (hash[offset + 3] & 0xff);
+
+        int otp = binary % DIGITS_POWER[returnDigits];
+
+        result = Integer.toString(otp);
+
+        while (result.length() < returnDigits) {
+            result = "0" + result;
+        }
+        return result;
+    }
+
+    /**
+     * This method uses the JCE to provide the crypto algorithm. HMAC computes a Hashed Message Authentication Code with the
+     * crypto hash algorithm as a parameter.
+     *
+     * @param crypto   the crypto algorithm (HmacSHA1, HmacSHA256, HmacSHA512)
+     * @param keyBytes the bytes to use for the HMAC key
+     * @param text     the message or text to be authenticated.
+     * @throws java.security.NoSuchAlgorithmException
+     *
+     * @throws java.security.InvalidKeyException
+     *
+     */
+    private byte[] hmac_sha1(String crypto, byte[] keyBytes, byte[] text) {
+        byte[] value;
+
+        try {
+            Mac hmac = Mac.getInstance(crypto);
+            SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
+
+            hmac.init(macKey);
+
+            value = hmac.doFinal(text);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        return value;
+    }
+
+    /**
+     * This method converts HEX string to Byte[]
+     *
+     * @param hex the HEX string
+     * @return A byte array
+     */
+    private byte[] hexStr2Bytes(String hex) {
+        // Adding one byte to get the right conversion
+        // values starting with "0" can be converted
+        byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();
+
+        // Copy all the REAL bytes, not the "first"
+        byte[] ret = new byte[bArray.length - 1];
+        for (int i = 0; i < ret.length; i++)
+            ret[i] = bArray[i + 1];
+        return ret;
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 3fcde28..5761256 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -9,6 +9,7 @@ import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.IdentityProviderMapperModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.ModelException;
+import org.keycloak.models.OTPPolicy;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredActionProviderModel;
@@ -64,7 +65,7 @@ public class ModelToRepresentation {
         rep.setEmail(user.getEmail());
         rep.setEnabled(user.isEnabled());
         rep.setEmailVerified(user.isEmailVerified());
-        rep.setTotp(user.isTotp());
+        rep.setTotp(user.isOtpEnabled());
         rep.setFederationLink(user.getFederationLink());
 
         List<String> reqActions = new ArrayList<String>();
@@ -152,6 +153,12 @@ public class ModelToRepresentation {
         if (realm.getPasswordPolicy() != null) {
             rep.setPasswordPolicy(realm.getPasswordPolicy().toString());
         }
+        OTPPolicy otpPolicy = realm.getOTPPolicy();
+        rep.setOtpPolicyAlgorithm(otpPolicy.getAlgorithm());
+        rep.setOtpPolicyDigits(otpPolicy.getDigits());
+        rep.setOtpPolicyInitialCounter(otpPolicy.getInitialCounter());
+        rep.setOtpPolicyType(otpPolicy.getType());
+        rep.setOtpPolicyLookAheadWindow(otpPolicy.getLookAheadWindow());
 
         List<String> defaultRoles = realm.getDefaultRoles();
         if (!defaultRoles.isEmpty()) {
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 92060b0..33d640b 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -15,6 +15,7 @@ import org.keycloak.models.IdentityProviderMapperModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelException;
+import org.keycloak.models.OTPPolicy;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
@@ -63,7 +64,16 @@ import java.util.TreeSet;
 public class RepresentationToModel {
 
     private static Logger logger = Logger.getLogger(RepresentationToModel.class);
+    public static OTPPolicy toPolicy(RealmRepresentation rep) {
+        OTPPolicy policy = new OTPPolicy();
+        policy.setType(rep.getOtpPolicyType());
+        policy.setLookAheadWindow(rep.getOtpPolicyLookAheadWindow());
+        policy.setInitialCounter(rep.getOtpPolicyInitialCounter());
+        policy.setAlgorithm(rep.getOtpPolicyAlgorithm());
+        policy.setDigits(rep.getOtpPolicyDigits());
+        return policy;
 
+    }
     public static void importRealm(KeycloakSession session, RealmRepresentation rep, RealmModel newRealm) {
         convertDeprecatedSocialProviders(rep);
         convertDeprecatedApplications(session, rep);
@@ -144,6 +154,8 @@ public class RepresentationToModel {
         }
 
         if (rep.getPasswordPolicy() != null) newRealm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));
+        if (rep.getOtpPolicyType() != null) newRealm.setOTPPolicy(toPolicy(rep));
+        else newRealm.setOTPPolicy(OTPPolicy.DEFAULT_POLICY);
 
         importIdentityProviders(rep, newRealm);
         importIdentityProviderMappers(rep, newRealm);
@@ -497,6 +509,7 @@ public class RepresentationToModel {
         
 
         if (rep.getPasswordPolicy() != null) realm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));
+        if (rep.getOtpPolicyType() != null) realm.setOTPPolicy(toPolicy(rep));
 
         if (rep.getDefaultRoles() != null) {
             realm.updateDefaultRoles(rep.getDefaultRoles().toArray(new String[rep.getDefaultRoles().size()]));
@@ -847,7 +860,7 @@ public class RepresentationToModel {
         user.setFirstName(userRep.getFirstName());
         user.setLastName(userRep.getLastName());
         user.setFederationLink(userRep.getFederationLink());
-        user.setTotp(userRep.isTotp());
+        user.setOtpEnabled(userRep.isTotp());
         if (userRep.getAttributes() != null) {
             for (Map.Entry<String, Object> entry : userRep.getAttributes().entrySet()) {
                 Object value = entry.getValue();
@@ -922,13 +935,22 @@ public class RepresentationToModel {
             UserCredentialValueModel hashedCred = new UserCredentialValueModel();
             hashedCred.setType(cred.getType());
             hashedCred.setDevice(cred.getDevice());
-            hashedCred.setHashIterations(cred.getHashIterations());
+            if (cred.getHashIterations() != null) hashedCred.setHashIterations(cred.getHashIterations());
             try {
                 if (cred.getSalt() != null) hashedCred.setSalt(Base64.decode(cred.getSalt()));
             } catch (IOException ioe) {
                 throw new RuntimeException(ioe);
             }
             hashedCred.setValue(cred.getHashedSaltedValue());
+            if (cred.getCounter() != null) hashedCred.setCounter(cred.getCounter());
+            if (cred.getDigits() != null) hashedCred.setDigits(cred.getDigits());
+            if (cred.getAlgorithm() != null) hashedCred.setAlgorithm(cred.getAlgorithm());
+            if (cred.getDigits() == null && UserCredentialModel.isOtp(cred.getType())) {
+                hashedCred.setDigits(6);
+            }
+            if (cred.getAlgorithm() == null && UserCredentialModel.isOtp(cred.getType())) {
+                hashedCred.setAlgorithm(HmacOTP.HMAC_SHA1);
+            }
             user.updateCredentialDirectly(hashedCred);
         }
     }
diff --git a/model/api/src/main/java/org/keycloak/models/utils/TimeBasedOTP.java b/model/api/src/main/java/org/keycloak/models/utils/TimeBasedOTP.java
index 5f21d38..a9bf73c 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/TimeBasedOTP.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/TimeBasedOTP.java
@@ -1,8 +1,5 @@
 package org.keycloak.models.utils;
 
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-import java.math.BigInteger;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
 import java.util.TimeZone;
@@ -13,24 +10,12 @@ import java.util.TimeZone;
  * @author anil saldhana
  * @since Sep 20, 2010
  */
-public class TimeBasedOTP {
+public class TimeBasedOTP extends HmacOTP {
 
-    public static final String HMAC_SHA1 = "HmacSHA1";
-    public static final String HMAC_SHA256 = "HmacSHA256";
-    public static final String HMAC_SHA512 = "HmacSHA512";
-
-    public static final String DEFAULT_ALGORITHM = HMAC_SHA1;
-    public static final int DEFAULT_NUMBER_DIGITS = 6;
     public static final int DEFAULT_INTERVAL_SECONDS = 30;
     public static final int DEFAULT_DELAY_WINDOW = 1;
 
-    // 0 1 2 3 4 5 6 7 8
-    private static final int[] DIGITS_POWER = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
-
     private Clock clock;
-    private final String algorithm;
-    private final int numberDigits;
-    private final int delayWindow;
 
     public TimeBasedOTP() {
         this(DEFAULT_ALGORITHM, DEFAULT_NUMBER_DIGITS, DEFAULT_INTERVAL_SECONDS, DEFAULT_DELAY_WINDOW);
@@ -40,13 +25,11 @@ public class TimeBasedOTP {
      * @param algorithm the encryption algorithm
      * @param numberDigits the number of digits for tokens
      * @param timeIntervalInSeconds the number of seconds a token is valid
-     * @param delayWindow the number of previous intervals that should be used to validate tokens.
+     * @param lookAheadWindow the number of previous intervals that should be used to validate tokens.
      */
-    public TimeBasedOTP(String algorithm, int numberDigits, int timeIntervalInSeconds, int delayWindow) {
-        this.algorithm = algorithm;
-        this.numberDigits = numberDigits;
+    public TimeBasedOTP(String algorithm, int numberDigits, int timeIntervalInSeconds, int lookAheadWindow) {
+        super(numberDigits, algorithm, lookAheadWindow);
         this.clock = new Clock(timeIntervalInSeconds);
-        this.delayWindow = delayWindow;
     }
 
     /**
@@ -54,7 +37,7 @@ public class TimeBasedOTP {
      *
      * @param secretKey the secret key to derive the token from.
      */
-    public String generate(String secretKey) {
+    public String generateTOTP(String secretKey) {
         long T = this.clock.getCurrentInterval();
 
         String steps = Long.toHexString(T).toUpperCase();
@@ -63,53 +46,7 @@ public class TimeBasedOTP {
         while (steps.length() < 16)
             steps = "0" + steps;
 
-        return generateTOTP(secretKey, steps, this.numberDigits, this.algorithm);
-    }
-
-    /**
-     * This method generates an TOTP value for the given set of parameters.
-     *
-     * @param key          the shared secret, HEX encoded
-     * @param time         a value that reflects a time
-     * @param returnDigits number of digits to return
-     * @param crypto       the crypto function to use
-     * @return A numeric String in base 10 that includes {@link truncationDigits} digits
-     * @throws java.security.GeneralSecurityException
-     *
-     */
-    public String generateTOTP(String key, String time, int returnDigits, String crypto) {
-        String result = null;
-        byte[] hash;
-
-        // Using the counter
-        // First 8 bytes are for the movingFactor
-        // Complaint with base RFC 4226 (HOTP)
-        while (time.length() < 16)
-            time = "0" + time;
-
-        // Get the HEX in a Byte[]
-        byte[] msg = hexStr2Bytes(time);
-
-        // Adding one byte to get the right conversion
-        // byte[] k = hexStr2Bytes(key);
-        byte[] k = key.getBytes();
-
-        hash = hmac_sha1(crypto, k, msg);
-
-        // put selected bytes into result int
-        int offset = hash[hash.length - 1] & 0xf;
-
-        int binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16) | ((hash[offset + 2] & 0xff) << 8)
-                | (hash[offset + 3] & 0xff);
-
-        int otp = binary % DIGITS_POWER[returnDigits];
-
-        result = Integer.toString(otp);
-
-        while (result.length() < returnDigits) {
-            result = "0" + result;
-        }
-        return result;
+        return generateOTP(secretKey, steps, this.numberDigits, this.algorithm);
     }
 
     /**
@@ -119,17 +56,17 @@ public class TimeBasedOTP {
      * @param secret Shared secret
      * @return
      */
-    public boolean validate(String token, byte[] secret) {
+    public boolean validateTOTP(String token, byte[] secret) {
         long currentInterval = this.clock.getCurrentInterval();
 
-        for (int i = this.delayWindow; i >= 0; --i) {
+        for (int i = this.lookAheadWindow; i >= 0; --i) {
             String steps = Long.toHexString(currentInterval - i).toUpperCase();
 
             // Just get a 16 digit string
             while (steps.length() < 16)
                 steps = "0" + steps;
 
-            String candidate = generateTOTP(new String(secret), steps, this.numberDigits, this.algorithm);
+            String candidate = generateOTP(new String(secret), steps, this.numberDigits, this.algorithm);
 
             if (candidate.equals(token)) {
                 return true;
@@ -143,53 +80,6 @@ public class TimeBasedOTP {
         this.clock.setCalendar(calendar);
     }
 
-    /**
-     * This method uses the JCE to provide the crypto algorithm. HMAC computes a Hashed Message Authentication Code with the
-     * crypto hash algorithm as a parameter.
-     *
-     * @param crypto   the crypto algorithm (HmacSHA1, HmacSHA256, HmacSHA512)
-     * @param keyBytes the bytes to use for the HMAC key
-     * @param text     the message or text to be authenticated.
-     * @throws java.security.NoSuchAlgorithmException
-     *
-     * @throws java.security.InvalidKeyException
-     *
-     */
-    private byte[] hmac_sha1(String crypto, byte[] keyBytes, byte[] text) {
-        byte[] value;
-
-        try {
-            Mac hmac = Mac.getInstance(crypto);
-            SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
-
-            hmac.init(macKey);
-
-            value = hmac.doFinal(text);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-
-        return value;
-    }
-
-    /**
-     * This method converts HEX string to Byte[]
-     *
-     * @param hex the HEX string
-     * @return A byte array
-     */
-    private byte[] hexStr2Bytes(String hex) {
-        // Adding one byte to get the right conversion
-        // values starting with "0" can be converted
-        byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();
-
-        // Copy all the REAL bytes, not the "first"
-        byte[] ret = new byte[bArray.length - 1];
-        for (int i = 0; i < ret.length; i++)
-            ret[i] = bArray[i + 1];
-        return ret;
-    }
-
     private class Clock {
 
         private final int interval;
diff --git a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
index 3c1edb3..4cd162b 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
@@ -43,8 +43,8 @@ public class UserModelDelegate implements UserModel {
     }
 
     @Override
-    public boolean isTotp() {
-        return delegate.isTotp();
+    public boolean isOtpEnabled() {
+        return delegate.isOtpEnabled();
     }
 
     @Override
@@ -148,8 +148,8 @@ public class UserModelDelegate implements UserModel {
     }
 
     @Override
-    public void setTotp(boolean totp) {
-        delegate.setTotp(totp);
+    public void setOtpEnabled(boolean totp) {
+        delegate.setOtpEnabled(totp);
     }
 
     @Override
diff --git a/model/api/src/test/java/org/keycloak/models/HmacTest.java b/model/api/src/test/java/org/keycloak/models/HmacTest.java
new file mode 100755
index 0000000..0cda696
--- /dev/null
+++ b/model/api/src/test/java/org/keycloak/models/HmacTest.java
@@ -0,0 +1,32 @@
+package org.keycloak.models;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.models.utils.Base32;
+import org.keycloak.models.utils.HmacOTP;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.BufferedReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Scanner;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class HmacTest {
+
+    @Test
+    public void testHmac() throws Exception {
+        HmacOTP hmacOTP = new HmacOTP(6, HmacOTP.HMAC_SHA1, 10);
+        String secret = "JNSVMMTEKZCUGSKJIVGHMNSQOZBDA5JT";
+        String decoded = new String(Base32.decode(secret));
+        System.out.println(hmacOTP.generateHOTP(decoded, 0));
+        System.out.println(hmacOTP.validateHOTP("550233", decoded, 0));
+        Assert.assertEquals(1, hmacOTP.validateHOTP("550233", decoded, 0));
+    }
+}
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
index c2d85d3..17692b0 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
@@ -26,6 +26,7 @@ import org.keycloak.models.IdentityProviderMapperModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.OTPPolicy;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredActionProviderModel;
@@ -81,6 +82,7 @@ public class RealmAdapter implements RealmModel {
     protected volatile transient Key codeSecretKey;
 
     private volatile transient PasswordPolicy passwordPolicy;
+    private volatile transient OTPPolicy otpPolicy;
     private volatile transient KeycloakSession session;
 
     private final Map<String, ClientModel> allApps = new HashMap<String, ClientModel>();
@@ -288,6 +290,29 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public OTPPolicy getOTPPolicy() {
+        if (otpPolicy == null) {
+            otpPolicy = new OTPPolicy();
+            otpPolicy.setDigits(realm.getOtpPolicyDigits());
+            otpPolicy.setAlgorithm(realm.getOtpPolicyAlgorithm());
+            otpPolicy.setInitialCounter(realm.getOtpPolicyInitialCounter());
+            otpPolicy.setLookAheadWindow(realm.getOtpPolicyLookAheadWindow());
+            otpPolicy.setType(realm.getOtpPolicyType());
+        }
+        return otpPolicy;
+    }
+
+    @Override
+    public void setOTPPolicy(OTPPolicy policy) {
+        realm.setOtpPolicyAlgorithm(policy.getAlgorithm());
+        realm.setOtpPolicyDigits(policy.getDigits());
+        realm.setOtpPolicyInitialCounter(policy.getInitialCounter());
+        realm.setOtpPolicyLookAheadWindow(policy.getLookAheadWindow());
+        realm.setOtpPolicyType(policy.getType());
+
+    }
+
+    @Override
     public int getNotBefore() {
         return realm.getNotBefore();
     }
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
index 98e4254..f145141 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
@@ -22,6 +22,7 @@ import org.keycloak.models.ClientModel;
 import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
 
 import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.OTPPolicy;
 import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
@@ -256,12 +257,12 @@ public class UserAdapter implements UserModel, Comparable {
     }
 
     @Override
-    public boolean isTotp() {
+    public boolean isOtpEnabled() {
         return user.isTotp();
     }
 
     @Override
-    public void setTotp(boolean totp) {
+    public void setOtpEnabled(boolean totp) {
         user.setTotp(totp);
     }
 
@@ -270,7 +271,10 @@ public class UserAdapter implements UserModel, Comparable {
 
         if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
             updatePasswordCredential(cred);
-        } else {
+        } else if (UserCredentialModel.isOtp(cred.getType())){
+            updateOtpCredential(cred);
+
+        }else {
             CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
 
             if (credentialEntity == null) {
@@ -283,6 +287,27 @@ public class UserAdapter implements UserModel, Comparable {
         }
     }
 
+    private void updateOtpCredential(UserCredentialModel cred) {
+        CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
+
+        if (credentialEntity == null) {
+            credentialEntity = setCredentials(user, cred);
+            credentialEntity.setValue(cred.getValue());
+            OTPPolicy otpPolicy = realm.getOTPPolicy();
+            credentialEntity.setAlgorithm(otpPolicy.getAlgorithm());
+            credentialEntity.setDigits(otpPolicy.getDigits());
+            credentialEntity.setCounter(otpPolicy.getInitialCounter());
+            user.getCredentials().add(credentialEntity);
+        } else {
+            credentialEntity.setValue(cred.getValue());
+            OTPPolicy policy = realm.getOTPPolicy();
+            credentialEntity.setDigits(policy.getDigits());
+            credentialEntity.setCounter(policy.getInitialCounter());
+            credentialEntity.setAlgorithm(policy.getAlgorithm());
+        }
+    }
+
+
     private void updatePasswordCredential(UserCredentialModel cred) {
         CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
 
@@ -390,6 +415,9 @@ public class UserAdapter implements UserModel, Comparable {
             credModel.setValue(credEntity.getValue());
             credModel.setSalt(credEntity.getSalt());
             credModel.setHashIterations(credEntity.getHashIterations());
+            credModel.setCounter(credEntity.getCounter());
+            credModel.setAlgorithm(credEntity.getAlgorithm());
+            credModel.setDigits(credEntity.getDigits());
 
             result.add(credModel);
         }
@@ -414,6 +442,9 @@ public class UserAdapter implements UserModel, Comparable {
         credentialEntity.setSalt(credModel.getSalt());
         credentialEntity.setDevice(credModel.getDevice());
         credentialEntity.setHashIterations(credModel.getHashIterations());
+        credentialEntity.setCounter(credModel.getCounter());
+        credentialEntity.setAlgorithm(credModel.getAlgorithm());
+        credentialEntity.setDigits(credModel.getDigits());
     }
 
     @Override
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index f4b1e35..e93d415 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -7,6 +7,7 @@ import org.keycloak.models.AuthenticatorConfigModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.IdentityProviderMapperModel;
 import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.OTPPolicy;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RealmProvider;
@@ -63,6 +64,7 @@ public class CachedRealm implements Serializable {
     private int accessCodeLifespanLogin;
     private int notBefore;
     private PasswordPolicy passwordPolicy;
+    private OTPPolicy otpPolicy;
 
     private String publicKeyPem;
     private String privateKeyPem;
@@ -137,6 +139,7 @@ public class CachedRealm implements Serializable {
         accessCodeLifespanLogin = model.getAccessCodeLifespanLogin();
         notBefore = model.getNotBefore();
         passwordPolicy = model.getPasswordPolicy();
+        otpPolicy = model.getOTPPolicy();
 
         publicKeyPem = model.getPublicKeyPem();
         privateKeyPem = model.getPrivateKeyPem();
@@ -456,4 +459,8 @@ public class CachedRealm implements Serializable {
     public Map<String, RequiredActionProviderModel> getRequiredActionProvidersByAlias() {
         return requiredActionProvidersByAlias;
     }
+
+    public OTPPolicy getOtpPolicy() {
+        return otpPolicy;
+    }
 }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
index 853677b..9757c63 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
@@ -7,11 +7,9 @@ import org.keycloak.models.UserModel;
 import org.keycloak.util.MultivaluedHashMap;
 
 import java.io.Serializable;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 /**
@@ -48,7 +46,7 @@ public class CachedUser implements Serializable {
         this.emailVerified = user.isEmailVerified();
         this.credentials.addAll(user.getCredentialsDirectly());
         this.enabled = user.isEnabled();
-        this.totp = user.isTotp();
+        this.totp = user.isOtpEnabled();
         this.federationLink = user.getFederationLink();
         this.serviceAccountClientLink = user.getServiceAccountClientLink();
         this.requiredActions.addAll(user.getRequiredActions());
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index 84938bf..cb38a53 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
@@ -8,6 +8,7 @@ import org.keycloak.models.AuthenticatorConfigModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.IdentityProviderMapperModel;
 import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.OTPPolicy;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredActionProviderModel;
@@ -453,6 +454,19 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public OTPPolicy getOTPPolicy() {
+        if (updated != null) return updated.getOTPPolicy();
+        return cached.getOtpPolicy();
+    }
+
+    @Override
+    public void setOTPPolicy(OTPPolicy policy) {
+        getDelegateForUpdate();
+        updated.setOTPPolicy(policy);
+
+    }
+
+    @Override
     public RoleModel getRoleById(String id) {
         if (updated != null) return updated.getRoleById(id);
         return cacheSession.getRoleById(id, this);
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
index f2b5e33..a4af54e 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
@@ -80,8 +80,8 @@ public class UserAdapter implements UserModel {
     }
 
     @Override
-    public boolean isTotp() {
-        if (updated != null) return updated.isTotp();
+    public boolean isOtpEnabled() {
+        if (updated != null) return updated.isOtpEnabled();
         return cached.isTotp();
     }
 
@@ -208,9 +208,9 @@ public class UserAdapter implements UserModel {
     }
 
     @Override
-    public void setTotp(boolean totp) {
+    public void setOtpEnabled(boolean totp) {
         getDelegateForUpdate();
-        updated.setTotp(totp);
+        updated.setOtpEnabled(totp);
     }
 
     @Override
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
index f247f01..fc36f47 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
@@ -44,6 +44,15 @@ public class CredentialEntity {
     @JoinColumn(name="USER_ID")
     protected UserEntity user;
 
+    @Column(name="COUNTER")
+    protected int counter;
+
+    @Column(name="ALGORITHM")
+    protected String algorithm;
+    @Column(name="DIGITS")
+    protected int digits;
+
+
     public String getId() {
         return id;
     }
@@ -107,5 +116,28 @@ public class CredentialEntity {
     public void setCreatedDate(Long createdDate) {
         this.createdDate = createdDate;
     }
-    
+
+    public int getCounter() {
+        return counter;
+    }
+
+    public void setCounter(int counter) {
+        this.counter = counter;
+    }
+
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    public void setAlgorithm(String algorithm) {
+        this.algorithm = algorithm;
+    }
+
+    public int getDigits() {
+        return digits;
+    }
+
+    public void setDigits(int digits) {
+        this.digits = digits;
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index fe9b10d..f187cec 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -55,8 +55,22 @@ public class RealmEntity {
     protected boolean resetPasswordAllowed;
     @Column(name="REMEMBER_ME")
     protected boolean rememberMe;
+
     @Column(name="PASSWORD_POLICY")
     protected String passwordPolicy;
+
+    @Column(name="OTP_POLICY_TYPE")
+    protected String otpPolicyType;
+    @Column(name="OTP_POLICY_ALG")
+    protected String otpPolicyAlgorithm;
+    @Column(name="OTP_POLICY_COUNTER")
+    protected int otpPolicyInitialCounter;
+    @Column(name="OTP_POLICY_DIGITS")
+    protected int otpPolicyDigits;
+    @Column(name="OTP_POLICY_WINDOW")
+    protected int otpPolicyLookAheadWindow;
+
+
     @Column(name="EDIT_USERNAME_ALLOWED")
     protected boolean editUsernameAllowed;
 
@@ -580,5 +594,44 @@ public class RealmEntity {
         this.authenticationFlows = authenticationFlows;
     }
 
+    public String getOtpPolicyType() {
+        return otpPolicyType;
+    }
+
+    public void setOtpPolicyType(String otpPolicyType) {
+        this.otpPolicyType = otpPolicyType;
+    }
+
+    public String getOtpPolicyAlgorithm() {
+        return otpPolicyAlgorithm;
+    }
+
+    public void setOtpPolicyAlgorithm(String otpPolicyAlgorithm) {
+        this.otpPolicyAlgorithm = otpPolicyAlgorithm;
+    }
+
+    public int getOtpPolicyInitialCounter() {
+        return otpPolicyInitialCounter;
+    }
+
+    public void setOtpPolicyInitialCounter(int otpPolicyInitialCounter) {
+        this.otpPolicyInitialCounter = otpPolicyInitialCounter;
+    }
+
+    public int getOtpPolicyDigits() {
+        return otpPolicyDigits;
+    }
+
+    public void setOtpPolicyDigits(int otpPolicyDigits) {
+        this.otpPolicyDigits = otpPolicyDigits;
+    }
+
+    public int getOtpPolicyLookAheadWindow() {
+        return otpPolicyLookAheadWindow;
+    }
+
+    public void setOtpPolicyLookAheadWindow(int otpPolicyLookAheadWindow) {
+        this.otpPolicyLookAheadWindow = otpPolicyLookAheadWindow;
+    }
 }
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index c7dcf5a..f9e5d01 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -9,6 +9,7 @@ import org.keycloak.models.IdentityProviderMapperModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.OTPPolicy;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredActionProviderModel;
@@ -65,6 +66,7 @@ public class RealmAdapter implements RealmModel {
     protected volatile transient Key codeSecretKey;
     protected KeycloakSession session;
     private PasswordPolicy passwordPolicy;
+    private OTPPolicy otpPolicy;
 
     public RealmAdapter(KeycloakSession session, EntityManager em, RealmEntity realm) {
         this.session = session;
@@ -1018,6 +1020,30 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public OTPPolicy getOTPPolicy() {
+        if (otpPolicy == null) {
+            otpPolicy = new OTPPolicy();
+            otpPolicy.setDigits(realm.getOtpPolicyDigits());
+            otpPolicy.setAlgorithm(realm.getOtpPolicyAlgorithm());
+            otpPolicy.setInitialCounter(realm.getOtpPolicyInitialCounter());
+            otpPolicy.setLookAheadWindow(realm.getOtpPolicyLookAheadWindow());
+            otpPolicy.setType(realm.getOtpPolicyType());
+        }
+        return otpPolicy;
+    }
+
+    @Override
+    public void setOTPPolicy(OTPPolicy policy) {
+        realm.setOtpPolicyAlgorithm(policy.getAlgorithm());
+        realm.setOtpPolicyDigits(policy.getDigits());
+        realm.setOtpPolicyInitialCounter(policy.getInitialCounter());
+        realm.setOtpPolicyLookAheadWindow(policy.getLookAheadWindow());
+        realm.setOtpPolicyType(policy.getType());
+        em.flush();
+    }
+
+
+    @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || !(o instanceof RealmModel)) return false;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index e603777..ae5c66c 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -1,6 +1,7 @@
 package org.keycloak.models.jpa;
 
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.OTPPolicy;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.ModelDuplicateException;
@@ -94,7 +95,7 @@ public class UserAdapter implements UserModel {
     }
 
     @Override
-    public boolean isTotp() {
+    public boolean isOtpEnabled() {
         return user.isTotp();
     }
 
@@ -282,7 +283,7 @@ public class UserAdapter implements UserModel {
     }
 
     @Override
-    public void setTotp(boolean totp) {
+    public void setOtpEnabled(boolean totp) {
         user.setTotp(totp);
     }
 
@@ -291,6 +292,9 @@ public class UserAdapter implements UserModel {
 
         if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
             updatePasswordCredential(cred);
+        } else if (UserCredentialModel.isOtp(cred.getType())){
+            updateOtpCredential(cred);
+
         } else {
             CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
 
@@ -306,6 +310,31 @@ public class UserAdapter implements UserModel {
         em.flush();
     }
 
+    private void updateOtpCredential(UserCredentialModel cred) {
+        CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
+
+        if (credentialEntity == null) {
+            credentialEntity = setCredentials(user, cred);
+
+            credentialEntity.setValue(cred.getValue());
+            OTPPolicy otpPolicy = realm.getOTPPolicy();
+            credentialEntity.setAlgorithm(otpPolicy.getAlgorithm());
+            credentialEntity.setDigits(otpPolicy.getDigits());
+            credentialEntity.setCounter(otpPolicy.getInitialCounter());
+            em.persist(credentialEntity);
+            user.getCredentials().add(credentialEntity);
+        } else {
+            OTPPolicy policy = realm.getOTPPolicy();
+            credentialEntity.setDigits(policy.getDigits());
+            credentialEntity.setCounter(policy.getInitialCounter());
+            credentialEntity.setAlgorithm(policy.getAlgorithm());
+            credentialEntity.setValue(cred.getValue());
+        }
+    }
+
+
+
+
     private void updatePasswordCredential(UserCredentialModel cred) {
         CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
 
@@ -418,6 +447,9 @@ public class UserAdapter implements UserModel {
                 credModel.setCreatedDate(credEntity.getCreatedDate());
                 credModel.setSalt(credEntity.getSalt());
                 credModel.setHashIterations(credEntity.getHashIterations());
+                credModel.setCounter(credEntity.getCounter());
+                credModel.setAlgorithm(credEntity.getAlgorithm());
+                credModel.setDigits(credEntity.getDigits());
 
                 result.add(credModel);
             }
@@ -444,6 +476,9 @@ public class UserAdapter implements UserModel {
         credentialEntity.setSalt(credModel.getSalt());
         credentialEntity.setDevice(credModel.getDevice());
         credentialEntity.setHashIterations(credModel.getHashIterations());
+        credentialEntity.setCounter(credModel.getCounter());
+        credentialEntity.setAlgorithm(credModel.getAlgorithm());
+        credentialEntity.setDigits(credModel.getDigits());
 
         em.flush();
     }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index 795ccbf..19f2601 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -13,6 +13,7 @@ import org.keycloak.models.IdentityProviderMapperModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.OTPPolicy;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RealmProvider;
@@ -66,6 +67,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     protected volatile transient X509Certificate certificate;
     protected volatile transient Key codeSecretKey;
 
+    private volatile transient OTPPolicy otpPolicy;
     private volatile transient PasswordPolicy passwordPolicy;
     private volatile transient KeycloakSession session;
 
@@ -273,6 +275,30 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     }
 
     @Override
+    public OTPPolicy getOTPPolicy() {
+        if (otpPolicy == null) {
+            otpPolicy = new OTPPolicy();
+            otpPolicy.setDigits(realm.getOtpPolicyDigits());
+            otpPolicy.setAlgorithm(realm.getOtpPolicyAlgorithm());
+            otpPolicy.setInitialCounter(realm.getOtpPolicyInitialCounter());
+            otpPolicy.setLookAheadWindow(realm.getOtpPolicyLookAheadWindow());
+            otpPolicy.setType(realm.getOtpPolicyType());
+        }
+        return otpPolicy;
+    }
+
+    @Override
+    public void setOTPPolicy(OTPPolicy policy) {
+        realm.setOtpPolicyAlgorithm(policy.getAlgorithm());
+        realm.setOtpPolicyDigits(policy.getDigits());
+        realm.setOtpPolicyInitialCounter(policy.getInitialCounter());
+        realm.setOtpPolicyLookAheadWindow(policy.getLookAheadWindow());
+        realm.setOtpPolicyType(policy.getType());
+        updateRealm();
+    }
+
+
+    @Override
     public int getNotBefore() {
         return realm.getNotBefore();
     }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index 6dae14b..be5c45d 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -7,6 +7,7 @@ import com.mongodb.QueryBuilder;
 
 import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.OTPPolicy;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.KeycloakSession;
@@ -227,12 +228,12 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
     }
 
     @Override
-    public boolean isTotp() {
+    public boolean isOtpEnabled() {
         return user.isTotp();
     }
 
     @Override
-    public void setTotp(boolean totp) {
+    public void setOtpEnabled(boolean totp) {
         user.setTotp(totp);
         updateUser();
     }
@@ -242,6 +243,8 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
 
         if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
             updatePasswordCredential(cred);
+        } else if (UserCredentialModel.isOtp(cred.getType())){
+            updateOtpCredential(cred);
         } else {
             CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
 
@@ -256,6 +259,27 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
         getMongoStore().updateEntity(user, invocationContext);
     }
 
+    private void updateOtpCredential(UserCredentialModel cred) {
+        CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
+
+        if (credentialEntity == null) {
+            credentialEntity = setCredentials(user, cred);
+            credentialEntity.setValue(cred.getValue());
+            OTPPolicy otpPolicy = realm.getOTPPolicy();
+            credentialEntity.setAlgorithm(otpPolicy.getAlgorithm());
+            credentialEntity.setDigits(otpPolicy.getDigits());
+            credentialEntity.setCounter(otpPolicy.getInitialCounter());
+            user.getCredentials().add(credentialEntity);
+        } else {
+            credentialEntity.setValue(cred.getValue());
+            OTPPolicy policy = realm.getOTPPolicy();
+            credentialEntity.setDigits(policy.getDigits());
+            credentialEntity.setCounter(policy.getInitialCounter());
+            credentialEntity.setAlgorithm(policy.getAlgorithm());
+        }
+    }
+
+
     private void updatePasswordCredential(UserCredentialModel cred) {
         CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
 
@@ -362,6 +386,9 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
             credModel.setValue(credEntity.getValue());
             credModel.setSalt(credEntity.getSalt());
             credModel.setHashIterations(credEntity.getHashIterations());
+            credModel.setCounter(credEntity.getCounter());
+            credModel.setAlgorithm(credEntity.getAlgorithm());
+            credModel.setDigits(credEntity.getDigits());
 
             result.add(credModel);
         }
@@ -384,6 +411,9 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
         credentialEntity.setSalt(credModel.getSalt());
         credentialEntity.setDevice(credModel.getDevice());
         credentialEntity.setHashIterations(credModel.getHashIterations());
+        credentialEntity.setCounter(credModel.getCounter());
+        credentialEntity.setAlgorithm(credModel.getAlgorithm());
+        credentialEntity.setDigits(credModel.getDigits());
 
 
         getMongoStore().updateEntity(user, invocationContext);
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java
index c603686..6c3b09a 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java
@@ -45,7 +45,7 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A
             context.challenge(challengeResponse);
             return;
         }
-        credentials.add(UserCredentialModel.totp(password));
+        credentials.add(UserCredentialModel.otp(context.getRealm().getOTPPolicy().getType(), password));
         boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
         if (!valid) {
             context.getEvent().user(context.getUser())
@@ -75,7 +75,7 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A
 
     @Override
     public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
-        return session.users().configuredForCredentialType(UserCredentialModel.TOTP, realm, user) && user.isTotp();
+        return session.users().configuredForCredentialType(realm.getOTPPolicy().getType(), realm, user);
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidateOTP.java b/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidateOTP.java
index 7d4da65..9c9846b 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidateOTP.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidateOTP.java
@@ -50,7 +50,7 @@ public class ValidateOTP extends AbstractDirectGrantAuthenticator {
             context.failure(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
             return;
         }
-        credentials.add(UserCredentialModel.totp(otp));
+        credentials.add(UserCredentialModel.otp(context.getRealm().getOTPPolicy().getType(), otp));
         boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
         if (!valid) {
             context.getEvent().user(context.getUser());
@@ -74,7 +74,7 @@ public class ValidateOTP extends AbstractDirectGrantAuthenticator {
     }
 
     private boolean isConfigured(KeycloakSession session, RealmModel realm, UserModel user) {
-        return session.users().configuredForCredentialType(UserCredentialModel.TOTP, realm, user) && user.isTotp();
+        return session.users().configuredForCredentialType(realm.getOTPPolicy().getType(), realm, user);
     }
 
     @Override
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 c011551..a445113 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -11,6 +11,7 @@ import org.keycloak.models.BrowserSecurityHeaders;
 import org.keycloak.models.Constants;
 import org.keycloak.models.ImpersonationConstants;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.OTPPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RealmProvider;
 import org.keycloak.models.RoleModel;
@@ -150,6 +151,7 @@ public class RealmManager {
         realm.setMaxDeltaTimeSeconds(60 * 60 * 12); // 12 hours
         realm.setFailureFactor(30);
         realm.setSslRequired(SslRequired.EXTERNAL);
+        realm.setOTPPolicy(OTPPolicy.DEFAULT_POLICY);
 
         realm.setEventsListeners(Collections.singleton("jboss-logging"));
     }
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 bd702d2..1756f12 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -22,11 +22,6 @@
 package org.keycloak.services.resources;
 
 import org.jboss.logging.Logger;
-import org.jboss.resteasy.spi.BadRequestException;
-import org.jboss.resteasy.spi.HttpRequest;
-import org.keycloak.AbstractOAuthClient;
-import org.keycloak.ClientConnection;
-import org.keycloak.OAuth2Constants;
 import org.keycloak.account.AccountPages;
 import org.keycloak.account.AccountProvider;
 import org.keycloak.events.Details;
@@ -38,10 +33,8 @@ import org.keycloak.login.LoginFormsProvider;
 import org.keycloak.models.AccountRoles;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.Constants;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.IdentityProviderModel;
-import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelDuplicateException;
 import org.keycloak.models.ModelException;
 import org.keycloak.models.ModelReadOnlyException;
@@ -50,11 +43,11 @@ import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserCredentialValueModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.CredentialValidation;
 import org.keycloak.models.utils.FormMessage;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.TimeBasedOTP;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
-import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
 import org.keycloak.protocol.oidc.utils.RedirectUtils;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
@@ -65,7 +58,6 @@ import org.keycloak.services.managers.Auth;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.messages.Messages;
-import org.keycloak.services.util.CookieHelper;
 import org.keycloak.services.util.ResolveRelative;
 import org.keycloak.services.validation.Validation;
 import org.keycloak.util.UriUtils;
@@ -76,12 +68,8 @@ import javax.ws.rs.OPTIONS;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.Cookie;
-import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.NewCookie;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
@@ -441,7 +429,7 @@ public class AccountService extends AbstractSecuredLocalService {
         csrfCheck(stateChecker);
 
         UserModel user = auth.getUser();
-        user.setTotp(false);
+        user.setOtpEnabled(false);
 
         event.event(EventType.REMOVE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
 
@@ -552,17 +540,23 @@ public class AccountService extends AbstractSecuredLocalService {
         if (Validation.isBlank(totp)) {
             setReferrerOnPage();
             return account.setError(Messages.MISSING_TOTP).createResponse(AccountPages.TOTP);
-        } else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
+        } else if (!CredentialValidation.validOTP(realm, totp, totpSecret)) {
             setReferrerOnPage();
             return account.setError(Messages.INVALID_TOTP).createResponse(AccountPages.TOTP);
         }
 
         UserCredentialModel credentials = new UserCredentialModel();
-        credentials.setType(CredentialRepresentation.TOTP);
+        credentials.setType(realm.getOTPPolicy().getType());
         credentials.setValue(totpSecret);
         session.users().updateCredential(realm, user, credentials);
 
-        user.setTotp(true);
+        user.setOtpEnabled(true);
+
+        // to update counter
+        UserCredentialModel cred = new UserCredentialModel();
+        cred.setType(realm.getOTPPolicy().getType());
+        cred.setValue(totp);
+        session.users().validCredentials(realm, user, cred);
 
         event.event(EventType.UPDATE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 1a1aa29..245a3d3 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -69,7 +69,6 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -213,7 +212,7 @@ public class UsersResource {
         user.setLastName(rep.getLastName());
 
         user.setEnabled(rep.isEnabled());
-        user.setTotp(rep.isTotp());
+        user.setOtpEnabled(rep.isTotp());
         user.setEmailVerified(rep.isEmailVerified());
 
         List<String> reqActions = rep.getRequiredActions();
@@ -821,7 +820,7 @@ public class UsersResource {
             throw new NotFoundException("User not found");
         }
 
-        user.setTotp(false);
+        user.setOtpEnabled(false);
         adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index 14f81d0..c6b77b5 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -47,6 +47,7 @@ import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserModel.RequiredAction;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.CredentialValidation;
 import org.keycloak.models.utils.DefaultAuthenticationFlows;
 import org.keycloak.models.utils.FormMessage;
 import org.keycloak.models.utils.TimeBasedOTP;
@@ -563,18 +564,25 @@ public class LoginActionsService {
             return loginForms.setError(Messages.MISSING_TOTP)
                     .setClientSessionCode(accessCode.getCode())
                     .createResponse(RequiredAction.CONFIGURE_TOTP);
-        } else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
+        } else if (!CredentialValidation.validOTP(realm, totp, totpSecret)) {
             return loginForms.setError(Messages.INVALID_TOTP)
                     .setClientSessionCode(accessCode.getCode())
                     .createResponse(RequiredAction.CONFIGURE_TOTP);
         }
 
         UserCredentialModel credentials = new UserCredentialModel();
-        credentials.setType(CredentialRepresentation.TOTP);
+        credentials.setType(realm.getOTPPolicy().getType());
         credentials.setValue(totpSecret);
         session.users().updateCredential(realm, user, credentials);
 
-        user.setTotp(true);
+
+        // if type is HOTP, to update counter we execute validation based on supplied token
+        UserCredentialModel cred = new UserCredentialModel();
+        cred.setType(realm.getOTPPolicy().getType());
+        cred.setValue(totp);
+        session.users().validCredentials(realm, user, cred);
+
+        user.setOtpEnabled(true);
 
         user.removeRequiredAction(RequiredAction.CONFIGURE_TOTP);
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index 7faacda..bcaa98e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -167,7 +167,7 @@ public class AccountTest {
         });
     }
 
-    //@Test
+    @Test
     public void ideTesting() throws Exception {
         Thread.sleep(100000000);
     }
@@ -517,11 +517,11 @@ public class AccountTest {
         Assert.assertFalse(driver.getPageSource().contains("Remove Google"));
 
         // Error with false code
-        totpPage.configure(totp.generate(totpPage.getTotpSecret() + "123"));
+        totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret() + "123"));
 
         Assert.assertEquals("Invalid authenticator code.", profilePage.getError());
 
-        totpPage.configure(totp.generate(totpPage.getTotpSecret()));
+        totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
 
         Assert.assertEquals("Mobile authenticator configured.", profilePage.getSuccess());
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
index c448a92..cb5c721 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
@@ -25,7 +25,6 @@ import org.junit.Assert;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
-import org.keycloak.authentication.requiredactions.UpdateTotp;
 import org.keycloak.events.Details;
 import org.keycloak.events.Event;
 import org.keycloak.events.EventType;
@@ -113,7 +112,7 @@ public class RequiredActionTotpSetupTest {
 
         totpPage.assertCurrent();
 
-        totpPage.configure(totp.generate(totpPage.getTotpSecret()));
+        totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
 
         String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp").assertEvent().getSessionId();
 
@@ -131,7 +130,7 @@ public class RequiredActionTotpSetupTest {
 
         String totpSecret = totpPage.getTotpSecret();
 
-        totpPage.configure(totp.generate(totpSecret));
+        totpPage.configure(totp.generateTOTP(totpSecret));
 
         String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent().getSessionId();
 
@@ -146,7 +145,7 @@ public class RequiredActionTotpSetupTest {
         loginPage.open();
         loginPage.login("test-user@localhost", "password");
         String src = driver.getPageSource();
-        loginTotpPage.login(totp.generate(totpSecret));
+        loginTotpPage.login(totp.generateTOTP(totpSecret));
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
@@ -166,7 +165,7 @@ public class RequiredActionTotpSetupTest {
         totpPage.assertCurrent();
 
         String totpCode = totpPage.getTotpSecret();
-        totpPage.configure(totp.generate(totpCode));
+        totpPage.configure(totp.generateTOTP(totpCode));
 
         // After totp config, user should be on the app page
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
@@ -184,11 +183,13 @@ public class RequiredActionTotpSetupTest {
         loginPage.login("setupTotp2", "password2");
 
         // Totp is already configured, thus one-time password is needed, login page should be loaded
+        String uri = driver.getCurrentUrl();
+        String src = driver.getPageSource();
         Assert.assertTrue(loginPage.isCurrent());
         Assert.assertFalse(totpPage.isCurrent());
 
         // Login with one-time password
-        loginTotpPage.login(totp.generate(totpCode));
+        loginTotpPage.login(totp.generateTOTP(totpCode));
 
         loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "setuptotp2").assertEvent();
 
@@ -211,7 +212,7 @@ public class RequiredActionTotpSetupTest {
 
         // Since the authentificator was removed, it has to be set up again
         totpPage.assertCurrent();
-        totpPage.configure(totp.generate(totpPage.getTotpSecret()));
+        totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
 
         String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp2").assertEvent().getSessionId();
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java
index 19cbbf0..8f0778c 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java
@@ -72,7 +72,7 @@ public class BruteForceTest {
             credentials.setValue("totpSecret");
             user.updateCredential(credentials);
 
-            user.setTotp(true);
+            user.setOtpEnabled(true);
             appRealm.setEventsListeners(Collections.singleton("dummy"));
 
             appRealm.setBruteForceProtected(true);
@@ -158,14 +158,14 @@ public class BruteForceTest {
     @Test
     public void testGrantInvalidPassword() throws Exception {
         {
-            String totpSecret = totp.generate("totpSecret");
+            String totpSecret = totp.generateTOTP("totpSecret");
             OAuthClient.AccessTokenResponse response = getTestToken("password", totpSecret);
             Assert.assertNotNull(response.getAccessToken());
             Assert.assertNull(response.getError());
             events.clear();
         }
         {
-            String totpSecret = totp.generate("totpSecret");
+            String totpSecret = totp.generateTOTP("totpSecret");
             OAuthClient.AccessTokenResponse response = getTestToken("invalid", totpSecret);
             Assert.assertNull(response.getAccessToken());
             Assert.assertEquals(response.getError(), "invalid_grant");
@@ -173,7 +173,7 @@ public class BruteForceTest {
             events.clear();
         }
         {
-            String totpSecret = totp.generate("totpSecret");
+            String totpSecret = totp.generateTOTP("totpSecret");
             OAuthClient.AccessTokenResponse response = getTestToken("invalid", totpSecret);
             Assert.assertNull(response.getAccessToken());
             Assert.assertEquals(response.getError(), "invalid_grant");
@@ -181,7 +181,7 @@ public class BruteForceTest {
             events.clear();
         }
         {
-            String totpSecret = totp.generate("totpSecret");
+            String totpSecret = totp.generateTOTP("totpSecret");
             OAuthClient.AccessTokenResponse response = getTestToken("password", totpSecret);
             Assert.assertNull(response.getAccessToken());
             Assert.assertNotNull(response.getError());
@@ -191,7 +191,7 @@ public class BruteForceTest {
         }
         clearUserFailures();
         {
-            String totpSecret = totp.generate("totpSecret");
+            String totpSecret = totp.generateTOTP("totpSecret");
             OAuthClient.AccessTokenResponse response = getTestToken("password", totpSecret);
             Assert.assertNotNull(response.getAccessToken());
             Assert.assertNull(response.getError());
@@ -203,7 +203,7 @@ public class BruteForceTest {
     @Test
     public void testGrantInvalidOtp() throws Exception {
         {
-            String totpSecret = totp.generate("totpSecret");
+            String totpSecret = totp.generateTOTP("totpSecret");
             OAuthClient.AccessTokenResponse response = getTestToken("password", totpSecret);
             Assert.assertNotNull(response.getAccessToken());
             Assert.assertNull(response.getError());
@@ -224,7 +224,7 @@ public class BruteForceTest {
             events.clear();
         }
         {
-            String totpSecret = totp.generate("totpSecret");
+            String totpSecret = totp.generateTOTP("totpSecret");
             OAuthClient.AccessTokenResponse response = getTestToken("password", totpSecret);
             Assert.assertNull(response.getAccessToken());
             Assert.assertNotNull(response.getError());
@@ -234,7 +234,7 @@ public class BruteForceTest {
         }
         clearUserFailures();
         {
-            String totpSecret = totp.generate("totpSecret");
+            String totpSecret = totp.generateTOTP("totpSecret");
             OAuthClient.AccessTokenResponse response = getTestToken("password", totpSecret);
             Assert.assertNotNull(response.getAccessToken());
             Assert.assertNull(response.getError());
@@ -244,7 +244,7 @@ public class BruteForceTest {
     }   @Test
         public void testGrantMissingOtp() throws Exception {
         {
-            String totpSecret = totp.generate("totpSecret");
+            String totpSecret = totp.generateTOTP("totpSecret");
             OAuthClient.AccessTokenResponse response = getTestToken("password", totpSecret);
             Assert.assertNotNull(response.getAccessToken());
             Assert.assertNull(response.getError());
@@ -265,7 +265,7 @@ public class BruteForceTest {
             events.clear();
         }
         {
-            String totpSecret = totp.generate("totpSecret");
+            String totpSecret = totp.generateTOTP("totpSecret");
             OAuthClient.AccessTokenResponse response = getTestToken("password", totpSecret);
             Assert.assertNull(response.getAccessToken());
             Assert.assertNotNull(response.getError());
@@ -275,7 +275,7 @@ public class BruteForceTest {
         }
         clearUserFailures();
         {
-            String totpSecret = totp.generate("totpSecret");
+            String totpSecret = totp.generateTOTP("totpSecret");
             OAuthClient.AccessTokenResponse response = getTestToken("password", totpSecret);
             Assert.assertNotNull(response.getAccessToken());
             Assert.assertNull(response.getError());
@@ -353,7 +353,7 @@ public class BruteForceTest {
 
         loginTotpPage.assertCurrent();
 
-        String totpSecret = totp.generate("totpSecret");
+        String totpSecret = totp.generateTOTP("totpSecret");
         loginTotpPage.login(totpSecret);
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
index 0374715..b37401c 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
@@ -43,7 +43,6 @@ import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
 import org.keycloak.testsuite.rule.WebResource;
 import org.keycloak.testsuite.rule.WebRule;
-import org.keycloak.util.Time;
 import org.openqa.selenium.WebDriver;
 
 import java.net.MalformedURLException;
@@ -66,7 +65,7 @@ public class LoginTotpTest {
             credentials.setValue("totpSecret");
             user.updateCredential(credentials);
 
-            user.setTotp(true);
+            user.setOtpEnabled(true);
             appRealm.setEventsListeners(Collections.singleton("dummy"));
         }
 
@@ -147,7 +146,7 @@ public class LoginTotpTest {
 
         loginTotpPage.assertCurrent();
 
-        loginTotpPage.login(totp.generate("totpSecret"));
+        loginTotpPage.login(totp.generateTOTP("totpSecret"));
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/TotpGenerator.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/TotpGenerator.java
index 1e2449b..6126602 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/TotpGenerator.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/TotpGenerator.java
@@ -43,7 +43,7 @@ public class TotpGenerator {
         public void run() {
             String google = new String(Base32.decode(secret));
             TimeBasedOTP otp = new TimeBasedOTP();
-            System.out.println(otp.generate(google));
+            System.out.println(otp.generateTOTP(google));
         }
     }