TOTP Authentication - Part I

Tips submitted by PHPMaker users
Post Reply
sticcino
User
Posts: 1043

TOTP Authentication - Part I

Post by sticcino »

In the next few posts, i'll share code to add two factor authentication to your app. This is based on phpGansta PHPGangsta_GoogleAuthenticator library.

Files affected.

1. userfn.php - code add
2. login.php - code add
3. 2fa.php - new
4. verify.php - new

Notes:
you will notice some function calls _getPreferences() in the code -- we keep our settings in a table for various application settings, you can swap these calls out with your static variables instead.
example: $secret = _getPreferences("2FA_SECRET"); --> change to $secret = "YOUR SECRET HERE";

Cavets:
in its current form, when the user logins, if they haven't been authentication yet, they are redirect to the 2fa auth page. you will notice that the menu's the user is allowed to access will be visible, but attempting to access then will fail. I will check with the admin or experts to see if they can be hidden at this stage and re-display after the 2fa authentication occurs. you can work around this but removing the include "header.php" in the 2fa.php file, but then the page won't adhere to the reset of your app - unless you want to add your own html code to pretty it up... up to you

sticcino
User
Posts: 1043

Post by sticcino »

in the classes/login.php code add the following:

// User Logged In event
function User_LoggedIn($usr) {
global $CONFIG, $_SESSION;

if(_getPreferences("ENABLE_2FA_AUTHENTICATION")) {
@$_SESSION['2fa-code'] = "";
if(@$_SESSION['2fa-verified'] == FALSE);
$this->terminate("../library/2fa/2fa.php");
}
}

notes:
change ../library/2fa/2fa.php TO the location of where you are placing these files.

sticcino
User
Posts: 1043

Post by sticcino »

in the userfn.php file add the following to the Page_Rendering Event (make sure to add via the phpmaker app, otherwise if you regenerate and have userfn.php to re-generate, you'll lose the code)

// Page Rendering event
function Page_Rendering() {
global $Page, $_SESSION;

if(_getPreferences("ENABLE_2FA_AUTHENTICATION")) {
if($Page->PageID != "login")
{
if($_SESSION['2fa-verified'] == FALSE) {
AddHeader("Location", "../../logout.php");
exit();
}
}
}
}

Notes:
if(_getPreferences("ENABLE_2FA_AUTHENTICATION")) --> we set this up to enable/disable the 2fa code on demand.. remove the if block if you aren't going to use something similar
Change "../../logout.php" to reflect where you stored the 2fa files...

sticcino
User
Posts: 1043

Post by sticcino »

create a new file, add the following code: -- this was not generated via phpmaker

<?php
namespace PHPMaker2020\aami;

// Session
if (session_status() !== PHP_SESSION_ACTIVE)
session_start(); // Init session data

// Output buffering
ob_start();
?>
<?php
$RELATIVE_PATH = "../../";
?>
<?php include_once $RELATIVE_PATH . "autoload.php"; ?>
<?php
$authenticator = new \PHPGangsta_GoogleAuthenticator();
$checkResult = FALSE;
$tolerance = 0; //Every otp is valid for 30 sec. tolerance = 2, verifies current and last two OTPS

$otp = Post("otp", "") ;//Generated by Authenticator.
$secret = _getPreferences("2FA_SECRET");

$checkResult = $authenticator->verifyCode($secret, $otp, $tolerance);

if ($checkResult) {
global $Page, $_SESSION;

@$_SESSION['2fa-verified'] = TRUE;
@$_SESSION['2fa-code'] = $otp;

echo 'validated';

} else {
echo 'Authentication Attempt Failed';
}
?>

Notes:
change $RELATIVE_PATH = "../../"; to reflect where you have saved the files
_getPreferences("2FA_SECRET"); change to your SECRET CODE: $secret = "YOUR SECRET CODE"

sticcino
User
Posts: 1043

Post by sticcino »

create the core file 2fa.php -- this was created via phpmaker

add the following code to the custom file:

<html>
<head>
<title>TOTP Authentication</title>
<script src="httppppp ajax.googleapis . com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
</head>
<body>
<strong>Scan the QR code using the Google Authenticator App <a target="_blank" hrefffffff="play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en">Google Play</a>
or <a target="_blank" hreffffff=" itunes.apple.com/en/app/google-authenticator/id388497605?mt=8">Apple App Store</a>
<br>
/* imgsrc='htttttpsss chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth%3A%2F%2Ftotp%2F***IDENTIFIER***%3Fsecret%3D***SECRET_CODE_HERE***%26issuer%3D***YOUR_WEB_SITE_HERE***'/></div> */

<br><br>
<div class="well">
<br>
<strong>OTP Token: </strong>
<input type="text" name="otp" id="otp" value="" maxlength="6" autocomplete="off" />
<button class="btn btn-info" id="verify">Validate</button>
<br>
<div id ="status"></div>
<br><br>
<script>
$(document).ready(function()
{
$("#verify").click(function()
{
$.post( "verify.php",
{otp:$("#otp").val()},
function(data) {
if(data == 'validated')
window.location.assign("../../dashboard.php");
else
$("#status").html("<br><div class='alert alert-danger'>Verification Failed.</div>");
})
.fail(function() {
alert(data);
});
});
});
</script>
</body>
</html>

NOTES:
had to break the image source string due to web site... file the beginning portion

/* imgsrc='https chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth%3A%2F%2Ftotp%2F***IDENTIFIER***%3Fsecret%3D***SECRET_CODE_HERE***%26issuer%3D***YOUR_WEB_SITE_HERE***'/></div> */

to generate the proper barcode and create your site record in google authenticator
CHANGE:

totp: ***IDENTIFIER*** TO a Friendly name to display on the authenticator app
secret: ***SECRET_CODE_HERE*** TO your secret code -- this must match the same code in the verify.php
issuer: ***YOUR_WEB_SITE_HERE*** change to your web site so the user knows which site the token is for

(replace the complete string including the *** -- this is just a placeholder)

if you've done this correctly, when you scan the barcode in the authentication app, it will create your site:
on the Google Authenticator app, you'll see something like: tonyspizza.com (Tonys Pizza) displayed below the generated token

window.location.assign("../../dashboard.php"); =>>> change to the landing page you want the user redirected to after successful 2fa authentication

sticcino
User
Posts: 1043

Post by sticcino »

any enhancements /bug fixes welcome.

make sure you TEST all scenarios before going live, I cannot confirm this is full proof.
because the 2fa.php was generated via phpmaker... make sure you give all your users permission to run the file, otherwise they are not going to go very far......
don't forget to add the phpGansta pacakge to your composer packages (phpgansta/googleauthenticator) using dev-master as version

TODO:

- add feature to send token to user via SMS instead of having to scan code all the time
- Check with phpMaker guru's about hiding the menus after the initial login, so only the 2FA page is displayed.

sticcino
User
Posts: 1043

Post by sticcino »

if you want to send the code via sms message to a users cell device
once again... _getUserInfo() is a custom function we use to various pieces of information, change the call to your static data or however you get the required information...

in 2fa.php replace the all the code AFTER the <img src> code line.... with:

<br>
<div class="well">
A verification code is an extra layer of security to protect your account online and verify your identity.<br>
You can receive the verification code by text message, email or by entering the code displayed on your authentication app.<br>
We will send you an automated message with a one-time verification code to the contact method you select below.<br>
If you no longer have access to the contact method below, please contact us for assistance.
<br><br>
<strong>If you leave this page, you will not be able to enter the one-time verification code.</strong><br>
<strong>Enter the code displayed on your authentication app, or sent to your mobile phone: </strong>
<input type="text" name="otp" id="otp" value="" maxlength="6" autocomplete="off" />
<button class="btn btn-info" id="verify">Validate</button>
<?php
$smsTO = _getUserInfo("CellularPhone", CurrentUserID());
$smsTO = substr_replace($smsTO,"xxx xxx xx",0,10);
echo "<br>Send the verification code to my phone ending in: <strong>" . $smsTO. "&nbsp;&nbsp;&nbsp;" ;
?>
<button class="btn btn-info" id="verifysms">Send Me My Code</button><br>
<div id ="status"></div>
<br><br>
<script>
$(document).ready(function()
{
$("#verifysms").click(function()
{
$.post( "_sendSMSMessage.php",
function(data) {
$("#status").html("<br><div class='alert alert-info'>"+data+"</div>");
})
.fail(function() {
alert(data);
});
});

$("#verify").click(function()
{
$.post( "verify.php",
{otp:$("#otp").val()},
function(data) {
if(data == 'validated')
window.location.assign("../../dashboard.php");
else
$("#status").html("<br><div class='alert alert-danger'>Verification Failed.</div>");
})
.fail(function() {
alert(data);
});
});
});
</script>
</body>
</html>

Notes:
we will create the ajax post file _sendSMSMessage.php in the next post.

sticcino
User
Posts: 1043

Post by sticcino »

we need the post file for the ajax call to send the user the token. This is using twilio SMS API, you will need your own account to use it and don't forget to add it to your composer packages...

you can create this without going through phpmaker. the code below is created via phpmaker, so don't forget to give the users permissions to run the script. Also, note that the some functions have been removed as they are not needed for the ajax post or will interfere with it. Pay attention to the "remmed out" phpmaker code

remove/rem out:
// Page_Rendering();
<?php // include_once $RELATIVE_PATH . "header.php"; ?>
<?php // if (Config("DEBUG")) echo GetDebugMessage(); ?>
<?php // include_once $RELATIVE_PATH . "footer.php"; ?>
<?php //$_sendSMSMessage->terminate(); ?>
-------------------------------------------------------------------------------------------------------------
in the custom code section:

<?php
use \Twilio\Rest\Client as twiSMS;
$authenticator = new \PHPGangsta_GoogleAuthenticator();

$sid = _getPreferences("TWILIO_SMS_SID");
$token = _getPreferences("TWILIO_SMS_TOKEN");
$Mobile_No = _getUserInfo("CellularPhone", CurrentUserID());
$secret = _getPreferences("2FA_SECRET");

$oneCode = $authenticator->getCode($secret); // get the verification token without using authentication app
$msg = "Please enter " .$oneCode. " withing 1 minute to confirm your session.";

if($Mobile_No == '') {
WriteAuditTrail("log", StdCurrentDateTime(), ScriptName(), CurrentUserID(), 'SMS', CurrentUserIP(), $Mobile_No, 'Invalid Mobile Number', '', 'SMS Send');
echo "No celluar number found on file. Unable to send verification token" ;
exit();
}
$msg = RemoveHtml($msg);
try {
$client = new twiSMS($sid, $token); // Twilio\Rest\Client($sid, $token);
WriteAuditTrail("log", StdCurrentDateTime(), ScriptName(), CurrentUserID(), 'SMS', 'client->create', $client, $sid, $token, 'Completed');
}
catch(Exception $e) {
WriteAuditTrail("log", StdCurrentDateTime(), ScriptName(), CurrentUserID(), 'SMS', 'client->create', $e, $sid, $token, 'Failed');
echo "$e";
exit();
}
try {
$message = $client->messages->create(
$Mobile_No, // Text this number
array(
'from' => _getPreferences("TWILIO_SMS_NUMBER"), // From a valid Twilio number
'body' => $msg
)
);
WriteAuditTrail("log", StdCurrentDateTime(), ScriptName(), CurrentUserID(), 'SMS', CurrentUserIP(), $Mobile_No, $msg, $message, 'Completed');
}
catch(Exception $e) {
WriteAuditTrail("log", StdCurrentDateTime(), ScriptName(), CurrentUserID(), 'SMS', 'messages->create', $e, $Mobile_No, $msg, 'Failed');
echo "Failed To Send Token<br>". "$e";
exit();
}
echo "Token Sent.";

?>

Notes:
remove any calls to WriteAuditTrail if you are not interested in tracking the progress.
_getPreferences(), _getUserInfo() are custom functions we use, replace with your static data or method of retrieving the data that is needed.

Fakiro82
User
Posts: 108

Post by Fakiro82 »

Hi Sticcino,

I need to add two auth factory but I use phpmaker 2018. Is It xwork with this version?
I read your post but I have any question:

1. for the first point: Where is the classes/login.php? Where I can insert your first code?

It would be better if the user use the verification code and not BarCode.

Can you help to set up entire file?

Many Thanks.
Andrea

sticcino
User
Posts: 1043

Post by sticcino »

this will not work with v2018 codebase.

1. for the first point: Where is the classes/login.php? Where I can insert your first code?
in folder classes

It would be better if the user use the verification code and not BarCode.
the bardcode is used to register the web application with the Authentication App. verification codes are not retrieved with the barcode...

Fakiro82
User
Posts: 108

Post by Fakiro82 »

Hi Sticcino,

many thanks for your answer.
But I need to use the new release of phpmaker 2020.

Do you know how can I make it work on phpmaker 2018?

Many thanks,
Andrea.

Riba
User
Posts: 1

Post by Riba »

To hide the menus during 2fa authentication
function Menu_Rendering($menu) {
// Change menu items here
global $Page;

// Hide menu on 2fa page
if($Page->TableName == "2fa.php") {
$menu->Clear();
}
}

sticcino
User
Posts: 1043

Post by sticcino »

minor adjustment to code.

if 2FA is enabled, the forgotpwd.php needs to be able to run before the 2FA test.


// Page Rendering event
function Page_Rendering() {
global $Page, $_SESSION;

CHANGE:
if($Page->PageID != "login")

TO:
if($Page->PageID != "login" && $Page->PageID != "forgotpwd")
}

Post Reply