{"id":595,"date":"2018-11-05T10:42:03","date_gmt":"2018-11-05T15:42:03","guid":{"rendered":"http:\/\/salzlechner.com\/dev\/?p=595"},"modified":"2018-11-05T11:35:44","modified_gmt":"2018-11-05T16:35:44","slug":"webapp-security-series-two-factor-authentication","status":"publish","type":"post","link":"http:\/\/salzlechner.com\/dev\/2018\/11\/05\/webapp-security-series-two-factor-authentication\/","title":{"rendered":"WebApp Security Series \u2013 Two factor Authentication"},"content":{"rendered":"<p>Our second article in this series is about Two Factor Authentication. Two Factor Authentication or Multi Factor Authentication (2FA or MFA) require the user to supply multiple forms of authentication something the user &#8216;knows&#8217; and something the user is in possession of or something the user is.<\/p>\n<p>For example an ATM withdrawal consists of the passcode (something the user knows) and the ATM cing the user possesses).ard (someth<\/p>\n<p>In case of a login the simplest form of 2FA would be the password (something the user knows) and a code sent to the users email or phone (something the user possesses).<\/p>\n<p>In a multi factor authentication other options come into play such as bio metrics (the users fingerprint, face id, voice id, etc).<\/p>\n<p>We are going to look into adding simple two factor authentication to a DataFlex WebApp.<\/p>\n<p>A standard DataFlex WebApp currently has a simple login screen that authenticates the user and creates an active session. The system then allows the user to use the application.<\/p>\n<p>In order to support 2FA we will need to modify the login logic. In a 2FA system we will first validate the first part of the authentication which is the password.<\/p>\n<p>Once validated we will need to create a random token and communicate that token to the user and the user will then need to enter that token to actually gain access to the application<\/p>\n<p>First lets dive a bit deeper into how the login works in a DataFlex WebApp<\/p>\n<p>The <em>UserLogin<\/em> method of the session handler validates the username and password combination and then updates a record in the WebAppSession table to reflect the login.<\/p>\n<p>Our first step is to add a second screen for the confirmation of the Two Factor Authentication code.\u00a0 This screen is very similar to the login screen.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-597\" src=\"http:\/\/salzlechner.com\/dev\/wp-content\/uploads\/sites\/2\/2018\/11\/2fascreen.png\" alt=\"\" width=\"246\" height=\"264\" srcset=\"http:\/\/salzlechner.com\/dev\/wp-content\/uploads\/sites\/2\/2018\/11\/2fascreen.png 400w, http:\/\/salzlechner.com\/dev\/wp-content\/uploads\/sites\/2\/2018\/11\/2fascreen-279x300.png 279w\" sizes=\"(max-width: 246px) 100vw, 246px\" \/><\/p>\n<p>We also need to create a code and send the code to the user. Because of the way web applications work we cannot simply store this in memory. The data in memory would not be available on the next call from the client<\/p>\n<p>To handle the 2FA codes we will create a database table as follows<\/p>\n<p>Name: WEBAPP2FA<br \/>\nColumns:\u00a0SESSIONKEY ASCII 36, CREATEDDATE DATE, CREATEDTIME ASCII 8, CODE ASCII 20<br \/>\nIndex 1: SESSIONKEY, CREATEDDATE, CREATEDTIME<\/p>\n<p>now we also need a method to create and send the code. We will add the following method to the oWebApp object<\/p>\n<pre class=\"lang:default decode:true \">\/\/\r\nFunction RequestSendTwoFactorAuthCode String sToEmail Returns Boolean\r\n    Open WebAppSession\r\n    Open WEBAPP2FA\r\n        \r\n    \/\/ fail if we do not have a session\r\n    If not status WebAppSession Function_Return (False)\r\n                \r\n    DateTime dtCurrentDateTime        \r\n    Move (CurrentDateTime()) to dtCurrentDateTime\r\n    \r\n    Clear WEBAPP2FA\r\n    Move WebAppSession.SessionKey       to WEBAPP2FA.SessionKey\r\n    Get TimeToString dtCurrentDateTime  to WEBAPP2FA.CreateTime\r\n    Move dtCurrentDateTime              to WEBAPP2FA.CreateDate\r\n        \r\n    Integer iRand        \r\n    Move (random(999998)+1) to iRand\r\n    Move (Trim(iRand)) to WEBAPP2FA.CODE\r\n        \r\n    SaveRecord WEBAPP2FA\r\n                        \r\n    Send DoSendTwoFactorAuthEmail sToEmail WEBAPP2FA.CODE\r\n                                         \r\n    Function_Return (True)\r\nEnd_Function\r\n<\/pre>\n<p>The\u00a0<em>RequestSendTwoFactorAuthCode<\/em> method creates a simple random code and then sends the code via email to the user.<br \/>\nThe code to actually send the email is left out. Simply use whatever method you are using to send email. You can also send text messages or use any other method to send the code to the end user.<br \/>\nFor testing purposes of course you can simply get the code from the database table as well.<\/p>\n<p>We can now modify the <em>DoLogin<\/em> method in the login view to call this function and then show the TwoFactorAuthentication view.<\/p>\n<pre class=\"lang:default decode:true\">\/\/    \r\nProcedure DoLogin\r\n    String sLoginName sPassword\r\n    Boolean bResult\r\n    Handle hoDefaultView\r\n        \r\n    WebGet psValue of oLoginName to sLoginName\r\n    WebGet psValue of oPassword to sPassword\r\n        \r\n    Get UserLogin of ghoWebSessionManager sLoginName sPassword to bResult\r\n        \r\n    If (bResult) Begin\r\n        \/\/ login successful\r\n        Send Hide of oLoginDialog\r\n\r\n        \/\/ clear the login values. we don't want to return the login id &amp; password as synchronized properties....\r\n        WebSet psValue of oLoginName to \"\"\r\n        WebSet psValue of oPassword  to \"\"\r\n        WebSet pbVisible of oWarning to False\r\n\r\n        \/\/ now send and validate 2FA code\r\n        String sEmail\r\n\r\n        \/\/ get logged in users email here\r\n                    \r\n        Boolean bOk\r\n        Get RequestSendTwoFactorAuthCode of oWebApp sEmail to bOk\r\n        If (bOk) Begin\r\n            \/\/ show Two Factor Auth View\r\n            Send Show to oTwoFactorAuthView               \r\n        End\r\n        Else Begin                \r\n            WebSet pbVisible of oWarning to True\r\n        End\r\n    End\r\n    Else Begin\r\n        WebSet pbVisible of oWarning to True\r\n    End\r\nEnd_Procedure<\/pre>\n<p>The new login method works a bit different now. It first validates the login credentials.<br \/>\nThen instead of showing the default view we are sending a two factor authentication code to the user and redirect the user to the Two Factor Authentication validation page.<\/p>\n<p>The user will now have to wait to receive the Two Factor Authentication code to enter it and proceed with the login.<\/p>\n<p>But &#8230; here is a problem. If we enter the credentials and then when the Two Factor Authentication page loads we will simply go back to the apps main url DataFlex WebApp thinks we are logged in already and simply skip the Two Factor Authentication.<\/p>\n<p>That is obviously not what we want. We need to teach DataFlex WebApp about the different statuses of a session<\/p>\n<ul>\n<li>Session Login Validated but Two Factor Authentication missing<\/li>\n<li>Session Login Validated and Two Factor Authentication validated<\/li>\n<\/ul>\n<p>to handle this we will add a field to the WebAppSession table. The fields name is\u00a0MFASTATUS and it is a single character ASCII field<\/p>\n<p>After a successful login and sending of the Two Factor Authentication key we will set the value of this field to &#8216;W&#8217; for Waiting Authentication.<\/p>\n<p>Once the user authenticates with the Two Factor Authentication code we will clear the value in this field. This will then allow us to prevent the user from skipping the Two Factor Authentication<\/p>\n<p>The design of the login code in DataFlex WebApp is not very developer friendly and doesnt provide hooks in areas were they would allow us to add functionality like this.<\/p>\n<p>Because of this we have to add the following code to the SessionManager object in the WebApplication which is a copy of DAWs original login code plus the additional code to initialize the database fields for Two Factor Authentication<\/p>\n<pre class=\"lang:default decode:true\">\/\/\r\nFunction UserLogin String sLoginName String sPassword Returns Boolean\r\n    String sSessionKey sUserPassword\r\n    Handle hoSessionDD hoUserDD\r\n    Boolean bMatch  \r\n        \r\n    Get phoSessionDD to hoSessionDD\r\n    Get phoUserDD to hoUserDD\r\n    Integer iErr eLoginMode\r\n        \r\n    \/\/ Refind session record\r\n    Get psSessionKey to sSessionKey\r\n    Send Clear of hoSessionDD\r\n    Move sSessionKey to WebAppSession.SessionKey\r\n    Send Find of hoSessionDD EQ Index.1\r\n        \r\n    If (Found and WebAppSession.SessionKey = sSessionKey) Begin\r\n        Get peLoginMode to eLoginMode\r\n            \r\n        \/\/  Find the user\r\n        Move sLoginName to WebAppUser.LoginName\r\n        Send Find of hoUserDD EQ Index.1\r\n            \r\n        \/\/ Check username and password\r\n        If (Found and (Lowercase(sLoginName) = Lowercase(Trim(WebAppUser.LoginName)))) Begin\r\n            Get Field_Current_Value of hoUserDD Field WebAppUser.Password to sUserPassword\r\n            Get ComparePasswords (Trim(sUserPassword)) (Trim(sPassword)) to bMatch\r\n               \r\n            If (bMatch) Begin\r\n                \/\/ Store the login\r\n                Set Field_Changed_Value of hoUserDD Field WebAppUser.LastLogin to (CurrentDateTime())\r\n                Get Request_Validate of hoSessionDD to iErr\r\n                If (iErr) Begin\r\n                    \/\/ this should not happen. If it does its a programming error\r\n                    Error DFERR_PROGRAM C_$WebAppSesionValidateFailed\r\n                    Function_Return False\r\n                End\r\n                    \r\n                \/\/ Require Two Factor Authentication\r\n                Set Field_Changed_Value of hoSessionDD Field WebAppSession.MFASTATUS to \"W\"\r\n                    \r\n                Send Request_Save of hoSessionDD\r\n                    \r\n                \/\/ Update session properties\r\n                Send UpdateSessionProperties False\r\n                Send NotifyChangeRights\r\n                Function_Return True\r\n            End\r\n            Else Begin\r\n                \/\/  We should rely directly on this buffer elsewhere but just be sure\r\n                Send Clear of hoUserDD\r\n            End\r\n        End\r\n    End\r\n       \r\n    Function_Return False\r\nEnd_Function\r\n<\/pre>\n<p>the code above will handle the login and if successful set the WebAppSessions MFASTATUS field to &#8216;W&#8217;.<\/p>\n<p>We will also need to modify the LoadWebApp method to ensure it will send the user to the Two Factor Authentication page when needed.<\/p>\n<pre class=\"lang:default decode:true \">Function LoadWebApp Boolean bOptLoadInitial String sClientLanguage Integer iClientTimezoneOffset Returns String\r\n    String sStartupView sStateHash\r\n    Integer eLoginMode\r\n    Boolean bIsLoggedIn bAllowAccess bOverrideState bLoadInitial\r\n    tWebObjectDef[] aObjectDefs\r\n    tWebObjectDef ObjectDef\r\n    Handle hoView hoDefaultView\r\n    Handle[] aDDOViews\r\n    tWebNameValue[] aTranslations\r\n    tWebValueTree tData\r\n    String[] aParams\r\n    \r\n    \/\/  bLoadInitial is optional to maintain compatibility with 18.1, we don't want to throw errors before the version check\r\n    If (num_arguments &gt;= 1) Begin\r\n        Move bOptLoadInitial to bLoadInitial\r\n    End\r\n    Else Begin\r\n        Move True to bLoadInitial\r\n    End\r\n    \r\n    \/\/  Trigger event allowing application to initialize things based on the client locales\r\n    Send OnInitializeLocales sClientLanguage iClientTimezoneOffset\r\n    \r\n    \/\/  Load translations\r\n    Get ClientTranslations to aTranslations\r\n    ValueTreeSerializeParameter aTranslations to tData\r\n    Send ClientAction \"initTranslations\" aParams tData\r\n    \r\n    \/\/  Serialize Global Objects\r\n    Get SerializeObject to ObjectDef\r\n    Get paObjectDef to aObjectDefs\r\n    Move ObjectDef to aObjectDefs[SizeOfArray(aObjectDefs)]\r\n    Set paObjectDef to aObjectDefs\r\n    \r\n    \/\/  Determine if loginview needs to be shown\r\n    Get peLoginMode to eLoginMode\r\n    \r\n    If (eLoginMode=lmLoginRequired) Begin\r\n        \/\/ Test for login....\r\n        Get IsLoggedIn of ghoWebSessionManager to bIsLoggedIn\r\n        \r\n        If (not(bIsLoggedIn)) Begin\r\n            \/\/ Send back the login view as the first view to load\r\n            If (bLoadInitial) Begin\r\n                Get GetLoginView to hoView\r\n            End\r\n            \/\/ If hoView is 0, this is probably a progamming error. No way to log in and it is required.\r\n            \/\/ For now do nothing which will load the default view\r\n        End\r\n    End\r\n\r\n    \/\/ reroute user to Two Factor Auth Page when needed\r\n    Declare_Datafile WebAppSession        \r\n    If (WebAppSession.MFASTATUS=\"W\") Begin\r\n        Move (oTwofactorAuthView(Self)) to hoView\r\n    End        \r\n\r\n    \/\/  Set initial page title (a login dialog might not set one)\r\n    Send UpdatePageTitle C_WebUnresolvedObject \"\"\r\n    \r\n    \/\/  Send back default view unless a login dialog needs to be sent\r\n    If (hoView = 0) Begin\r\n        \/\/  Only load the initial view when needed\r\n        If (bLoadInitial and SizeOfArray(aObjectDefs) &lt;= 1) Begin\r\n            \/\/  Restore application state\r\n            If (peApplicationStateMode(Self) &lt;&gt; asmOff) Begin\r\n                Get StateHash to sStateHash \r\n                Send RestoreState sStateHash True\r\n            End\r\n            Else Begin \/\/  Or return default view\r\n                Get GetDefaultView to hoView\r\n                If (hoView &gt; 0) Begin\r\n                    Send Show of hoView\r\n                End\r\n            End\r\n        End\r\n    End\r\n    Else Begin\r\n        Send Show of hoView\r\n\r\n    End\r\n     \r\n    Function_Return \"\"\r\nEnd_Function<\/pre>\n<p>In the Two Factor Authentication view we will allow the user to type in a code and then verify it against our database<\/p>\n<pre class=\"lang:default decode:true \">Procedure DoConfirmCode\r\n    DateTime dtCurrentDateTime\r\n    \r\n    Move (CurrentDateTime()) to dtCurrentDateTime\r\n\r\n    Clear WEBAPP2FA\r\n    Move WebAppSession.SessionKey to WEBAPP2FA.SESSIONKEY\r\n    String sTimeNow\r\n    Get TimeToString of oWebApp dtCurrentDateTime to sTimeNow\r\n    Move dtCurrentDateTime to WEBAPP2FA.CREATEDATE\r\n    Move sTimeNow to WEBAPP2FA.CreateTime\r\n    Find LE WEBAPP2FA by Index.1\r\n    If (WEBAPP2FA.SESSIONKEY&lt;&gt;WebAppSession.SessionKey) Move False to Found\r\n    If (WEBAPP2FA.CREATEDATE&gt;Date(dtCurrentDateTime)) Move False to Found\r\n    If (WEBAPP2FA.CREATETIME&gt;sTimeNow) Move False to Found\r\n    If (not(Found)) Begin\r\n        Send ShowInfoBox \"Invalid Code\"\r\n        Procedure_Return\r\n    End\r\n    \r\n    \/\/ check expiration on code\r\n    \r\n    String sCode\r\n    WebGet psValue of oValidationCode to sCode\r\n    If (trim(WEBAPP2FA.CODE)&lt;&gt;Trim(sCode)) Begin\r\n        Send ShowInfoBox \"Code is invalid\"\r\n        Procedure_Return            \r\n    End\r\n        \r\n    Reread WebAppSession\r\n    Move \"\" to WebAppSession.MFASTATUS\r\n    SaveRecord WebAppSession\r\n    Unlock\r\n    \r\n    Integer hoDefaultView\r\n    Send ShowHeader of ghoWebApp \r\n    WebSet psCSSClass of ghoWebApp to \"\"\r\n    Get GetDefaultView to hoDefaultView\r\n    If (hoDefaultView &gt; 0) Begin\r\n        Send Show of hoDefaultView\r\n    End        \r\n        \r\nEnd_Procedure<\/pre>\n<p>Our <em>DoConfirmCode<\/em> procedure, called from the buttons OnClick event does just that. First we find the last 2FA code in our database. If we\u00a0 cannot find an entry we bail out with an error message.<\/p>\n<p>If we can find an entry we validate the code. At this time this code simply uses the last code sent to the user. A better implementation will use expiration time and also allow multiple active codes within the expiration and then also remove any entries that are expired to clean up the database.<\/p>\n<p>This will now allow us to run the application which will bring up the login page. Once a login is successful the system will generate a Two Factor Authentication code and send the user to the validation screen. Once the proper code is entered the application will start as it should.<\/p>\n<p>But there is another problem. If the user, after logging in successfully uses a direct url to navigate to one of the views of the application the system will allow him to do that without having to enter the 2FA code.<\/p>\n<p>We can fix this by overriding the IsLoggedIn event in the session manager obect<\/p>\n<pre class=\"lang:default decode:true \">Function IsLoggedIn Returns Boolean\r\n    Boolean bLoggedIn\r\n    \r\n    Forward Get IsLoggedIn to bLoggedIn\r\n    \r\n    If (bLoggedIn) Begin\r\n        If (WebAppSession.MFASTATUS=\"W\") Begin\r\n            Function_Return (False)            \r\n        End\r\n    End\r\n\r\n    Function_Return bLoggedIn\r\nEnd_Function<\/pre>\n<p>This will now prevent a user from navigating to any page directly before the Two Factor authentication is complete<\/p>\n<p>As mentioned already this is a base implementation and in a real world system would have a few more features such as expiration time on the 2FA code but the main part of this post is to show how to add functionality like this to an existing DataFlex WebApp .<\/p>\n<p>The Two Factor Authentication can also be combined with the reCaptcha functionality. In a real world system we would use reCaptcha V3 to get a human\/robot score. if the score points to certainly human we can simply proceed with the login. If the score is certain robot we can show an error screen or reroute to a page intended for a robot. All scores in between would be forced to use Tow Factor Authentication to log into the system.<\/p>\n\n\t\t<div class='author-shortcodes'>\n\t\t\t<div class='author-inner'>\n\t\t\t\t<div class='author-image'>\n\t\t\t<img src='http:\/\/salzlechner.com\/dev\/wp-content\/uploads\/sites\/2\/2016\/02\/mike5crop-566174_60x60.jpg' alt='' \/>\n\t\t\t<div class='author-overlay'><\/div>\n\t\t<\/div> \n\t\t<div class='author-info'>\n\t\t\tMichael Salzlechner is the CEO of StarZen Technologies, Inc.<\/p>\n<p>He was part of the Windows Team at Data Access Worldwide that created the DataFlex for Windows Product before joining\u00a0<a href=\"http:\/\/starzen.com\">StarZen Technologies<\/a>. StarZen Technologies provides consulting services as well as custom Application development and third party products specifically for DataFlex developers<\/p>\n\t\t<\/div>\n\t\t\t<\/div>\n\t\t<\/div>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Our second article in this series is about Two Factor Authentication. Two Factor Authentication or Multi Factor Authentication (2FA or MFA) require the user to supply multiple forms of authentication something the user &#8216;knows&#8217; and something the user is in possession of or something the user is. For example an ATM withdrawal consists of the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_et_pb_use_builder":"","_et_pb_old_content":"","_et_gb_content_width":"","ngg_post_thumbnail":0,"footnotes":""},"categories":[27],"tags":[],"class_list":["post-595","post","type-post","status-publish","format-standard","hentry","category-dataflex-webapp"],"_links":{"self":[{"href":"http:\/\/salzlechner.com\/dev\/wp-json\/wp\/v2\/posts\/595","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/salzlechner.com\/dev\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/salzlechner.com\/dev\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/salzlechner.com\/dev\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/salzlechner.com\/dev\/wp-json\/wp\/v2\/comments?post=595"}],"version-history":[{"count":9,"href":"http:\/\/salzlechner.com\/dev\/wp-json\/wp\/v2\/posts\/595\/revisions"}],"predecessor-version":[{"id":606,"href":"http:\/\/salzlechner.com\/dev\/wp-json\/wp\/v2\/posts\/595\/revisions\/606"}],"wp:attachment":[{"href":"http:\/\/salzlechner.com\/dev\/wp-json\/wp\/v2\/media?parent=595"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/salzlechner.com\/dev\/wp-json\/wp\/v2\/categories?post=595"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/salzlechner.com\/dev\/wp-json\/wp\/v2\/tags?post=595"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}