diff --git a/config.template.yml b/config.template.yml
index cd6db1f9..0ed76997 100644
--- a/config.template.yml
+++ b/config.template.yml
@@ -35,12 +35,21 @@ default_redirection_url: https://home.example.com:8080/
 #
 ## google_analytics: UA-00000-01
 
-# TOTP Issuer Name
+# TOTP Settings
 #
-# This will be the issuer name displayed in Google Authenticator
-# See: https://github.com/google/google-authenticator/wiki/Key-Uri-Format for more info on issuer names
+# Parameters used for TOTP generation
 totp:
+  # The issuer name displayed in the Authenticator application of your choice
+  # See: https://github.com/google/google-authenticator/wiki/Key-Uri-Format for more info on issuer names
   issuer: authelia.com
+  # The period in seconds a one-time password is current for. Changing this will require all users to register
+  # their TOTP applications again.
+  # Warning: before changing period read the docs link below.
+  period: 30
+  # The skew controls number of one-time passwords either side of the current one that are valid.
+  # Warning: before changing skew read the docs link below.
+  skew: 1
+  #  See: https://docs.authelia.com/configuration/one-time-password.html#period-and-skew to read the documentation.
 
 # Duo Push API
 #
diff --git a/docs/configuration/one-time-password.md b/docs/configuration/one-time-password.md
index 08c7c5f8..9aa38379 100644
--- a/docs/configuration/one-time-password.md
+++ b/docs/configuration/one-time-password.md
@@ -7,11 +7,47 @@ nav_order: 6
 
 # One-Time Password
 
-Applications generating one-time passwords usually displays an issuer to
-differentiate the various applications registered by the user.
-
-Authelia allows to customize the issuer to differentiate the entry created
-by Authelia from others.
+Authelia uses time based one-time passwords as the OTP method. You have 
+the option to tune the settings of the TOTP generation and you can see a
+full example of TOTP configuration below, as well as sections describing them.
 
     totp:
-        issuer: authelia.com
\ No newline at end of file
+        issuer: authelia.com
+        period: 30
+        skew: 1
+        
+## Issuer
+
+Applications generating one-time passwords usually display an issuer to
+differentiate applications registered by the user.
+
+Authelia allows customisation of the issuer to differentiate the entry created
+by Authelia from others.
+
+## Period and Skew
+
+The period and skew configuration parameters affect each other. The default values are
+a period of 30 and a skew of 1. It is highly recommended you do not change these unless
+you wish to set skew to 0.
+
+The way you configure these affects security by changing the length of time a one-time
+password is valid for. The formula to calculate the effective validity period is 
+`period + (period * skew * 2)`. For example period 30 and skew 1 would result in 90 
+seconds of validity, and period 30 and skew 2 would result in 150 seconds of validity.
+
+
+### Period
+
+Configures the period of time in seconds a one-time password is current for. It is important
+to note that changing this value will require your users to register their application again.
+
+It is recommended to keep this value set to 30, the minimum is 1.
+  
+### Skew
+
+Configures the number of one-time passwords either side of the current one that are
+considered valid, each time you increase this it makes two more one-time passwords valid. 
+For example the default of 1 has a total of 3 keys valid. A value of 2 has 5 one-time passwords 
+valid.
+
+It is recommended to keep this value set to 0 or 1, the minimum is 0.
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 753c5a33..e3664037 100644
--- a/go.mod
+++ b/go.mod
@@ -13,7 +13,7 @@ require (
 	github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
 	github.com/fasthttp/router v0.7.0
 	github.com/fasthttp/session v1.1.7
-	github.com/go-ldap/ldap/v3 v3.1.7 // indirect
+	github.com/go-ldap/ldap/v3 v3.1.7
 	github.com/go-sql-driver/mysql v1.5.0
 	github.com/golang/mock v1.4.3
 	github.com/golang/snappy v0.0.1 // indirect
diff --git a/go.sum b/go.sum
index 249eb774..18603dc0 100644
--- a/go.sum
+++ b/go.sum
@@ -223,10 +223,12 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9
 github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
 github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
 github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
 github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
 github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
diff --git a/internal/configuration/schema/totp.go b/internal/configuration/schema/totp.go
index bcdc29a2..45c38d0f 100644
--- a/internal/configuration/schema/totp.go
+++ b/internal/configuration/schema/totp.go
@@ -3,4 +3,6 @@ package schema
 // TOTPConfiguration represents the configuration related to TOTP options.
 type TOTPConfiguration struct {
 	Issuer string `mapstructure:"issuer"`
+	Period int    `mapstructure:"period"`
+	Skew   *int   `mapstructure:"skew"`
 }
diff --git a/internal/configuration/validator/configuration.go b/internal/configuration/validator/configuration.go
index 890b2bdc..e44b7d3d 100644
--- a/internal/configuration/validator/configuration.go
+++ b/internal/configuration/validator/configuration.go
@@ -46,8 +46,8 @@ func Validate(configuration *schema.Configuration, validator *schema.StructValid
 
 	if configuration.TOTP == nil {
 		configuration.TOTP = &schema.TOTPConfiguration{}
-		ValidateTOTP(configuration.TOTP, validator)
 	}
+	ValidateTOTP(configuration.TOTP, validator)
 
 	if configuration.Notifier == nil {
 		validator.Push(fmt.Errorf("A notifier configuration must be provided"))
diff --git a/internal/configuration/validator/totp.go b/internal/configuration/validator/totp.go
index edd7ad75..5dfa9cf3 100644
--- a/internal/configuration/validator/totp.go
+++ b/internal/configuration/validator/totp.go
@@ -1,14 +1,29 @@
 package validator
 
 import (
+	"fmt"
 	"github.com/authelia/authelia/internal/configuration/schema"
 )
 
 const defaultTOTPIssuer = "Authelia"
+const DefaultTOTPPeriod = 30
+const DefaultTOTPSkew = 1
 
 // ValidateTOTP validates and update TOTP configuration.
 func ValidateTOTP(configuration *schema.TOTPConfiguration, validator *schema.StructValidator) {
 	if configuration.Issuer == "" {
 		configuration.Issuer = defaultTOTPIssuer
 	}
+	if configuration.Period == 0 {
+		configuration.Period = DefaultTOTPPeriod
+	} else if configuration.Period < 0 {
+		validator.Push(fmt.Errorf("TOTP Period must be 1 or more"))
+	}
+
+	if configuration.Skew == nil {
+		var skew = DefaultTOTPSkew
+		configuration.Skew = &skew
+	} else if *configuration.Skew < 0 {
+		validator.Push(fmt.Errorf("TOTP Skew must be 0 or more"))
+	}
 }
diff --git a/internal/configuration/validator/totp_test.go b/internal/configuration/validator/totp_test.go
index ff32354c..aae98c5b 100644
--- a/internal/configuration/validator/totp_test.go
+++ b/internal/configuration/validator/totp_test.go
@@ -8,7 +8,7 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-func TestShouldSetDefaultIssuer(t *testing.T) {
+func TestShouldSetDefaultTOTPValues(t *testing.T) {
 	validator := schema.NewStructValidator()
 	config := schema.TOTPConfiguration{}
 
@@ -16,4 +16,20 @@ func TestShouldSetDefaultIssuer(t *testing.T) {
 
 	require.Len(t, validator.Errors(), 0)
 	assert.Equal(t, "Authelia", config.Issuer)
+	assert.Equal(t, DefaultTOTPSkew, *config.Skew)
+	assert.Equal(t, DefaultTOTPPeriod, config.Period)
+}
+
+func TestShouldRaiseErrorWhenInvalidTOTPMinimumValues(t *testing.T) {
+	var badSkew = -1
+	validator := schema.NewStructValidator()
+	config := schema.TOTPConfiguration{
+		Period: -5,
+		Skew:   &badSkew,
+	}
+	ValidateTOTP(&config, validator)
+	assert.Len(t, validator.Errors(), 2)
+	assert.EqualError(t, validator.Errors()[0], "TOTP Period must be 1 or more")
+	assert.EqualError(t, validator.Errors()[1], "TOTP Skew must be 0 or more")
+
 }
diff --git a/internal/handlers/handler_extended_configuration.go b/internal/handlers/handler_extended_configuration.go
index 09085ee2..0ac352a7 100644
--- a/internal/handlers/handler_extended_configuration.go
+++ b/internal/handlers/handler_extended_configuration.go
@@ -11,12 +11,16 @@ type ExtendedConfigurationBody struct {
 
 	// SecondFactorEnabled whether second factor is enabled
 	SecondFactorEnabled bool `json:"second_factor_enabled"`
+
+	// TOTP Period
+	TOTPPeriod int `json:"totp_period"`
 }
 
 // ExtendedConfigurationGet get the extended configuration accessible to authenticated users.
 func ExtendedConfigurationGet(ctx *middlewares.AutheliaCtx) {
 	body := ExtendedConfigurationBody{}
 	body.AvailableMethods = MethodList{authentication.TOTP, authentication.U2F}
+	body.TOTPPeriod = ctx.Configuration.TOTP.Period
 
 	if ctx.Configuration.DuoAPI != nil {
 		body.AvailableMethods = append(body.AvailableMethods, authentication.Push)
diff --git a/internal/handlers/handler_extended_configuration_test.go b/internal/handlers/handler_extended_configuration_test.go
index 27c76141..507e5d4f 100644
--- a/internal/handlers/handler_extended_configuration_test.go
+++ b/internal/handlers/handler_extended_configuration_test.go
@@ -4,9 +4,10 @@ import (
 	"testing"
 
 	"github.com/authelia/authelia/internal/authorization"
+	"github.com/authelia/authelia/internal/configuration/schema"
+	"github.com/authelia/authelia/internal/configuration/validator"
 	"github.com/authelia/authelia/internal/mocks"
 
-	"github.com/authelia/authelia/internal/configuration/schema"
 	"github.com/stretchr/testify/suite"
 )
 
@@ -28,9 +29,15 @@ func (s *SecondFactorAvailableMethodsFixture) TearDownTest() {
 }
 
 func (s *SecondFactorAvailableMethodsFixture) TestShouldServeDefaultMethods() {
+	s.mock.Ctx.Configuration = schema.Configuration{
+		TOTP: &schema.TOTPConfiguration{
+			Period: validator.DefaultTOTPPeriod,
+		},
+	}
 	expectedBody := ExtendedConfigurationBody{
 		AvailableMethods:    []string{"totp", "u2f"},
 		SecondFactorEnabled: false,
+		TOTPPeriod:          validator.DefaultTOTPPeriod,
 	}
 	ExtendedConfigurationGet(s.mock.Ctx)
 	s.mock.Assert200OK(s.T(), expectedBody)
@@ -39,16 +46,25 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldServeDefaultMethods() {
 func (s *SecondFactorAvailableMethodsFixture) TestShouldServeDefaultMethodsAndMobilePush() {
 	s.mock.Ctx.Configuration = schema.Configuration{
 		DuoAPI: &schema.DuoAPIConfiguration{},
+		TOTP: &schema.TOTPConfiguration{
+			Period: validator.DefaultTOTPPeriod,
+		},
 	}
 	expectedBody := ExtendedConfigurationBody{
 		AvailableMethods:    []string{"totp", "u2f", "mobile_push"},
 		SecondFactorEnabled: false,
+		TOTPPeriod:          validator.DefaultTOTPPeriod,
 	}
 	ExtendedConfigurationGet(s.mock.Ctx)
 	s.mock.Assert200OK(s.T(), expectedBody)
 }
 
 func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsDisabledWhenNoRuleIsSetToTwoFactor() {
+	s.mock.Ctx.Configuration = schema.Configuration{
+		TOTP: &schema.TOTPConfiguration{
+			Period: validator.DefaultTOTPPeriod,
+		},
+	}
 	s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
 		DefaultPolicy: "bypass",
 		Rules: []schema.ACLRule{
@@ -70,10 +86,16 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsDisab
 	s.mock.Assert200OK(s.T(), ExtendedConfigurationBody{
 		AvailableMethods:    []string{"totp", "u2f"},
 		SecondFactorEnabled: false,
+		TOTPPeriod:          validator.DefaultTOTPPeriod,
 	})
 }
 
 func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsEnabledWhenDefaultPolicySetToTwoFactor() {
+	s.mock.Ctx.Configuration = schema.Configuration{
+		TOTP: &schema.TOTPConfiguration{
+			Period: validator.DefaultTOTPPeriod,
+		},
+	}
 	s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
 		DefaultPolicy: "two_factor",
 		Rules: []schema.ACLRule{
@@ -95,10 +117,16 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsEnabl
 	s.mock.Assert200OK(s.T(), ExtendedConfigurationBody{
 		AvailableMethods:    []string{"totp", "u2f"},
 		SecondFactorEnabled: true,
+		TOTPPeriod:          validator.DefaultTOTPPeriod,
 	})
 }
 
 func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsEnabledWhenSomePolicySetToTwoFactor() {
+	s.mock.Ctx.Configuration = schema.Configuration{
+		TOTP: &schema.TOTPConfiguration{
+			Period: validator.DefaultTOTPPeriod,
+		},
+	}
 	s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
 		DefaultPolicy: "bypass",
 		Rules: []schema.ACLRule{
@@ -120,6 +148,7 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsEnabl
 	s.mock.Assert200OK(s.T(), ExtendedConfigurationBody{
 		AvailableMethods:    []string{"totp", "u2f"},
 		SecondFactorEnabled: true,
+		TOTPPeriod:          validator.DefaultTOTPPeriod,
 	})
 }
 
diff --git a/internal/handlers/handler_register_totp.go b/internal/handlers/handler_register_totp.go
index 63ea2dc3..9dee1465 100644
--- a/internal/handlers/handler_register_totp.go
+++ b/internal/handlers/handler_register_totp.go
@@ -41,6 +41,7 @@ func secondFactorTOTPIdentityFinish(ctx *middlewares.AutheliaCtx, username strin
 		Issuer:      ctx.Configuration.TOTP.Issuer,
 		AccountName: username,
 		SecretSize:  32,
+		Period:      uint(ctx.Configuration.TOTP.Period),
 	})
 
 	if err != nil {
diff --git a/internal/handlers/handler_sign_totp.go b/internal/handlers/handler_sign_totp.go
index e1b11b6e..fe91a657 100644
--- a/internal/handlers/handler_sign_totp.go
+++ b/internal/handlers/handler_sign_totp.go
@@ -25,7 +25,11 @@ func SecondFactorTOTPPost(totpVerifier TOTPVerifier) middlewares.RequestHandler
 			return
 		}
 
-		isValid := totpVerifier.Verify(bodyJSON.Token, secret)
+		isValid, err := totpVerifier.Verify(bodyJSON.Token, secret)
+		if err != nil {
+			ctx.Error(fmt.Errorf("Error occurred during OTP validation for user %s: %s", userSession.Username, err), mfaValidationFailedMessage)
+			return
+		}
 
 		if !isValid {
 			ctx.Error(fmt.Errorf("Wrong passcode during TOTP validation for user %s", userSession.Username), mfaValidationFailedMessage)
diff --git a/internal/handlers/handler_sign_totp_test.go b/internal/handlers/handler_sign_totp_test.go
index 6f2ce253..baf8ae3d 100644
--- a/internal/handlers/handler_sign_totp_test.go
+++ b/internal/handlers/handler_sign_totp_test.go
@@ -40,7 +40,7 @@ func (s *HandlerSignTOTPSuite) TestShouldRedirectUserToDefaultURL() {
 
 	verifier.EXPECT().
 		Verify(gomock.Eq("abc"), gomock.Eq("secret")).
-		Return(true)
+		Return(true, nil)
 
 	s.mock.Ctx.Configuration.DefaultRedirectionURL = "http://redirection.local"
 
@@ -65,7 +65,7 @@ func (s *HandlerSignTOTPSuite) TestShouldNotReturnRedirectURL() {
 
 	verifier.EXPECT().
 		Verify(gomock.Eq("abc"), gomock.Eq("secret")).
-		Return(true)
+		Return(true, nil)
 
 	bodyBytes, err := json.Marshal(signTOTPRequestBody{
 		Token: "abc",
@@ -86,7 +86,7 @@ func (s *HandlerSignTOTPSuite) TestShouldRedirectUserToSafeTargetURL() {
 
 	verifier.EXPECT().
 		Verify(gomock.Eq("abc"), gomock.Eq("secret")).
-		Return(true)
+		Return(true, nil)
 
 	bodyBytes, err := json.Marshal(signTOTPRequestBody{
 		Token:     "abc",
@@ -110,7 +110,7 @@ func (s *HandlerSignTOTPSuite) TestShouldNotRedirectToUnsafeURL() {
 
 	verifier.EXPECT().
 		Verify(gomock.Eq("abc"), gomock.Eq("secret")).
-		Return(true)
+		Return(true, nil)
 
 	bodyBytes, err := json.Marshal(signTOTPRequestBody{
 		Token:     "abc",
@@ -132,7 +132,7 @@ func (s *HandlerSignTOTPSuite) TestShouldRegenerateSessionForPreventingSessionFi
 
 	verifier.EXPECT().
 		Verify(gomock.Eq("abc"), gomock.Eq("secret")).
-		Return(true)
+		Return(true, nil)
 
 	bodyBytes, err := json.Marshal(signTOTPRequestBody{
 		Token: "abc",
diff --git a/internal/handlers/totp.go b/internal/handlers/totp.go
index 814afc56..83476d4d 100644
--- a/internal/handlers/totp.go
+++ b/internal/handlers/totp.go
@@ -1,15 +1,26 @@
 package handlers
 
 import (
+	"github.com/pquerna/otp"
 	"github.com/pquerna/otp/totp"
+	"time"
 )
 
 type TOTPVerifier interface {
-	Verify(token, secret string) bool
+	Verify(token, secret string) (bool, error)
 }
 
-type TOTPVerifierImpl struct{}
-
-func (tv *TOTPVerifierImpl) Verify(token, secret string) bool {
-	return totp.Validate(token, secret)
+type TOTPVerifierImpl struct {
+	Period uint
+	Skew   uint
+}
+
+func (tv *TOTPVerifierImpl) Verify(token, secret string) (bool, error) {
+	opts := totp.ValidateOpts{
+		Period:    tv.Period,
+		Skew:      tv.Skew,
+		Digits:    otp.DigitsSix,
+		Algorithm: otp.AlgorithmSHA1,
+	}
+	return totp.ValidateCustom(token, secret, time.Now().UTC(), opts)
 }
diff --git a/internal/handlers/totp_mock.go b/internal/handlers/totp_mock.go
index 80e5596e..09d2641e 100644
--- a/internal/handlers/totp_mock.go
+++ b/internal/handlers/totp_mock.go
@@ -33,11 +33,12 @@ func (m *MockTOTPVerifier) EXPECT() *MockTOTPVerifierMockRecorder {
 }
 
 // Verify mocks base method
-func (m *MockTOTPVerifier) Verify(token, secret string) bool {
+func (m *MockTOTPVerifier) Verify(token, secret string) (bool, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Verify", token, secret)
 	ret0, _ := ret[0].(bool)
-	return ret0
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
 }
 
 // Verify indicates an expected call of Verify
diff --git a/internal/server/server.go b/internal/server/server.go
index 1beb38de..cec8dbb0 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -61,7 +61,10 @@ func StartServer(configuration schema.Configuration, providers middlewares.Provi
 	router.POST("/api/secondfactor/totp/identity/finish", autheliaMiddleware(
 		middlewares.RequireFirstFactor(handlers.SecondFactorTOTPIdentityFinish)))
 	router.POST("/api/secondfactor/totp", autheliaMiddleware(
-		middlewares.RequireFirstFactor(handlers.SecondFactorTOTPPost(&handlers.TOTPVerifierImpl{}))))
+		middlewares.RequireFirstFactor(handlers.SecondFactorTOTPPost(&handlers.TOTPVerifierImpl{
+			Period: uint(configuration.TOTP.Period),
+			Skew:   uint(*configuration.TOTP.Skew),
+		}))))
 
 	// U2F related endpoints
 	router.POST("/api/secondfactor/u2f/identity/start", autheliaMiddleware(
diff --git a/web/src/components/PieChartIcon.tsx b/web/src/components/PieChartIcon.tsx
index 3316d3f2..a8fd4176 100644
--- a/web/src/components/PieChartIcon.tsx
+++ b/web/src/components/PieChartIcon.tsx
@@ -26,7 +26,7 @@ export default function (props: Props) {
             <circle r="5" cx="13" cy="13" fill="none"
                 stroke={color}
                 strokeWidth="10"
-                strokeDasharray={`calc(${props.progress} * 31.6 / ${maxProgress}) 31.6`}
+                strokeDasharray={`${props.progress} ${maxProgress}`}
                 transform="rotate(-90) translate(-26)" />
         </svg>
     )
diff --git a/web/src/components/TimerIcon.tsx b/web/src/components/TimerIcon.tsx
index d82423ba..1c480d78 100644
--- a/web/src/components/TimerIcon.tsx
+++ b/web/src/components/TimerIcon.tsx
@@ -4,32 +4,31 @@ import PieChartIcon from "./PieChartIcon";
 export interface Props {
     width: number;
     height: number;
+    period: number;
 
     color?: string;
     backgroundColor?: string;
 }
 
 export default function (props: Props) {
-    const maxTimeProgress = 1000;
+    const radius = 31.6;
     const [timeProgress, setTimeProgress] = useState(0);
 
     useEffect(() => {
         // Get the current number of seconds to initialize timer.
-        const initialValue = Math.floor((new Date().getSeconds() % 30) / 30 * maxTimeProgress);
+        const initialValue = (new Date().getTime() / 1000) % props.period / props.period * radius;
         setTimeProgress(initialValue);
 
         const interval = setInterval(() => {
-            const ms = new Date().getSeconds() * 1000.0 + new Date().getMilliseconds();
-            const value = (ms % 30000) / 30000 * maxTimeProgress;
+            const value = (new Date().getTime() / 1000) % props.period / props.period * radius;
             setTimeProgress(value);
         }, 100);
         return () => clearInterval(interval);
-    }, []);
+    }, [props]);
 
     return (
         <PieChartIcon width={props.width} height={props.height}
-            maxProgress={maxTimeProgress}
-            progress={timeProgress}
+            progress={timeProgress} maxProgress={radius}
             backgroundColor={props.backgroundColor} color={props.color} />
     )
 }
diff --git a/web/src/models/Configuration.ts b/web/src/models/Configuration.ts
index 6d2559d6..709efa60 100644
--- a/web/src/models/Configuration.ts
+++ b/web/src/models/Configuration.ts
@@ -7,4 +7,5 @@ export interface Configuration {
 export interface ExtendedConfiguration {
     available_methods: Set<SecondFactorMethod>;
     second_factor_enabled: boolean;
+    totp_period: number;
 }
\ No newline at end of file
diff --git a/web/src/services/Configuration.ts b/web/src/services/Configuration.ts
index 123ab99a..a0c7530d 100644
--- a/web/src/services/Configuration.ts
+++ b/web/src/services/Configuration.ts
@@ -10,6 +10,7 @@ export async function getConfiguration(): Promise<Configuration> {
 interface ExtendedConfigurationPayload {
     available_methods: Method2FA[];
     second_factor_enabled: boolean;
+    totp_period: number;
 }
 
 export async function getExtendedConfiguration(): Promise<ExtendedConfiguration> {
diff --git a/web/src/views/LoginPortal/SecondFactor/OTPDial.tsx b/web/src/views/LoginPortal/SecondFactor/OTPDial.tsx
index 90fcad98..b3669d55 100644
--- a/web/src/views/LoginPortal/SecondFactor/OTPDial.tsx
+++ b/web/src/views/LoginPortal/SecondFactor/OTPDial.tsx
@@ -10,13 +10,13 @@ import SuccessIcon from "../../../components/SuccessIcon";
 export interface Props {
     passcode: string;
     state: State;
+    period: number
 
     onChange: (passcode: string) => void;
 }
 
 export default function (props: Props) {
     const style = useStyles();
-
     const dial = (
         <span className={style.otpInput} id="otp-input">
             <OtpInput
@@ -31,7 +31,7 @@ export default function (props: Props) {
 
     return (
         <IconWithContext
-            icon={<Icon state={props.state} />}
+            icon={<Icon state={props.state} period={props.period} />}
             context={dial} />
     )
 }
@@ -61,12 +61,13 @@ const useStyles = makeStyles(theme => ({
 
 interface IconProps {
     state: State;
+    period: number;
 }
 
 function Icon(props: IconProps) {
     return (
         <Fragment>
-            {props.state !== State.Success ? <TimerIcon backgroundColor="#000" color="#FFFFFF" width={64} height={64} /> : null}
+            {props.state !== State.Success ? <TimerIcon backgroundColor="#000" color="#FFFFFF" width={64} height={64} period={props.period} /> : null}
             {props.state === State.Success ? <SuccessIcon /> : null}
         </Fragment>
     )
diff --git a/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx b/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx
index 43c021fc..5196c722 100644
--- a/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx
+++ b/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx
@@ -16,6 +16,7 @@ export interface Props {
     id: string;
     authenticationLevel: AuthenticationLevel;
     registered: boolean;
+    totp_period: number
 
     onRegisterClick: () => void;
     onSignInError: (err: Error) => void;
@@ -83,7 +84,8 @@ export default function (props: Props) {
             <OTPDial
                 passcode={passcode}
                 onChange={setPasscode}
-                state={state} />
+                state={state}
+                period={props.totp_period} />
         </MethodContainer>
     )
 }
\ No newline at end of file
diff --git a/web/src/views/LoginPortal/SecondFactor/SecondFactorForm.tsx b/web/src/views/LoginPortal/SecondFactor/SecondFactorForm.tsx
index 62b4994c..7f8d2576 100644
--- a/web/src/views/LoginPortal/SecondFactor/SecondFactorForm.tsx
+++ b/web/src/views/LoginPortal/SecondFactor/SecondFactorForm.tsx
@@ -111,6 +111,7 @@ export default function (props: Props) {
                                 authenticationLevel={props.authenticationLevel}
                                 // Whether the user has a TOTP secret registered already
                                 registered={props.userInfo.has_totp}
+                                totp_period={props.configuration.totp_period}
                                 onRegisterClick={initiateRegistration(initiateTOTPRegistrationProcess)}
                                 onSignInError={err => createErrorNotification(err.message)}
                                 onSignInSuccess={props.onAuthenticationSuccess} />