Adding Authenticator App Support to your WEBDEV site

Adding Authenticator App Support to your WEBDEV site

From version 26 onwards, adding Authenticator App support to a WEBDEV site has become effortless. Authenticator apps generate Time-Based One-Time Passwords (TOTPs), offering a more secure and convenient approach to Two-Factor Authentication (2FA) compared to email or text messages.

What is an Authenticator App and Why Should I Care?

Authenticator apps are applications that facilitate Two-Factor Authentication (2FA) by generating Time-Based One-Time Passwords (TOTP). This enables you to provide 2FA support without the user needing to wait for an email or text message containing the code. Once registered with your site, the Authenticator App will generate the code, which is refreshed every 30 seconds. Using Authenticator Apps instead of One-Time Passwords (OTP) via email or text offers many benefits in terms of both convenience and security.

TOTPs are based on industry standards, offering users the flexibility to select their preferred Authenticator app, including options like Google, Microsoft, Duo, Authy, BitWarden or LastPass. These apps function offline, are simple to configure, and provide increased security as the code is not sent via email or text.

I’m sold! How do I add it to my WEBDEV site?

I’m glad you asked. Starting with version 26, PCSOFT has made it incredibly easy to implement Authenticator App support. I assure you, this will be one of the simplest security enhancements you’ve ever made! It would be strange for me to write this article if that weren’t the case. 🙂

First, let’s see it in action on our wxSocial example site. We provide users with the option to enable it on their profile page.

When users enable the option, they are presented with a QR code to scan with their Authenticator app to register our site. Please note that the TOTPkey is also displayed below the QR code, allowing users to manually enter it if they cannot scan the code.

Once the code is scanned in their Authenticator app, a display similar to this will appear. It reveals the name of the site and the account used, which is useful when managing multiple accounts on the same site. A 30-second countdown is also visible, indicating when the code will change.

Now, after validating their password, we request the TOTP code from the Authenticator app when they log in.

If the correct code is entered, they will be logged in. Remember, this is just a demo site, other than the account page there literally isn’t anything to “See Here” 🙂

The Code

First, we require a 16-character string in our database table to store the TOTPKey. This key will be transmitted to the authenticator app, enabling it to register with our site.

On our user profile page, the following code is behind the checkbox.

IF NOT NewUser THEN
	IF ckEnable2FA
		IF NoSpace(edtEmailAddress) = "" THEN
			Info("Email address is required for Two Factor Authentication")
			ckEnable2FA = False
			RETURN
		END
		Glo.recUser.TOTPKey = TwoFactorAuthenticationGenerateTOTPKey(edtUserID + edtEmailAddress)
		popRegister2FA.bar2FA = TwoFactorAuthenticationGenerateLink(Glo.recUser.TOTPKey,edtEmailAddress,"wxPerts Social Signon Demo")
		popRegister2FA.stcTOTPKEy = Glo.recUser.TOTPKey
		PopupDisplay(popRegister2FA)
	ELSE
		Glo.recUser.TOTPKey = ""
	END
END

When creating a new account, we need to wait until the account is saved. This is because we use the UserID as part of the Identifier string to generate the TOTPKey. There is additional code behind the Save button to manage the generation of the code for a new account.

Next, we utilize one of the three WL functions necessary to implement this entire process. The function, TwoFactorAuthenticationGenerateTOTPKey, requires a unique identifier string. This can be any string that uniquely identifies the user in your system. In this case, I’m using a combination of the UserID and their email address. Note the resulting TOTPKey is stored in the database field we created earlier.

We employ the second function out of the three, TwoFactorAuthenticationGenerateLink, to generate the QR code for the TOTPKey. Note that the other two parameters are the account and site description, which are displayed in the Authenticator app.

The result of that function is placed in a barcode control, set up as a QR code on our popup. It also includes a static display of the TOTPKey for manual entry.

Finally, if they uncheck the box, we remove the TOTPKey from the record.

That’s all it takes to generate the code and display a QR code, allowing the Authenticator app to register our site! It requires just two functions and a few lines of code!

As mentioned above, the same code is also included on the save button. It is triggered only when the NewUser Boolean is true, allowing it to execute after the UserID is created.

Let’s examine the login code. If you read my recent post Password Hash Primer for WEBDEV, you might recall that I presented this code and mentioned the Check2FA() procedure will be explained in a future article. Well, this is that article!

PageToFile()
tmpPassword is string = NoSpace(edtPassword)

// Look For User Record
mgrUser.Filterstring = [
	WHERE emailaddress = '[%NoSpace(edtEmail)%]' 
]
mgrUser.FetchData()
IF ArrayCount(mgrUser.arrRec) = 1 THEN
	Glo.recUser = mgrUser.arrRec[1]
	IF Glo.recUser.isPasswordValid(tmpPassword) THEN
		Check2FA()	
	ELSE
		VariableReset(Glo.recUser)
		Info("Sorry we are unable to verify you login information, please try again.")
		RETURN		
	END
ELSE
	Info("Sorry we are unable to verify you login information, please try again.")
	RETURN		
END

After validating the user’s password, the Check2FA procedure determines if any 2FA options are enabled. If no options are enabled, the process concludes and the Home page is displayed.

PROCEDURE Check2FA()

IF Glo.recUser.Use2FA THEN
	Process2FA("Authenticator")
ELSE IF Glo.recUser.Use2FASMS
	Process2FA("SMS")
ELSE IF Glo.recUser.Use2FAEmail
	Process2FA("Email")				
ELSE
	PageDisplay(home)
END

Observe that the remainder of the procedure uses the same Process2FA function, but with different parameters. I designed the original application this way because we are essentially performing the same action: comparing the code input by the user with the expected code. The only difference is the method of checking the code. Today, we will examine the “Authenticator” version.

Here is the complete code for the ‘Process2FA’ procedure.

PROCEDURE Process2FA(AuthType)

Mode2FA = AuthType

btnSendSMS..Visible = True
btnSendEmail..Visible = True
btnUseAuthenticator..Visible = True

IF NOT Glo.recUser.Use2FASMS THEN
	btnSendSMS..Visible = False
END
IF NOT Glo.recUser.Use2FAEmail THEN
	btnSendEmail..Visible = False
END
IF NOT Glo.recUser.Use2FA THEN
	btnUseAuthenticator..Visible = False
END

SWITCH AuthType
	CASE "Authenticator"
		btnUseAuthenticator..Visible = False
		stcPleaseEnter = "Please enter the code from your Authenticator app"
	CASE "SMS"
		btnSendSMS..Visible = False
		stcPleaseEnter = "Please enter the code from your SMS"
		recResetRequest = mgrResetRequest.createRequest("2FA")
		retJSON is JSON = TW.Send2FAOTP(Glo.recUser.MobileNumber,recResetRequest.Hash)
		IF retJSON.Success = False THEN
			ToastDisplay("Unable to Text One Time Passcode " + CR + retJSON.ErrorMessage,toastLong)
		END
	CASE "Email"	
		btnSendEmail..Visible = False
		stcPleaseEnter = "Please enter the code from your EMail"
		recResetRequest = mgrResetRequest.createRequest("2FA")
		retVal is string = MG.Send2FAOTP(Glo.recUser.emailaddress,recResetRequest.Hash)
		IF retVal[[1 TO 6]] = "Failed" THEN
			ToastDisplay("Unable to Email One Time Passcode " + CR + retVal,toastLong)
		END
END
	
PopupDisplay(popGet2FA)	

Note that for the Authenticator mode, we are merely setting up some display fields. In contrast, with the SMS or Email options, we must generate the code and send it ourselves. Now you can see why I prefer the Authenticator App method!

The code for the ‘Continue’ button on our pop-up (popGet2FA) incorporates coding for all 2FA modes. As before, the version for the Authenticator is significantly simpler.

RetVal is boolean = False
SWITCH Mode2FA
	CASE "Authenticator"
		RetVal = TwoFactorAuthenticationCheckCode(edt2FA,Glo.recUser.TOTPKey) 
	CASE "SMS"
		recResetRequest = mgrResetRequest.ValidateResetCode(edt2FA)
		IF recResetRequest.ResetRequestID > 0 AND recResetRequest.UserID = Glo.recUser.UserID AND recResetRequest.ResetType = "2FA" THEN
			RetVal = True
		END
	CASE "Email"	
		recResetRequest = mgrResetRequest.ValidateResetCode(edt2FA)
		IF recResetRequest.ResetRequestID > 0 AND recResetRequest.UserID = Glo.recUser.UserID AND recResetRequest.ResetType = "2FA" THEN
			RetVal = True
		END
END
IF RetVal THEN
	PageDisplay(home)
ELSE
	Info("Sorry that code is invalid or has expired")	
END

We simply call the third and final function of the WL TwoFactor functions, TwoFactorAuthenticationCheckCode. We provide it with the code that the user entered, as well as the TOTPKey we stored on the user’s account. It performs the necessary calculations to determine if the entered code is valid, then returns either true or false for us to act upon.

What’s Next

That’s all it takes to add Authenticator App support to your WEBDEV sites. It’s very simple! Just one database field, two popups, and three function calls are all you need.

The holy grail of password management is a concept formerly known as “Passwordless,” now referred to as “Passkeys”. These are not only difficult to spoof or steal, but they also make it easier for the user to log in. I’m preparing an article exploring how to implement Passkeys in WEBDEV.

This example is from our wxSocial project, which you can view online at https://wxsocial.wxperts.com/. The project employs several of our helper classes to streamline the management of OAuth and OpenID communications, HTTP communications, JSON Web Tokens, and Cookie Management.

wxPerts offers these classes along with other 3rd party API classes, to our consulting and mentoring clients. If you’re interested in integrating any of these classes, please contact us at sales@wxperts.com.

Leave a comment