You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by na...@apache.org on 2017/11/27 12:11:25 UTC

[2/4] fineract git commit: Two-Factor Authentication

Two-Factor Authentication

Technical Spec: https://mifosforge.jira.com/wiki/spaces/projects/pages/185277689/GSoC+2017+-+Two-Factor+Authentication


Project: http://git-wip-us.apache.org/repos/asf/fineract/repo
Commit: http://git-wip-us.apache.org/repos/asf/fineract/commit/1a966e8e
Tree: http://git-wip-us.apache.org/repos/asf/fineract/tree/1a966e8e
Diff: http://git-wip-us.apache.org/repos/asf/fineract/diff/1a966e8e

Branch: refs/heads/develop
Commit: 1a966e8e735dce586feae1e1c1372902a03f31e4
Parents: b56cc43
Author: Alex Ivanov <al...@gmail.com>
Authored: Mon Jun 12 20:20:26 2017 +0100
Committer: Alex Ivanov <al...@gmail.com>
Committed: Fri Sep 22 14:24:52 2017 +0100

----------------------------------------------------------------------
 api-docs/apiLive.htm                            | 242 +++++++++++++++
 api-docs/apidocs.css                            |   4 +
 fineract-provider/build.gradle                  |  36 ++-
 .../basicauth/twofactor/application.properties  |  21 ++
 .../oauth/twofactor/application.properties      |  21 ++
 .../commands/service/CommandWrapperBuilder.java |  16 +-
 .../domain/ConfigurationDomainService.java      |   9 +-
 .../domain/ConfigurationDomainServiceJpa.java   |  37 +++
 .../boot/AbstractApplicationConfiguration.java  |   3 +-
 .../core/boot/WebTwoFactorXmlConfiguration.java |  36 +++
 .../security/api/AuthenticationApiResource.java |  14 +-
 .../security/api/TwoFactorApiResource.java      | 132 ++++++++
 .../api/TwoFactorConfigurationApiResource.java  |  86 ++++++
 .../security/api/UserDetailsApiResource.java    |  17 +-
 .../InvalidateTFAccessTokenCommandHandler.java  | 108 +++++++
 .../UpdateTwoFactorConfigCommandHandler.java    |  60 ++++
 .../TwoFactorConfigurationConstants.java        |  59 ++++
 .../security/constants/TwoFactorConstants.java  |  30 ++
 .../security/data/AccessTokenData.java          |  47 +++
 .../data/AuthenticatedOauthUserData.java        |  12 +-
 .../security/data/AuthenticatedUserData.java    |  12 +-
 .../security/data/OTPDeliveryMethod.java        |  38 +++
 .../security/data/OTPMetadata.java              |  53 ++++
 .../security/data/OTPRequest.java               |  53 ++++
 .../data/TwoFactorConfigurationValidator.java   | 120 ++++++++
 .../security/domain/OTPRequestRepository.java   |  51 ++++
 .../security/domain/TFAccessToken.java          | 137 +++++++++
 .../domain/TFAccessTokenRepository.java         |  31 ++
 .../security/domain/TwoFactorConfiguration.java |  84 +++++
 .../TwoFactorConfigurationRepository.java       |  34 +++
 .../exception/AccessTokenInvalidIException.java |  28 ++
 .../OTPDeliveryMethodInvalidException.java      |  29 ++
 .../exception/OTPTokenInvalidException.java     |  28 ++
 .../InsecureTwoFactorAuthenticationFilter.java  |  82 +++++
 .../filter/TwoFactorAuthenticationFilter.java   | 139 +++++++++
 .../service/AccessTokenGenerationService.java   |  24 ++
 .../security/service/RandomOTPGenerator.java    |  38 +++
 .../service/TwoFactorConfigurationService.java  |  51 ++++
 .../TwoFactorConfigurationServiceImpl.java      | 304 +++++++++++++++++++
 .../security/service/TwoFactorService.java      |  43 +++
 .../security/service/TwoFactorServiceImpl.java  | 229 ++++++++++++++
 .../security/service/TwoFactorUtils.java        |  47 +++
 .../UUIDAccessTokenGenerationService.java       |  32 ++
 .../useradministration/domain/AppUser.java      |  17 ++
 .../main/resources/META-INF/spring/ehcache.xml  |   4 +
 .../META-INF/spring/securityContext.xml         |  38 ++-
 .../core_db/V336__two_factor_authentication.sql |  63 ++++
 47 files changed, 2763 insertions(+), 36 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/api-docs/apiLive.htm
----------------------------------------------------------------------
diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm
index bc0c43e..9a87171 100644
--- a/api-docs/apiLive.htm
+++ b/api-docs/apiLive.htm
@@ -2629,6 +2629,30 @@
 								<td></td>
 							</tr>
 							<tr>
+                                <td><a href="#twofactor">Two-Factor Authentication</a></td>
+								<td>twofactor</td>
+								<td><a href="#twofactor_request">Request OTP</a></td>
+								<td><a href="#twofactor_deliverymethods">List OTP delivery methods</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>twofactor/validate</td>
+								<td><a href="#twofactor_validate">Validate OTP</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+                            <tr>
+                                <td></td>
+                                <td>twofactor/invalidate</td>
+                                <td><a href="#twofactor_invalidate">Invalidate Access Token</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+							<tr>
 								<td><a href="#users">User</a></td>
 								<td>users</td>
 								<td><a href="#users_create">Create a User</a></td>
@@ -2942,6 +2966,16 @@
 										external service Configuration</a></td>
 								<td></td>
 							</tr>
+                            <tr>
+                                <td><a href="#twofactor_config_get">Two-Factor Configuration</a></td>
+                                <td>twofactor/configure</td>
+                                <td></td>
+                                <td><a href="#twofactor_config_get">Retrieve
+                                    Two-Factor Configuration</a></td>
+                                <td><a href="#twofactor_config_update">Update
+                                    Two-Factor Configuration</a></td>
+                                <td></td>
+                            </tr>
 						</table>
 					</div>
 				</div>
@@ -3931,6 +3965,11 @@
 						the platform setup <a href="https://github.com/openMF/mifosx/wiki/Launching-platform-server-locally-from-the-command-line#choosing-authentication-mechanism"> wiki</a> for additional details.
 					</p>
 					<p>
+						Optionally, two-factor authentication can be enabled by using
+						<i>-Ptwofactor=enabled</i> on gradle build.
+						Details of the authentication workflow with two-factor authentication enabled can be found <a href="#twofactor">here</a>.
+					</p>
+					<p>
 						The platform has been configured to reject plain HTTP requests and
 						to expect all API requests to be made over <a
 							href="http://en.wikipedia.org/wiki/HTTP_Secure">HTTPS</a>. All
@@ -4057,6 +4096,142 @@ function executeAjaxRequest(url, verbType, jsonData, authKey, successFunction, e
 </code>
 					</div>
 			</div>
+			<a id="twofactor" name="twofactor" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Two-Factor Authentication</h3>
+					<p>
+						Two-Factor authentication is supported by requesting & verifying
+						one-time passwords(OTP). OTPs are sent via SMS & email.
+					</p>
+					<p>
+						By default, two-factor authentication is disabled by default.
+						More information on how to enable TFA can be found <a href="#authentication_overview">here</a>.
+					</p>
+					<p>
+						Two-factor authentication workflow:
+						<ol class="normalli">
+							<li class="normalli">User authticates via BasicAuth / oAauth</li>
+							<li>Client requests a list of supported OTP delivery methods for the authenticated user(<a href="#twofactor_deliverymethods">Get Delivery Methods</a>)</li>
+							<li>User selects an OTP delivery method and client sends a request for OTP(<a href="#twofactor_request">Request OTP</a>)</li>
+							<li>User receives an OTP and the client sends it for verification(<a href="#twofactor_validate">Validate OTP</a>)</li>
+							<li>If the OTP is valid, an access token is returned</li>
+							<li>The access token is sent in following requestes to the server as a header <i>Fineract-Platform-TFA-Token</i></li>
+                            <li>On session end, the access token should be invalidated<a href="#twofactor_invalidate">Invalidate Access Token</a>)</li>
+						</ol>
+					</p>
+					<p>
+						Two-Factor authentication and delivery methods can be configured via
+						the <a href="#twofactor_configure"<i>/twofactor/configure</i> endpoint.</a>
+					</p>
+				</div>
+				<div class="method-example">
+
+				</div>
+			</div>
+
+			<a id="twofactor_deliverymethods" name="twofactor_deliverymethods" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Get Delivery Methods</h4>
+					<p>Returns a list of possible OTP delivery methods for the current user</p>
+					<p>Requires first-factor authenticated user.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">GET https://DomainName/api/v1/twofactor</code>
+					<code class="method-response">
+[
+    {
+        "name": "sms",
+        "target": "08888888888"
+    },
+    {
+        "name": "email",
+        "target": "user@example.com"
+    }
+]</code>
+				</div>
+			</div>
+
+			<a id="twofactor_request" name="twofactor_request" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Request OTP</h4>
+					<p>Requests an OTP.</p>
+					<p>Requires first-factor authenticated user.</p>
+					<h5>Arguments</h5>
+					<dl class="argument-list">
+						<dt>deliveryMethod</dt>
+						<dd>
+							String<span> mandatory, the delivery method name</span>
+						</dd>
+						<dt>extendedToken</dt>
+						<dd>
+							boolean<span> optional, whether to request an extended token, default false</span>
+						</dd>
+					</dl>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">POST https://DomainName/api/v1/twofactor?deliveryMethod=sms&extendedToken=false</code>
+					<code class="method-response">
+{
+    "requestTime": 1500000000000,
+    "tokenLiveTimeInSec": 300,
+    "extendedAccessToken": false,
+    "deliveryMethod": {
+        "name": "sms",
+        "target": "08888888888"
+    }
+}</code>
+				</div>
+			</div>
+
+			<a id="twofactor_validate" name="twofactor_validate" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Validate OTP</h4>
+					<p>Validates an OTP. If the OTP is valid, an access token is created.</p>
+					<p>The returned access token is later sent as a header <i>Fineract-Platform-TFA-Token</i>.</p>
+					<p>Requires first-factor authenticated user.</p>
+					<h5>Arguments</h5>
+					<dl class="argument-list">
+						<dt>token</dt>
+						<dd>
+							String<span> mandatory, the OTP to validate</span>
+						</dd>
+					</dl>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">POST https://DomainName/api/v1/twofactor/validate?token=YYYYY</code>
+					<code class="method-response">
+{
+    "token": "cb0bb6e33fc540709d50a16eb2e555f9",
+    "validFrom": 1501530702801,
+    "validTo": 1501617102801
+}</code>
+				</div>
+			</div>
+
+            <a id="twofactor_invalidate" name="twofactor_invalidate" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Invalidate Access Token</h4>
+                    <p>Invalidates an access token.</p>
+                    <p>Two factor access tokens should be invalidated on logout.</p>
+                    <p>Requires fully authenticated user.</p>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">POST https://DomainName/api/v1/twofactor/invalidate</code>
+                    <code class="method-request">
+{
+    "token": "cb0bb6e33fc540709d50a16eb2e555f9"
+}</code>
+                    <code class="method-response">
+{
+    "resourceIdentifier": "cb0bb6e33fc540709d50a16eb2e555f9"
+}</code>
+                </div>
+            </div>
 
 			<a id="batch_api" name="batch_api" class="old-syle-anchor">&nbsp;</a>
 			<div class="method-section">
@@ -19391,6 +19566,73 @@ Content-Type: application/json
 				</div>
 			</div>
 
+            <a id="twofactor_config" name="twofactor_config"
+               class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h3>Two-Factor Configuration</h3>
+                    <p>The following section describes the way to configure two-factor authentication</p>
+                    <p>Two-Factor Authentication has to be enabled by either building with Gradle arguments
+                        <i>-Ptwofactor=enabled</i> or enabling the <i>twofactor</i> profile via env. variable
+                    </p>
+                    <p>In order for SMS to be enabled an SMS bridge has to be setup with the message-gateway service.</p>
+                </div>
+                <div class="method-example">
+                </div>
+            </div>
+
+            <a id="twofactor_config_get" name="twofactor_config_get" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Retrieve Two-Factor Configuration</h4>
+                    <p>Returns available two-factor configuration.</p>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration"> GET https://DomainName/api/v1/twofactor/configure
+                    </code>
+                    <code class="method-response">
+{
+    "otp-delivery-email-body": "Hello {{username}}.\n\nYour OTP login token is {{token}}.",
+    "otp-delivery-sms-enable": true,
+    "otp-delivery-sms-provider": 6,
+    "otp-delivery-email-subject": "Fineract Two-Factor Authentication Token",
+    "otp-token-length": 5,
+    "access-token-live-time-extended": 604800,
+    "otp-delivery-email-enable": true,
+    "otp-token-live-time": 300,
+    "otp-delivery-sms-text": "Your authentication token for Fineract is {{token}}.",
+    "access-token-live-time": 86400
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="twofactor_config_update" name="twofactor_config_update" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Update Two-Factor Configuration</h4>
+                    <p>Update two-factor configuration.</p>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration"> PUT https://DomainName/api/v1/twofactor/configure
+                    </code>
+                    <code class="method-request">
+{
+    "otp-delivery-sms-provider": 7
+    "otp-delivery-sms-enable": false
+}
+                    </code>
+                    <code class="method-response">
+{
+    "changes": {
+        "otp-delivery-sms-enable": false,
+        "otp-delivery-sms-provider": 7
+    }
+}
+                    </code>
+                </div>
+            </div>
+
 			<!-- Fund starts here -->
 			<a id="funds" name="funds" class="old-syle-anchor">&nbsp;</a>
 			<div class="method-section">

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/api-docs/apidocs.css
----------------------------------------------------------------------
diff --git a/api-docs/apidocs.css b/api-docs/apidocs.css
index f8816a7..21bc11c 100644
--- a/api-docs/apidocs.css
+++ b/api-docs/apidocs.css
@@ -745,4 +745,8 @@ ul.field li {
 
 tt {
     font-size: 9.5pt;
+}
+
+ol.normalli li {
+    list-style-type: decimal;
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/build.gradle
----------------------------------------------------------------------
diff --git a/fineract-provider/build.gradle b/fineract-provider/build.gradle
index 771fcde..5a2b8b3 100644
--- a/fineract-provider/build.gradle
+++ b/fineract-provider/build.gradle
@@ -193,17 +193,33 @@ if (project.hasProperty('env') && project.getProperty('env') == 'dev') {
 
 /* Enable Oauth2 authentication based on environment, default to HTTP basic auth */
 if (project.hasProperty('security') && project.getProperty('security') == 'oauth') {
-	copy {
-	    from './properties/oauth/'
-	    into 'src/main/resources/'
-	    include '*.properties'
-	}
+    if(project.hasProperty('twofactor') && project.getProperty('twofactor') == 'enabled') {
+        copy {
+            from './properties/oauth/twofactor/'
+            into 'src/main/resources/'
+            include '*.properties'
+        }
+    } else {
+        copy {
+            from './properties/oauth/'
+            into 'src/main/resources/'
+            include '*.properties'
+        }
+    }
 }  else {
-	copy {
-	    from './properties/basicauth/'
-	    into 'src/main/resources/'
-	    include '*.properties'
-	}
+    if(project.hasProperty('twofactor') && project.getProperty('twofactor') == 'enabled') {
+        copy {
+            from './properties/basicauth/twofactor/'
+            into 'src/main/resources/'
+            include '*.properties'
+        }
+    } else {
+        copy {
+            from './properties/basicauth/'
+            into 'src/main/resources/'
+            include '*.properties'
+        }
+    }
 }
 
 task dist(type:Zip){

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/properties/basicauth/twofactor/application.properties
----------------------------------------------------------------------
diff --git a/fineract-provider/properties/basicauth/twofactor/application.properties b/fineract-provider/properties/basicauth/twofactor/application.properties
new file mode 100644
index 0000000..5b4d496
--- /dev/null
+++ b/fineract-provider/properties/basicauth/twofactor/application.properties
@@ -0,0 +1,21 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+spring.profiles.default=basicauth
+spring.profiles.active=basicauth,twofactor
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/properties/oauth/twofactor/application.properties
----------------------------------------------------------------------
diff --git a/fineract-provider/properties/oauth/twofactor/application.properties b/fineract-provider/properties/oauth/twofactor/application.properties
new file mode 100644
index 0000000..12cbfbe
--- /dev/null
+++ b/fineract-provider/properties/oauth/twofactor/application.properties
@@ -0,0 +1,21 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+spring.profiles.default=basicauth
+spring.profiles.active=oauth,twofactor
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index 0ad3612..93bd160 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -2929,7 +2929,7 @@ public class CommandWrapperBuilder {
         this.href = "/smscampaigns/"+resourceId;
         return this;
     }
-    
+
     public CommandWrapperBuilder holdAmount(final Long accountId) {
         this.actionName = "HOLDAMOUNT";
         this.entityName = "SAVINGSACCOUNT";
@@ -3041,4 +3041,18 @@ public class CommandWrapperBuilder {
         this.json = "{}";
         return this;
     }
+
+    public CommandWrapperBuilder invalidateTwoFactorAccessToken() {
+        this.actionName = "INVALIDATE";
+        this.entityName = "TWOFACTOR_ACCESSTOKEN";
+        this.href = "/twofactor/invalidate";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateTwoFactorConfiguration() {
+        this.actionName = "UPDATE";
+        this.entityName = "TWOFACTOR_CONFIGURATION";
+        this.href = "/twofactor/configure";
+        return this;
+    }
 }

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
index 4abbb4b..f2eab64 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
@@ -85,5 +85,12 @@ public interface ConfigurationDomainService {
     Long getDailyTPTLimit();
 
     void removeGlobalConfigurationPropertyDataFromCache(String propertyName);
-    
+
+    boolean isSMSOTPDeliveryEnabled();
+
+    boolean isEmailOTPDeliveryEnabled();
+
+    Integer retrieveOTPCharacterLength();
+
+    Integer retrieveOTPLiveTime();
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
index 95170f8..3d51ddd 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
@@ -287,6 +287,43 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
         configurations.remove(key);
     }
 
+    @Override
+    public boolean isSMSOTPDeliveryEnabled() {
+        final String propertyName = "use-sms-for-2fa";
+        final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName);
+        return property.isEnabled();
+    }
+
+    @Override
+    public boolean isEmailOTPDeliveryEnabled() {
+        final String propertyName = "use-email-for-2fa";
+        final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName);
+        return property.isEnabled();
+    }
+
+    @Override
+    public Integer retrieveOTPCharacterLength() {
+        final String propertyName = "otp-character-length";
+        final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName);
+        int defaultValue = 6;
+        int value = property.getValue().intValue();
+        if(value < 1)
+            return defaultValue;
+        return value;
+    }
+
+    @Override
+    public Integer retrieveOTPLiveTime() {
+        final String propertyName = "otp-validity-period";
+        final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName);
+        int defaultValue = 300;
+        int value = property.getValue().intValue();
+        if(value < 1) {
+            return defaultValue;
+        }
+        return value;
+    }
+
     private GlobalConfigurationPropertyData getGlobalConfigurationPropertyData(final String propertyName) {
         String identifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier();
         String key = identifier + "_" + propertyName;

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java
index 7823a0d..dd8fc20 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java
@@ -40,7 +40,8 @@ import org.springframework.context.annotation.PropertySource;
  * and MariaDB4j (because those differ in the subclasses).
  */
 @Configuration
-@Import({ WebXmlConfiguration.class, WebXmlOauthConfiguration.class, WebFrontEndConfiguration.class })
+@Import({ WebXmlConfiguration.class, WebXmlOauthConfiguration.class, WebFrontEndConfiguration.class,
+		WebTwoFactorXmlConfiguration.class })
 @ImportResource({ "classpath*:META-INF/spring/appContext.xml" })
 @PropertySource(value="classpath:META-INF/spring/jdbc.properties")
 @EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class,

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebTwoFactorXmlConfiguration.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebTwoFactorXmlConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebTwoFactorXmlConfiguration.java
new file mode 100644
index 0000000..e8f1688
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebTwoFactorXmlConfiguration.java
@@ -0,0 +1,36 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.boot;
+
+import org.apache.fineract.infrastructure.security.filter.TwoFactorAuthenticationFilter;
+import org.springframework.boot.context.embedded.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+
+@Configuration
+public class WebTwoFactorXmlConfiguration {
+
+    @Bean
+    public FilterRegistrationBean twoFactorFilterBean(TwoFactorAuthenticationFilter filter) {
+        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
+        registrationBean.setEnabled(false);
+        return registrationBean;
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/AuthenticationApiResource.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/AuthenticationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/AuthenticationApiResource.java
index 1638359..e5c20ed 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/AuthenticationApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/AuthenticationApiResource.java
@@ -30,8 +30,10 @@ import javax.ws.rs.core.MediaType;
 
 import org.apache.fineract.infrastructure.core.data.EnumOptionData;
 import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.constants.TwoFactorConstants;
 import org.apache.fineract.infrastructure.security.data.AuthenticatedUserData;
 import org.apache.fineract.infrastructure.security.service.SpringSecurityPlatformSecurityContext;
+import org.apache.fineract.infrastructure.security.service.TwoFactorUtils;
 import org.apache.fineract.useradministration.data.RoleData;
 import org.apache.fineract.useradministration.domain.AppUser;
 import org.apache.fineract.useradministration.domain.Role;
@@ -56,15 +58,17 @@ public class AuthenticationApiResource {
     private final DaoAuthenticationProvider customAuthenticationProvider;
     private final ToApiJsonSerializer<AuthenticatedUserData> apiJsonSerializerService;
     private final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext;
+    private final TwoFactorUtils twoFactorUtils;
 
     @Autowired
     public AuthenticationApiResource(
             @Qualifier("customAuthenticationProvider") final DaoAuthenticationProvider customAuthenticationProvider,
             final ToApiJsonSerializer<AuthenticatedUserData> apiJsonSerializerService,
-            final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext) {
+            final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext, TwoFactorUtils twoFactorUtils) {
         this.customAuthenticationProvider = customAuthenticationProvider;
         this.apiJsonSerializerService = apiJsonSerializerService;
         this.springSecurityPlatformSecurityContext = springSecurityPlatformSecurityContext;
+        this.twoFactorUtils = twoFactorUtils;
     }
 
     @POST
@@ -100,12 +104,16 @@ public class AuthenticationApiResource {
 
             final EnumOptionData organisationalRole = principal.organisationalRoleData();
 
+            boolean isTwoFactorRequired = twoFactorUtils.isTwoFactorAuthEnabled() && !
+                    principal.hasSpecificPermissionTo(TwoFactorConstants.BYPASS_TWO_FACTOR_PERMISSION);
             if (this.springSecurityPlatformSecurityContext.doesPasswordHasToBeRenewed(principal)) {
-                authenticatedUserData = new AuthenticatedUserData(username, principal.getId(), new String(base64EncodedAuthenticationKey));
+                authenticatedUserData = new AuthenticatedUserData(username, principal.getId(),
+                        new String(base64EncodedAuthenticationKey), isTwoFactorRequired);
             } else {
 
                 authenticatedUserData = new AuthenticatedUserData(username, officeId, officeName, staffId, staffDisplayName,
-                        organisationalRole, roles, permissions, principal.getId(), new String(base64EncodedAuthenticationKey));
+                        organisationalRole, roles, permissions, principal.getId(),
+                        new String(base64EncodedAuthenticationKey), isTwoFactorRequired);
             }
 
         }

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/TwoFactorApiResource.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/TwoFactorApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/TwoFactorApiResource.java
new file mode 100644
index 0000000..482906a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/TwoFactorApiResource.java
@@ -0,0 +1,132 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.api;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.data.AccessTokenData;
+import org.apache.fineract.infrastructure.security.data.OTPDeliveryMethod;
+import org.apache.fineract.infrastructure.security.data.OTPMetadata;
+import org.apache.fineract.infrastructure.security.data.OTPRequest;
+import org.apache.fineract.infrastructure.security.domain.TFAccessToken;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.infrastructure.security.service.TwoFactorService;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Profile;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/twofactor")
+@Component
+@Profile("twofactor")
+@Scope("singleton")
+public class TwoFactorApiResource {
+
+
+    private final ToApiJsonSerializer<OTPMetadata> otpRequestSerializer;
+    private final ToApiJsonSerializer<OTPDeliveryMethod> otpDeliveryMethodSerializer;
+    private final ToApiJsonSerializer<AccessTokenData> accessTokenSerializer;
+    private final DefaultToApiJsonSerializer<Map<String, Object>> toApiJsonSerializer;
+
+    private final PlatformSecurityContext context;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final TwoFactorService twoFactorService;
+
+
+
+    @Autowired
+    public TwoFactorApiResource(ToApiJsonSerializer<OTPMetadata> otpRequestSerializer,
+                                ToApiJsonSerializer<OTPDeliveryMethod> otpDeliveryMethodSerializer,
+                                ToApiJsonSerializer<AccessTokenData> accessTokenSerializer,
+                                DefaultToApiJsonSerializer<Map<String, Object>> toApiJsonSerializer,
+                                PlatformSecurityContext context,
+                                PortfolioCommandSourceWritePlatformService
+                                            commandsSourceWritePlatformService,
+                                TwoFactorService twoFactorService) {
+        this.otpRequestSerializer = otpRequestSerializer;
+        this.otpDeliveryMethodSerializer = otpDeliveryMethodSerializer;
+        this.accessTokenSerializer = accessTokenSerializer;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.context = context;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.twoFactorService = twoFactorService;
+    }
+
+
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String getOTPDeliveryMethods(@Context final UriInfo uriInfo) {
+        AppUser user = context.authenticatedUser();
+
+        List<OTPDeliveryMethod> otpDeliveryMethods = twoFactorService.getDeliveryMethodsForUser(user);
+        return this.otpDeliveryMethodSerializer.serialize(otpDeliveryMethods);
+    }
+
+    @POST
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String requestToken(@QueryParam("deliveryMethod") final String deliveryMethod,
+                               @QueryParam("extendedToken") @DefaultValue("false") boolean extendedAccessToken,
+                               @Context final UriInfo uriInfo) {
+        final AppUser user = context.authenticatedUser();
+
+        final OTPRequest request = twoFactorService.createNewOTPToken(user, deliveryMethod, extendedAccessToken);
+        return this.otpRequestSerializer.serialize(request.getMetadata());
+    }
+
+    @Path("validate")
+    @POST
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String validate(@QueryParam("token") final String token) {
+        final AppUser user = context.authenticatedUser();
+
+        TFAccessToken accessToken = twoFactorService.createAccessTokenFromOTP(user, token);
+
+        return accessTokenSerializer.serialize(accessToken.toTokenData());
+    }
+
+    @Path("invalidate")
+    @POST
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateConfiguration(final String apiRequestBodyAsJson) {
+        final CommandWrapper commandRequest = new CommandWrapperBuilder()
+                .invalidateTwoFactorAccessToken().withJson(apiRequestBodyAsJson).build();
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.
+                logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/TwoFactorConfigurationApiResource.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/TwoFactorConfigurationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/TwoFactorConfigurationApiResource.java
new file mode 100644
index 0000000..13fd69e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/TwoFactorConfigurationApiResource.java
@@ -0,0 +1,86 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.api;
+
+
+import java.util.Map;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.infrastructure.security.service.TwoFactorConfigurationService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Profile;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/twofactor/configure")
+@Consumes({ MediaType.APPLICATION_JSON })
+@Produces({ MediaType.APPLICATION_JSON })
+@Component
+@Scope("singleton")
+@Profile("twofactor")
+public class TwoFactorConfigurationApiResource {
+
+    private final String resourceNameForPermissions = "TWOFACTOR_CONFIG";
+
+    private final PlatformSecurityContext context;
+    private final TwoFactorConfigurationService configurationService;
+    private final DefaultToApiJsonSerializer<Map<String, Object>> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public TwoFactorConfigurationApiResource(PlatformSecurityContext context,
+             TwoFactorConfigurationService configurationService,
+             DefaultToApiJsonSerializer<Map<String, Object>> toApiJsonSerializer,
+             PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.configurationService = configurationService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+
+    @GET
+    public String retrieveAll() {
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+        Map<String, Object> configurationMap = configurationService.retrieveAll();
+        return toApiJsonSerializer.serialize(configurationMap);
+    }
+
+    @PUT
+    public String updateConfiguration(final String apiRequestBodyAsJson) {
+        final CommandWrapper commandRequest = new CommandWrapperBuilder()
+                .updateTwoFactorConfiguration().withJson(apiRequestBodyAsJson).build();
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.
+                logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/UserDetailsApiResource.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/UserDetailsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/UserDetailsApiResource.java
index 67f0616..d05f589 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/UserDetailsApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/UserDetailsApiResource.java
@@ -30,8 +30,10 @@ import javax.ws.rs.core.MediaType;
 
 import org.apache.fineract.infrastructure.core.data.EnumOptionData;
 import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.constants.TwoFactorConstants;
 import org.apache.fineract.infrastructure.security.data.AuthenticatedOauthUserData;
 import org.apache.fineract.infrastructure.security.service.SpringSecurityPlatformSecurityContext;
+import org.apache.fineract.infrastructure.security.service.TwoFactorUtils;
 import org.apache.fineract.useradministration.data.RoleData;
 import org.apache.fineract.useradministration.domain.AppUser;
 import org.apache.fineract.useradministration.domain.Role;
@@ -56,14 +58,17 @@ public class UserDetailsApiResource {
     private final ResourceServerTokenServices tokenServices;
     private final ToApiJsonSerializer<AuthenticatedOauthUserData> apiJsonSerializerService;
     private final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext;
+    private final TwoFactorUtils twoFactorUtils;
 
     @Autowired
     public UserDetailsApiResource(@Qualifier("tokenServices") final ResourceServerTokenServices tokenServices,
             final ToApiJsonSerializer<AuthenticatedOauthUserData> apiJsonSerializerService,
-            final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext) {
+            final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext,
+            final TwoFactorUtils twoFactorUtils) {
         this.tokenServices = tokenServices;
         this.apiJsonSerializerService = apiJsonSerializerService;
         this.springSecurityPlatformSecurityContext = springSecurityPlatformSecurityContext;
+        this.twoFactorUtils = twoFactorUtils;
     }
 
     @GET
@@ -96,12 +101,16 @@ public class UserDetailsApiResource {
 
             final EnumOptionData organisationalRole = principal.organisationalRoleData();
 
+            final boolean requireTwoFactorAuth = twoFactorUtils.isTwoFactorAuthEnabled()
+                    && !principal.hasSpecificPermissionTo(TwoFactorConstants.BYPASS_TWO_FACTOR_PERMISSION);
             if (this.springSecurityPlatformSecurityContext.doesPasswordHasToBeRenewed(principal)) {
-                authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(), principal.getId(), accessToken);
+                authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(),
+                        principal.getId(), accessToken, requireTwoFactorAuth);
             } else {
 
-                authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(), officeId, officeName, staffId, staffDisplayName,
-                        organisationalRole, roles, permissions, principal.getId(), accessToken);
+                authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(),
+                        officeId, officeName, staffId, staffDisplayName, organisationalRole, roles,
+                        permissions, principal.getId(), accessToken, requireTwoFactorAuth);
             }
             return this.apiJsonSerializerService.serialize(authenticatedUserData);
         }

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/command/InvalidateTFAccessTokenCommandHandler.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/command/InvalidateTFAccessTokenCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/command/InvalidateTFAccessTokenCommandHandler.java
new file mode 100644
index 0000000..1ede412
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/command/InvalidateTFAccessTokenCommandHandler.java
@@ -0,0 +1,108 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.command;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.security.constants.TwoFactorConstants;
+import org.apache.fineract.infrastructure.security.domain.TFAccessToken;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.infrastructure.security.service.TwoFactorService;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+
+@Service
+@CommandType(entity = "TWOFACTOR_ACCESSTOKEN", action = "INVALIDATE")
+@Profile("twofactor")
+public class InvalidateTFAccessTokenCommandHandler implements NewCommandSourceHandler {
+
+
+    private final TwoFactorService twoFactorService;
+    private final PlatformSecurityContext securityContext;
+    private final FromJsonHelper fromJsonHelper;
+
+    @Autowired
+    public InvalidateTFAccessTokenCommandHandler(TwoFactorService twoFactorService,
+                                                 PlatformSecurityContext securityContext,
+                                                 FromJsonHelper fromJsonHelper) {
+        this.twoFactorService = twoFactorService;
+        this.securityContext = securityContext;
+        this.fromJsonHelper = fromJsonHelper;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        validateJson(command.json());
+
+        final AppUser user = securityContext.authenticatedUser();
+
+        final TFAccessToken accessToken = twoFactorService.invalidateAccessToken(user, command);
+
+        return new CommandProcessingResultBuilder()
+                .withCommandId(command.commandId())
+                .withResourceIdAsString(accessToken.getToken())
+                .build();
+    }
+
+    private void validateJson(String json) {
+        if (StringUtils.isBlank(json)) {
+            throw new InvalidJsonException();
+        }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                new HashSet<>(Collections.singletonList("token")));
+        final JsonElement element = this.fromJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(TwoFactorConstants.ACCESSTOKEN_RESOURCE_NAME);
+
+        final String token = this.fromJsonHelper.extractStringNamed("token", element);
+        baseDataValidator.reset().parameter("token").value(token).notNull().notBlank();
+
+        if(!dataValidationErrors.isEmpty()) {
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/command/UpdateTwoFactorConfigCommandHandler.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/command/UpdateTwoFactorConfigCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/command/UpdateTwoFactorConfigCommandHandler.java
new file mode 100644
index 0000000..b8c4e60
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/command/UpdateTwoFactorConfigCommandHandler.java
@@ -0,0 +1,60 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.command;
+
+import java.util.Map;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.security.data.TwoFactorConfigurationValidator;
+import org.apache.fineract.infrastructure.security.service.TwoFactorConfigurationService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "TWOFACTOR_CONFIGURATION", action = "UPDATE")
+@Profile("twofactor")
+public class UpdateTwoFactorConfigCommandHandler implements NewCommandSourceHandler {
+
+    private final TwoFactorConfigurationService configurationService;
+    private final TwoFactorConfigurationValidator dataValidator;
+
+    @Autowired
+    public UpdateTwoFactorConfigCommandHandler(TwoFactorConfigurationService configurationService,
+                                               TwoFactorConfigurationValidator dataValidator) {
+        this.configurationService = configurationService;
+        this.dataValidator = dataValidator;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        this.dataValidator.validateForUpdate(command.json());
+        final Map<String, Object> changes = configurationService.update(command);
+        return new CommandProcessingResultBuilder()
+                .withCommandId(command.commandId())
+                .with(changes)
+                .build();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/constants/TwoFactorConfigurationConstants.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/constants/TwoFactorConfigurationConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/constants/TwoFactorConfigurationConstants.java
new file mode 100644
index 0000000..22818de
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/constants/TwoFactorConfigurationConstants.java
@@ -0,0 +1,59 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.constants;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class TwoFactorConfigurationConstants {
+
+    public static final String RESOURCE_NAME = "TWOFACTOR_CONFIGURATION";
+
+    public static final String ENABLE_EMAIL_DELIVERY = "otp-delivery-email-enable";
+    public static final String EMAIL_SUBJECT = "otp-delivery-email-subject";
+    public static final String EMAIL_BODY = "otp-delivery-email-body";
+
+    public static final String ENABLE_SMS_DELIVERY = "otp-delivery-sms-enable";
+    public static final String SMS_PROVIDER_ID = "otp-delivery-sms-provider";
+    public static final String SMS_MESSAGE_TEXT = "otp-delivery-sms-text";
+
+    public static final String OTP_TOKEN_LIVE_TIME = "otp-token-live-time";
+    public static final String OTP_TOKEN_LENGTH = "otp-token-length";
+
+    public static final String ACCESS_TOKEN_LIVE_TIME = "access-token-live-time";
+    public static final String ACCESS_TOKEN_LIVE_TIME_EXTENDED = "access-token-live-time-extended";
+
+    public static final Set<String> REQUEST_DATA_PARAMETERS =
+            new HashSet<>(Arrays.asList(ENABLE_EMAIL_DELIVERY, EMAIL_SUBJECT, EMAIL_BODY,
+                    ENABLE_SMS_DELIVERY, SMS_PROVIDER_ID, SMS_MESSAGE_TEXT, OTP_TOKEN_LIVE_TIME,
+                    OTP_TOKEN_LENGTH, ACCESS_TOKEN_LIVE_TIME, ACCESS_TOKEN_LIVE_TIME_EXTENDED));
+
+    public static final List<String> STRING_PARAMETERS =
+            Arrays.asList(EMAIL_SUBJECT, EMAIL_BODY, SMS_MESSAGE_TEXT);
+
+    public static final List<String> BOOLEAN_PARAMETERS =
+            Arrays.asList(ENABLE_EMAIL_DELIVERY, ENABLE_SMS_DELIVERY);
+
+    public static final List<String> NUMBER_PARAMETERS =
+            Arrays.asList(SMS_PROVIDER_ID, OTP_TOKEN_LIVE_TIME, OTP_TOKEN_LENGTH,
+                    ACCESS_TOKEN_LIVE_TIME, ACCESS_TOKEN_LIVE_TIME_EXTENDED);
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/constants/TwoFactorConstants.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/constants/TwoFactorConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/constants/TwoFactorConstants.java
new file mode 100644
index 0000000..7997c39
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/constants/TwoFactorConstants.java
@@ -0,0 +1,30 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.constants;
+
+public class TwoFactorConstants {
+
+    public static final String ACCESSTOKEN_RESOURCE_NAME = "TWOFACTOR_ACCESSTOKEN";
+
+    public static final String SMS_DELIVERY_METHOD_NAME = "sms";
+    public static final String EMAIL_DELIVERY_METHOD_NAME = "email";
+
+    public static final String BYPASS_TWO_FACTOR_PERMISSION = "BYPASS_TWOFACTOR";
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AccessTokenData.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AccessTokenData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AccessTokenData.java
new file mode 100644
index 0000000..ebd27f7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AccessTokenData.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.data;
+
+import org.joda.time.DateTime;
+
+public class AccessTokenData {
+
+    private final String token;
+
+    private final DateTime validFrom;
+    private final DateTime validTo;
+
+    public AccessTokenData(String token, DateTime validFrom, DateTime validTo) {
+        this.token = token;
+        this.validFrom = validFrom;
+        this.validTo = validTo;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public DateTime getValidFrom() {
+        return validFrom;
+    }
+
+    public DateTime getValidTo() {
+        return validTo;
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedOauthUserData.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedOauthUserData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedOauthUserData.java
index 830fc67..116daa8 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedOauthUserData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedOauthUserData.java
@@ -54,6 +54,9 @@ public class AuthenticatedOauthUserData {
     @SuppressWarnings("unused")
     private final boolean shouldRenewPassword;
 
+    @SuppressWarnings("unused")
+    private final boolean isTwoFactorAuthenticationRequired;
+
     public AuthenticatedOauthUserData(final String username, final Collection<String> permissions) {
         this.username = username;
         this.userId = null;
@@ -67,11 +70,13 @@ public class AuthenticatedOauthUserData {
         this.roles = null;
         this.permissions = permissions;
         this.shouldRenewPassword = false;
+        this.isTwoFactorAuthenticationRequired = false;
     }
 
     public AuthenticatedOauthUserData(final String username, final Long officeId, final String officeName, final Long staffId,
             final String staffDisplayName, final EnumOptionData organisationalRole, final Collection<RoleData> roles,
-            final Collection<String> permissions, final Long userId, final String accessToken) {
+            final Collection<String> permissions, final Long userId, final String accessToken,
+            final boolean isTwoFactorAuthenticationRequired) {
         this.username = username;
         this.officeId = officeId;
         this.officeName = officeName;
@@ -84,9 +89,11 @@ public class AuthenticatedOauthUserData {
         this.roles = roles;
         this.permissions = permissions;
         this.shouldRenewPassword = false;
+        this.isTwoFactorAuthenticationRequired = isTwoFactorAuthenticationRequired;
     }
 
-    public AuthenticatedOauthUserData(final String username, final Long userId, final String accessToken) {
+    public AuthenticatedOauthUserData(final String username, final Long userId, final String accessToken,
+            final boolean isTwoFactorAuthenticationRequired) {
         this.username = username;
         this.officeId = null;
         this.officeName = null;
@@ -99,5 +106,6 @@ public class AuthenticatedOauthUserData {
         this.roles = null;
         this.permissions = null;
         this.shouldRenewPassword = true;
+        this.isTwoFactorAuthenticationRequired = isTwoFactorAuthenticationRequired;
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedUserData.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedUserData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedUserData.java
index 513f5df..c4713fc 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedUserData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedUserData.java
@@ -54,6 +54,9 @@ public class AuthenticatedUserData {
     @SuppressWarnings("unused")
     private final boolean shouldRenewPassword;
 
+    @SuppressWarnings("unused")
+    private final boolean isTwoFactorAuthenticationRequired;
+
     public AuthenticatedUserData(final String username, final Collection<String> permissions) {
         this.username = username;
         this.userId = null;
@@ -67,11 +70,13 @@ public class AuthenticatedUserData {
         this.roles = null;
         this.permissions = permissions;
         this.shouldRenewPassword = false;
+        this.isTwoFactorAuthenticationRequired = false;
     }
 
     public AuthenticatedUserData(final String username, final Long officeId, final String officeName, final Long staffId,
             final String staffDisplayName, final EnumOptionData organisationalRole, final Collection<RoleData> roles,
-            final Collection<String> permissions, final Long userId, final String base64EncodedAuthenticationKey) {
+            final Collection<String> permissions, final Long userId, final String base64EncodedAuthenticationKey,
+            final boolean isTwoFactorAuthenticationRequired) {
         this.username = username;
         this.officeId = officeId;
         this.officeName = officeName;
@@ -84,9 +89,11 @@ public class AuthenticatedUserData {
         this.roles = roles;
         this.permissions = permissions;
         this.shouldRenewPassword = false;
+        this.isTwoFactorAuthenticationRequired = isTwoFactorAuthenticationRequired;
     }
 
-    public AuthenticatedUserData(final String username, final Long userId, final String base64EncodedAuthenticationKey) {
+    public AuthenticatedUserData(final String username, final Long userId, final String base64EncodedAuthenticationKey,
+             final boolean isTwoFactorAuthenticationRequired) {
         this.username = username;
         this.officeId = null;
         this.officeName = null;
@@ -99,5 +106,6 @@ public class AuthenticatedUserData {
         this.roles = null;
         this.permissions = null;
         this.shouldRenewPassword = true;
+        this.isTwoFactorAuthenticationRequired = isTwoFactorAuthenticationRequired;
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPDeliveryMethod.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPDeliveryMethod.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPDeliveryMethod.java
new file mode 100644
index 0000000..f05c7ae
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPDeliveryMethod.java
@@ -0,0 +1,38 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.data;
+
+public class OTPDeliveryMethod {
+
+    private final String name;
+    private final String target;
+
+    public OTPDeliveryMethod(String name, String target) {
+        this.name = name;
+        this.target = target;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getTarget() {
+        return target;
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPMetadata.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPMetadata.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPMetadata.java
new file mode 100644
index 0000000..0365fff
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPMetadata.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.data;
+
+import org.joda.time.DateTime;
+
+public class OTPMetadata {
+
+    private final DateTime requestTime;
+    private final int tokenLiveTimeInSec;
+    private final boolean extendedAccessToken;
+    private final OTPDeliveryMethod deliveryMethod;
+
+    public OTPMetadata(DateTime requestTime, int tokenLiveTimeInSec,
+                       boolean extendedAccessToken, OTPDeliveryMethod deliveryMethod) {
+        this.requestTime = requestTime;
+        this.tokenLiveTimeInSec = tokenLiveTimeInSec;
+        this.extendedAccessToken = extendedAccessToken;
+        this.deliveryMethod = deliveryMethod;
+    }
+
+    public DateTime getRequestTime() {
+        return requestTime;
+    }
+
+    public int getTokenLiveTimeInSec() {
+        return tokenLiveTimeInSec;
+    }
+
+    public boolean isExtendedAccessToken() {
+        return extendedAccessToken;
+    }
+
+    public OTPDeliveryMethod getDeliveryMethod() {
+        return deliveryMethod;
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPRequest.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPRequest.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPRequest.java
new file mode 100644
index 0000000..f9f3e24
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPRequest.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.data;
+
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.joda.time.DateTime;
+
+public class OTPRequest {
+
+    private final String token;
+    private final OTPMetadata metadata;
+
+    public OTPRequest(String token, OTPMetadata metadata) {
+        this.token = token;
+        this.metadata = metadata;
+    }
+
+    public static OTPRequest create(String token, int tokenLiveTimeInSec, boolean extendedAccessToken,
+                                    OTPDeliveryMethod deliveryMethod) {
+        final OTPMetadata metadata = new OTPMetadata(DateUtils.getLocalDateTimeOfTenant().toDateTime(),
+                tokenLiveTimeInSec, extendedAccessToken, deliveryMethod);
+        return new OTPRequest(token, metadata);
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public OTPMetadata getMetadata() {
+        return metadata;
+    }
+
+    public boolean isValid() {
+        DateTime expireTime = metadata.getRequestTime().plusSeconds(metadata.getTokenLiveTimeInSec());
+        return DateTime.now().isBefore(expireTime);
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/TwoFactorConfigurationValidator.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/TwoFactorConfigurationValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/TwoFactorConfigurationValidator.java
new file mode 100644
index 0000000..41cf69b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/TwoFactorConfigurationValidator.java
@@ -0,0 +1,120 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.data;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.security.constants.TwoFactorConfigurationConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+@Profile("twofactor")
+public class TwoFactorConfigurationValidator {
+
+    private final FromJsonHelper fromJsonHelper;
+
+    @Autowired
+    public TwoFactorConfigurationValidator(FromJsonHelper fromJsonHelper) {
+        this.fromJsonHelper = fromJsonHelper;
+    }
+
+    public void validateForUpdate(final String json) {
+        if (StringUtils.isBlank(json)) {
+            throw new InvalidJsonException();
+        }
+
+        boolean atLeastOneParameterPassedForUpdate = false;
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                TwoFactorConfigurationConstants.REQUEST_DATA_PARAMETERS);
+        final JsonElement element = this.fromJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(TwoFactorConfigurationConstants.RESOURCE_NAME);
+
+
+        for(String parameterName : TwoFactorConfigurationConstants.BOOLEAN_PARAMETERS) {
+            if(this.fromJsonHelper.parameterExists(parameterName, element)) {
+                atLeastOneParameterPassedForUpdate = true;
+                validateBooleanParameter(parameterName, element, baseDataValidator);
+            }
+        }
+
+        for(String parameterName : TwoFactorConfigurationConstants.STRING_PARAMETERS) {
+            if(this.fromJsonHelper.parameterExists(parameterName, element)) {
+                atLeastOneParameterPassedForUpdate = true;
+                validateStringParameter(parameterName, element, baseDataValidator);
+            }
+        }
+
+        for(String parameterName : TwoFactorConfigurationConstants.NUMBER_PARAMETERS) {
+            if (this.fromJsonHelper.parameterExists(parameterName, element)) {
+                atLeastOneParameterPassedForUpdate = true;
+                validateNumberParameter(parameterName, element, baseDataValidator);
+            }
+        }
+
+        if(!atLeastOneParameterPassedForUpdate) {
+            final Object forceError = null;
+            baseDataValidator.reset().anyOfNotNull(forceError);
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(
+            final List<ApiParameterError> dataValidationErrors) {
+        if(!dataValidationErrors.isEmpty()) {
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+    }
+
+    private void validateBooleanParameter(final String name, final JsonElement element,
+                  final DataValidatorBuilder baseDataValidator) {
+        final String value = this.fromJsonHelper.extractStringNamed(name, element);
+        baseDataValidator.reset().parameter(name).value(value).notNull().trueOrFalseRequired(value);
+    }
+
+    private void validateStringParameter(final String name, final JsonElement element,
+                                          final DataValidatorBuilder baseDataValidator) {
+        final String value = this.fromJsonHelper.extractStringNamed(name, element);
+        baseDataValidator.reset().parameter(name).value(value).notBlank().notExceedingLengthOf(1000);
+    }
+
+    private void validateNumberParameter(final String name, final JsonElement element,
+                                         final DataValidatorBuilder baseDataValidator) {
+        final Integer value = this.fromJsonHelper.extractIntegerSansLocaleNamed(name, element);
+        baseDataValidator.reset().parameter(name).value(value).notNull().integerGreaterThanZero();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/OTPRequestRepository.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/OTPRequestRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/OTPRequestRepository.java
new file mode 100644
index 0000000..fc11300
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/OTPRequestRepository.java
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.domain;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.fineract.infrastructure.security.data.OTPRequest;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Repository;
+import org.springframework.util.Assert;
+
+@Repository
+@Profile("twofactor")
+public class OTPRequestRepository {
+
+    private final ConcurrentHashMap<Long, OTPRequest> OTPrequests = new ConcurrentHashMap<>();
+
+
+    public OTPRequest getOTPRequestForUser(AppUser user) {
+        Assert.notNull(user);
+
+        return this.OTPrequests.get(user.getId());
+    }
+
+    public void addOTPRequest(AppUser user, OTPRequest request) {
+        Assert.notNull(user);
+        Assert.notNull(request);
+        this.OTPrequests.put(user.getId(), request);
+    }
+
+    public void deleteOTPRequestForUser(AppUser user) {
+        this.OTPrequests.remove(user.getId());
+    }
+}