8 min read

A Step-by-Step Guide: Implementing Two-Factor Authentication (2FA) for Your Directory

A Step-by-Step Guide: Implementing Two-Factor Authentication (2FA) for Your Directory
Photo by Scott Webb / Unsplash

Introduction

By implementing 2FA, you introduce an additional layer of defense that requires users to provide a second form of verification in addition to their password.

With the increasing prevalence of cyber threats, it's crucial for your Brilliant Directory to prioritize user security.

By adding 2FA, you demonstrate your commitment to user privacy, build trust, and provide a robust defense against potential intrusions.

Don't wait until it's too late; protect your users' valuable information.

💡
The best part is, you have no excuses because here, you have a free, step-by-step guide on how to implement 2FA.

How it works

Creating the email template

To send the 2FA code, we need to create a custom email.

  • Visit managemydirectory.com and navigate to Emails > Email Templates in the left sidebar.
  • Click on the "New Email Template" button in blue.
  • Enter the subject as follows: Verification code: %%%user_code%%%.
  • Add the template name: two-factor-authentication.
  • Next, we need to add the email body:

<p style="
    text-align: center;
    font-size: 20px;"><strong style="
    font-size: 24px;">Let&rsquo;s get you signed in</strong></p>

<p style="
    text-align: center;
    font-size: 18px;">Your login code is:</p>
<div style="
    width: 320px;
    background: #ebeff3;
    padding: 20px;
    text-align: center;
    border-radius: 20px;
    margin: 0 auto;">

	<p style="
    font-size: 26px;
    line-height: 50px;
    letter-spacing: 0px;">%%%user_code%%%</p>
</div>

<p style="
    text-align: center;
    font-size: 14px;">
	<br>
</p>

<p id="isPasted" style="
    text-align: center;"><strong style="
    text-align: center;">Have questions or trouble logging in?</strong></p>

<p style="
    text-align: center;">Just reply to this email or contact alex@brilliantico.com</p>
  • Here's what the email looks like when the user receives the code:
💡
Note: While you can modify the email, please remember that the template name "two-factor-authentication" and the variable "%%%user_code%%%" are necessary for the 2FA to work.

Creating a custom page

After Brilliant Directories validates the user's email and password, we need a page where users can enter the code.

  • Visit managemydirectory.com and navigate to My Content > Web Page Builder in the left sidebar.
  • Click on the "New Web Page" button in blue.
  • Enter the page URL as "login/verification".
  • On the page content, add our custom widget that we will create later using the following code: [widget=two-factor-authentication]

Creating a custom widget to add our 2FA code

This widget will validate the code. First, let's create the widget, and after that, I will explain the validation rules we have implemented.

  • Visit managemydirectory.com and navigate to Toolbox > Widget Manager in the left sidebar.
  • Click on the "New Widget" button in blue.
  • Enter the widget name as "two-factor-authentication".
  • Copy and paste the code below to add all the functionality and design:

<?php
// Engine
$action = $_POST['action'];

if ($action == "login") {
  $code = $_POST['code'];
  $token = $_POST['token'];
  $query = mysql($w['database'], "SELECT * FROM two_factor_authentication WHERE code = '" . $code . "' AND token = '" . $token . "' AND used = 0");

  if (mysql_num_rows($query)) {
    $result = mysql_fetch_assoc($query);
    $user = getUser($result['user_id'], $w);
    $subscription = getSubscription($user['subscription_id'], $w);

    $json['result'] = true;
    mysql($w['database'], "UPDATE two_factor_authentication SET used = 1 WHERE token = '" . $token . "'");

    setcookie("token", $user['token'], time() + 3600000, "/");
    setcookie("useractive", $user['active'], time() + 3600000, "/");
    setcookie("userid", $user['user_id'], time() + 3600000, "/");
    setcookie("subscription_id", $user['subscription_id'], time() + 3600000, "/");
    setcookie("profession_id", $user['profession_id'], time() + 3600000, "/");
    setcookie("location_value", $user['location'], time() + 3600000, "/");
    setcookie("loggedin", "1", time() + 3600000, "/");

    $_COOKIE['token'] = $user['token'];
    $_COOKIE['useractive'] = $user['active'];
    $_COOKIE['userid'] = $user['userid'];
    $_COOKIE['subscription_id'] = $user['subscription_id'];
    $_COOKIE['profession_id'] = $user['profession_id'];
    $_COOKIE['location_value'] = $user['location'];
    $_COOKIE['loggedin'] = 1;

    mysql($w['database'], "UPDATE
            `users_data`
        SET
            `last_login` = '" . $w['date'] . "'
        WHERE
            `user_id` = '" . $user['user_id'] . "'");

    logUserActivity($user['user_id'], "Log In", $w);

    $ip = '';

    if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
        $ip = $_SERVER['HTTP_CLIENT_IP'];
    } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    } else {
        $ip = $_SERVER['REMOTE_ADDR'];
    }

    $lastLoginIP = array('last_login_ip' => $ip);
    storeMetaData("users_data", $user['user_id'], $lastLoginIP, $w);

  } else {
    $json['result'] = false;
  }

  echo json_encode($json);
  exit;
}

// Verification Code
$token = $_GET['token'];

if (!$token) {
  $redirectUrl = "/login";
  header("Location: " . $redirectUrl);
  exit;
}

$query = mysql($w['database'], "SELECT * FROM two_factor_authentication WHERE used = 0 AND token = '" . $token . "'");

if (mysql_num_rows($query)) {
  $result = mysql_fetch_assoc($query);
  $user = getUser($result['user_id'], $w);
} else {
  $redirectUrl = "/login";
  header("Location: " . $redirectUrl);
  exit;
}
?>

<style>
.code-wrapper {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.code-wrapper label {
  font-size: 20px;
  font-weight: 400;
}
.code-wrapper input {
  height: 60px;
  margin: 15px;
  border: 1px solid black;
  border-radius: 10px;
  font-size: 48px;
  width: 250px;
  text-align: center;
  letter-spacing: 5px;
  font-weight: 100;
}
.validation {
  background: #fcde6d;
  padding: 10px;
  text-align: center;
  font-size: 18px;
  margin: 30px 0;
  border-radius: 100px!important;
}
@media (max-width: 768px) {
  .code-wrapper input {
    font-size: 30px;
  }
}
</style>

<div>
  <div class="row member-login-page-container">
    <div class="fpad-lg novpad">
      <div class="module fpad-xl member-login-container">

        <h2 style="text-align: center;font-weight: 600;text-transform: capitalize;">Check your email</h2>
        <hr>

        <div>
          <form action="" id="validation">
            <div class="code-wrapper">
              <label for="" style="text-transform: capitalize;">Your login code</label>
              <input name="user_code" type="text" placeholder="000000" minlength="6" maxlength="6" required>
              <input type="hidden" name="token" value="<?php echo $_GET['token']; ?>">
            </div>
            <div>
              <div class="validation hidden">Not a valid code. Sure you typed it correctly?</div>
            </div>
            <div>
              <p class="text-center">We've sent a 6 digit login code to <strong><?php echo $user['email'];?></strong>. Can't find it? Check your spam folder.</p>
            </div>
            <div style="margin: 20px 0;">
              <button type="submit" class="btn btn-primary btn-lg btn-block">Login</button>
            </div>
          </form>
        </div>
      </div>
    </div>
  </div>
</div>

<script>

$('#validation').submit(function(e) {
  e.preventDefault();
  const action = "login";
  const token = $("input[name='token']").val();
  const code = $("input[name='user_code']").val().toUpperCase();

  $.post('/api/data/html/get/data_widgets/widget_name?name=two-factor-authentication',{ action, code, token },function(data){

    if (!data['result']) {
      $('.validation').removeClass('hidden');
    } else {
      window.location.href = "/account/home";
    }
  }, "json");
});

</script>
  • Here's what the page looks like:
  • Validation rules:
    • If you visit the page without a token as a parameter in the URL, the system will redirect you to the login page.
    • If you enter a token that is not in the database, the system will redirect you to the login page.
    • If you enter a validated token that has already been used, the system will redirect you to the login page.
    • If you have a validated token that has not been used, the system will allow you to proceed.
    • If you enter a non-validated code, the system will display an alert.
    • If you enter a code that has already been used, the system will not allow you to proceed and will display an alert.
    • If you enter a validate code and a validate token, but they do not match, the system will not grant you access. This prevents the use of a token from one user and a code from another use.
    • If a user requests a code but does not use it and then requests another one, the previously unused code will be disabled.
    • The code can be entered in either lowercase or uppercase letters.
💡
Note: As you can see, there are specific rules in place to validate the code. These rules enhance the security of the validation process. Although it may sound a bit complicated, it is actually quite simple for the users. They only need to go through the login page and then enter the code they receive in their email.

Customizing the "Bootstrap Theme - Member Login Page" widget

In this widget, we prevent login before entering the code, and at the same time, we utilize Brilliant Directories' validations to verify the user's email and password.

  • Visit managemydirectory.com and navigate to Toolbox > Widget Manager in the left sidebar.
  • Search for: Bootstrap Theme - Member Login Page
  • If the widget is the default one, go to actions > customize.
  • If the widget is custom, this part may be a bit complicated, and I cannot guarantee that it will work. However, please let me know, and I'll be more than glad to assist you.
  • Copy and paste the code below between line 82 and line 83.

// two factor authentication
// generate code
$code = '';
$characters = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
$length = strlen($characters);

for ($i = 0; $i < 6; $i++) {
    $index = mt_rand(0, $length - 1);
    $code .= $characters[$index];
}

// generate token
$token = hash(md5,rand(1,100000000));

// unused codes will be automatically disabled
mysql($w['database'], "UPDATE two_factor_authentication SET used = 1 WHERE user_id = " . $user['user_id']);

// save code
$insert = mysql($w['database'], "INSERT INTO two_factor_authentication (user_id, code, token) VALUES (" . $user['user_id'] . ", '". $code . "', '" . $token . "')");

// set email with the code
$w['user_code'] = $code;
$email_template = "two-factor-authentication";
$email = prepareEmail($email_template, $w);
$emailSender = $w['website_email'];

sendEmailTemplate($emailSender, $user['email'], $email['subject'], $email['html'], $email['text'], $email['priority'], $w, $email);

// engine responses
$json['result'] = "success";
$json['message'] = $label["member_successful_login"] . $_REQUEST['loc'];
$json['redirect_url'] = "/login/verification?token=" . $token;

echo json_encode($json);
exit;
💡
Note: With this code, we generate the code and token, save the relationship between the code, token, and user, and then send the email. Afterward, we instruct the system to redirect the user to the new validation page.

Creating a custom table in the database

We need a location to store the code, token, and user relationship in order to perform all the validations.

  • Visit managemydirectory.com and navigate to the Developer Hub in the left sidebar.
  • Click on MySQL Database
  • In the left sidebar, locate the database with the word "directory" at the end.” ⬇️
  • Click on SQL
  • Copy and paste the code below, then click the button labeled 'Go' below.

CREATE TABLE `two_factor_authentication` (
  `id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL,
  `code` varchar(20) NOT NULL,
  `token` varchar(255) NOT NULL,
  `used` int(11) NOT NULL DEFAULT 0
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;

ALTER TABLE `two_factor_authentication`
  ADD PRIMARY KEY (`id`);

ALTER TABLE `two_factor_authentication`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
COMMIT;

QA

So, after all the hard work, here comes the fun and exciting part! 😁

  • Firstly, go to managemydirectory and click on the "Refresh Live Website" button multiple times, just to be sure! 😅
  • Proceed to your login page.
  • Enter the email and password of your user.
  • If the email and password match, you will be redirected to the new validation page.
  • Check your email to retrieve the code. In case you don't see the email, remember to check your spam folder.
  • Copy and paste the code on the validation page.
  • Cross your fingers, and everything should work perfectly! 🎆
💡
Note: If, like me, you are curious, feel free to test the system and explore all the validations mentioned above. If you happen to find a bug, please let me know so that we can improve the code.

How to disable 2FA

Disabling the 2FA is a simple process. Just disable the "Bootstrap Theme - Member Login Page" widget.

Conclusion

Congratulations! You now have a two-factor authentication implemented on your directory.

I understand that depending on your experience with the platform and web development, it may have been challenging. However, I encourage you to give it a try.

I sincerely hope you enjoyed this tutorial as much as I enjoyed creating it. I put a lot of effort into this, and I would love for you to consider signing up! 🙌

If you successfully completed the process, please let us know in the comment section below.


That's all for now. Thanks for reading!