Conditional Two Factor Authentication (v2023)

I’m using 2FA for logins, but I need to have it enabled only when accessing the site from non-trusted IPs
I already had been doing this for CAPTCHA using the following code in globals:

//Disable reCaptcha for locally hosted servers
$localIPs="xxx.xxx.xxx.xxx, xxx.xxx.xxx.xxy, xxx.xxx.xxx.xxz";
$externalIPs="xxx.xxx.xxx.abc, xxx.xxx.xxx.def";
if (strpos($localIPs,$_SERVER['SERVER_ADDR'])>0 || strpos($externalIPs,$_SERVER['REMOTE_ADDR'])>0) { // check if allowed local IP without recaptcha
$CaptchaClass = "CaptchaBase"; /// Without Recaptcha
}

2FA is part of the AUTH_CONFIG section, and the syntax is different.
How can I oveeride only these in Globals with false values?

"USE_TWO_FACTOR_AUTHENTICATION" => true,
"FORCE_TWO_FACTOR_AUTHENTICATION" => true,

You may generate another version of your project without 2FA, then compare the two version and find out the differences, and see if you can make the changes by server events.

yes that’s what I was thinking. I may generate two config files, one with and one without 2FA, rename them, then php include one or the other based on IP address.

I’ve had this working for a while now… this was the code placed in globals:

/Bypass 2FA for trusted networks
//get external IPs
// Sanitize user input (user's IP address)
$conn = Conn();
$userIp = $_SERVER['REMOTE_ADDR'];
// Sanitize user input (user's IP address)
$userIp = $conn->quote($userIp);
// Construct the SQL query
$query = "SELECT IP FROM ClassAct_Trusted_IPs WHERE IP = $userIp";
// Execute the query
$result = $conn->executeQuery($query);
// Check the result and configure 2FA accordingly
$row = $result->fetchAssociative();
if (!empty($row)) {
    Config("USE_TWO_FACTOR_AUTHENTICATION", false);
    Config("FORCE_TWO_FACTOR_AUTHENTICATION", false);
	session_start();
	$_SESSION['redirme'] = 'trust'.time();
} else {
    Config("USE_TWO_FACTOR_AUTHENTICATION", true);
    Config("FORCE_TWO_FACTOR_AUTHENTICATION", true);
}
//End 2FA bypass

I don’t like the fact that it’s in globals (doesn’t seem right) and it’s not within a function, but also now I need to be able to bypass 2FA for certain users.
My site has a few demo accounts which mustn’t request a security code at login.
So I’m trying to figure out way around that.
I have tried a few things and got it accidentally working only once.
Essentially, I’d need to throw the username into a session on first click of the Login button, and there would have to be some code in page_Load server event for the logon page.
So I deally I’m trying to adapt the code above to include username session checking.I there a session already set on submit of the login form which I could use?
Or is there a simpler way to achieve what I’m looking for?

You may try to use User_CustomValidate server event to check the user name and change the config settings.

What is the variable that is passed as username during login?
“username” ?

Read User_CustomValidate.

Hi there - I’m working on this exact same thing. Did you figure out a way to dynamically enable and disable 2FA? I’d like to have it disabled on the local network, and enabled for people on outside ip addresses.Anybody know - would it be possible to have conditional 2FA with SMS?What I’d like to do is this - if person is on the local network, no 2FA. If outside the local network require 2FA.Anybody know how to do this? Thanks!

Been trying this out with the code above - I can get it to bypass the sms validation by placing you two lines of code in User_CustomValidate:

Config("USE_TWO_FACTOR_AUTHENTICATION", false);
Config("FORCE_TWO_FACTOR_AUTHENTICATION", false);

However, the system freezes, and I have to refresh the screen. Session Start, or even a meta redirect doesn’t work. How can I make it proceed after setting the two Config lines so the user doesn’t have to refresh the screen?I think I’m close. Thanks.

You may use Page_Redirecting server event to check if the user is authenticated and set $this->IsModal = true; to simulate JSON response originally for 2FA.

Yes - that works. Thanks very much!

Can you post details exactly how? What does your Page_Redirecting look like now?

sorry for delay.
note I replaced my local Ip addresses with xxx.this is returning an error if login or password is wrong so I will continue to tweak, but this is the basic idea:in login page, Page_Redirecting event:

    if ((isset($_SESSION['user_validated'])) && ($_SESSION['user_validated']))
    {
      $this->IsModal = true;
      $url = "Appointments";
    }

in global code, User_CustomValidate event:

$userIp = explode(".",$_SERVER['REMOTE_ADDR']); 

$isLocal = 0;

if (     ($userIp[0] == "::x")  || 
         ($userIp[0] == "xx") ||
         ($userIp[0] == "xxx")     )
              $isLocal = 1;

if ($isLocal)
{
   Config("USE_TWO_FACTOR_AUTHENTICATION", false);
   Config("FORCE_TWO_FACTOR_AUTHENTICATION", false);
   if (session_status() === PHP_SESSION_NONE)
         session_start();
   $_SESSION['user_validated'] = 1;
}

Adding this above the previous post fixes redirect problems when wrong password:

//check if valid usr and pwd
$_SESSION['valid_login'] = 0;

$query = "SELECT password FROM staffinfo WHERE login = '".addslashes($usr)."'";
$password = ExecuteScalar($query);

if ($password == md5($pwd)) $_SESSION['valid_login'] = 1;
else $_SESSION['valid_login'] = 0;

if (!$_SESSION['valid_login']) RETURN FALSE;

I know its been a while, but just wanted to say that I found a very solid and simple method for this. Works in 2023, not tested on later versions yet.

First make sure your site has 2FA turned on.

In Global Code:

// 3. Bypass 2FA for trusted networks

$userIp = $_SERVER['REMOTE_ADDR'];

$userIp = AdjustSql($userIp);



//make sure you have a table named Trusted_IPs !!
$query = "SELECT IP FROM Trusted_IPs WHERE IP = '$userIp'";

$result = ExecuteQuery($query);

$row = $result->fetchAssociative();




if (!empty($row)) {

    Config("USE_TWO_FACTOR_AUTHENTICATION", false);

    Config("FORCE_TWO_FACTOR_AUTHENTICATION", false);

    $_SESSION['redirme'] = 'trust' . time();

} else {

    Config("USE_TWO_FACTOR_AUTHENTICATION", true);

    Config("FORCE_TWO_FACTOR_AUTHENTICATION", true);

}

In events > Other > Login Page > User_Loggedin:

// If we are on a trusted network, ensure we clear the 2FA flag and force a redirect

if (isset($_SESSION['redirme'])) {

    // Check for AJAX header directly to avoid Config index errors

    $isAjax = (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');

    

    if ($isAjax) {

        // Clear any previous output buffers to ensure clean JSON

        while (ob_get_level()) {

            ob_end_clean();

        }

        // Return JSON redirect for the login page JavaScript

        header('Content-Type: application/json');

        echo json_encode(["url" => GetUrl("Home")]);

        exit();

    } else {

        // Standard redirect using the class method

        $this->Terminate('Home');

    }

}