2 factor Login Authentication – PHP & Twilio SMS

05 June, 2020

MySQL
PHP

In this tutorial, we are going to implement 2 factor login authentication. 2 factor login authentication means that user will be verified twice before login.

  1. 1st factor should be what they know, like password or some security question.
  2. 2nd factor will be what they have, like mobile or fingerprint.

Database structure

First create a table where all users data will be stored. You might already have that if you are working on existing project. You need to add a new field “phone” in your existing “users” table because we will be sending SMS to this field value. There should be an option to enable or disable this 2 factor authentication, so a boolean field named “is_tfa_enabled”. Also a pin number which will be sent as SMS.

users

  • id int(11) NOT NULL AUTO_INCREMENT
  • email text NOT NULL
  • password text NOT NULL
  • phone text NOT NULL
  • is_tfa_enabled tinyint(1) NOT NULL
  • pin text NOT NULL

User registration

During registration, we need to add a new field for phone number. Below is a sample registration form with password encryption.

Set default values to following fields:

  • “is_tfa_enabled” as false
  • “pin” as empty
<form method="POST" action="register.php">
	
	<input type="email" name="email">
	<input type="password" name="password">
	<input type="text" name="phone">
	
	<input type="submit" name="register">
</form>

register.php

<?php

	if (isset($_POST["register"]))
	{
		$email = $_POST["email"];
		$phone = $_POST["phone"];
		$password = $_POST["password"];
		$password = password_hash($password, PASSWORD_DEFAULT);

		$conn = mysqli_connect("localhost", "root", "", "tutorials");
		
		$sql = "INSERT INTO users (email, phone, password, is_tfa_enabled, pin) VALUES ('$email', '$phone', '$password', 0, '')";
		mysqli_query($conn, $sql);

		header("Location: login.php");
	}

?>

User login – Send OTP

There will be no extra field in login, it will have email/username and password or whatever fields you are using to login. The important thing is, we need to send an SMS if the user has enabled 2-factor authentication. Below is a typical login form:

<form method="POST" action="login.php" id="form-login">
	<input type="email" name="email">
	<input type="password" name="password">
	
	<input type="submit" name="login">
</form>

We will send an SMS using Twilio if user has enabled 2-factor authentication. First you need to include Twilio PHP library in your project using composer. Open terminal in your project root folder and run the following command to install Twilio:

composer require twilio/sdk

Create an account on Twilio from here. After creating account, you will be given a $15 balance to test the API. On your Twilio console dashboard you will find your “account SID” and “auth token”. You will need to place those on below variables accordingly:

Twilio console

login.php

<?php

	session_start();

	require_once "vendor/autoload.php";
	use Twilio\Rest\Client;

	$sid = "";
	$token = "";

	if (isset($_POST["login"]))
	{
		$email = $_POST["email"];
		$password = $_POST["password"];

		$conn = mysqli_connect("localhost", "root", "", "tutorials");
		
		$sql = "SELECT * FROM users WHERE email = '$email'";
		$result = mysqli_query($conn, $sql);

		if (mysqli_num_rows($result) > 0)
		{
			$row = mysqli_fetch_object($result);
			if (password_verify($password, $row->password))
			{
				if ($row->is_tfa_enabled)
				{
					$row->is_verified = false;
					$_SESSION["user"] = $row;

					$pin = rand(0, 9) . rand(0, 9) . rand(0, 9) . rand(0, 9) . rand(0, 9) . rand(0, 9);
					
					$sql = "UPDATE users SET pin = '$pin'  WHERE id = '" . $row->id . "'";
					mysqli_query($conn, $sql);

					$client = new Client($sid, $token);
					$client->messages->create(
						$row->phone, array(
							"from" => "",
							"body" => "Your adnan-tech.com 2-factor authentication code is: ". $pin
						)
					);

					header("Location: enter-pin.php");
				}
				else
				{
					$row->is_verified = true;
					$_SESSION["user"] = $row;

					header("Location: index.php");
				}
			}
			else
			{
				echo "Wrong password";
			}
		}
		else
		{
			echo "Not exists";
		}
	}

?>
  1. We need to call session_start() in order to use any $_SESSION variable.
  2. If the user has provided correct email and password, then we are checking if that user has enabled 2-factor authentication for his account.
  3. Then we are generating a random pin code of 6 digits and sending it to the user’s phone number.
  4. We are using “is_verified” field in $_SESSION to check if user account is pin verified or not.
  5. After SMS has been sent, you will be redirected to “enter-pin.php” where you need to enter the pin sent in SMS.

enter-pin.php

This file will only contain 1 field to enter pin code. User ID is already been stored in $_SESSION.

<form method="POST" action="enter-pin.php">
	<input type="text" name="pin">
	
	<input type="submit" name="enter_pin">
</form>

And when this form submits, we are simply going to check the entered pin with database and login the user. We also need to empty the pin from database so that it cannot be used again.

<?php

	session_start();

	if (isset($_POST["enter_pin"]))
	{
		$pin = $_POST["pin"];
		$user_id = $_SESSION["user"]->id;

		$conn = mysqli_connect("localhost", "root", "", "tutorials");
		
		$sql = "SELECT * FROM users WHERE id = '$user_id' AND pin = '$pin'";
		$result = mysqli_query($conn, $sql);

		if (mysqli_num_rows($result) > 0)
		{
			$sql = "UPDATE users SET pin = '' WHERE id = '$user_id'";
			mysqli_query($conn, $sql);

			$_SESSION["user"]->is_verified = true;
			header("Location: index.php");
		}
		else
		{
			echo "Wrong pin";
		}
	}

?>

Now after verification user will be redirected to “index.php” which is by default the home of every website. You might already have a check to prevent the user from entering this page without login. Just need to change few things,

  • Check if user is logged in and pin verified.
  • A switch to turn on/off 2-factor authentication.
  • A link to logout the user.

index.php

<?php

session_start();
$conn = mysqli_connect("localhost", "root", "", "tutorials");

if (isset($_SESSION["user"]) && $_SESSION["user"]->is_verified)
{

	$user_id = $_SESSION["user"]->id;

	if (isset($_POST["toggle_tfa"]))
	{
		$is_tfa_enabled = $_POST["is_tfa_enabled"];

		$sql = "UPDATE users SET is_tfa_enabled = '$is_tfa_enabled' WHERE id = '$user_id'";
		mysqli_query($conn, $sql);

		echo "Settings changed";
	}

	$sql = "SELECT * FROM users WHERE id = '$user_id'";
	$result = mysqli_query($conn, $sql);
	$row = mysqli_fetch_object($result);

	?>

	<form method="POST" action="index.php">
		<h1>Enable TFA</h1>

		<input type="radio" name="is_tfa_enabled" value="1" <?php echo $row->is_tfa_enabled ? "checked" : ""; ?>> Yes
		<input type="radio" name="is_tfa_enabled" value="0" <?php echo !$row->is_tfa_enabled ? "checked" : ""; ?>> No

		<input type="submit" name="toggle_tfa">
	</form>

	<a href="logout.php">
		Logout
	</a>

	<?php
}
else
{
	header("Location: login.php");
}

We are displaying 2 radio buttons and an ability to automatically select the radio based on enable or disability of 2-factor authentication. You can use the same condition on all pages which you want to show to only authenticated users. Now we just need to logout the user. We have created a link with text “Logout” which when clicked will redirect the user to “logout.php” page.

logout.php

<?php

session_start();
unset($_SESSION["user"]);
session_destroy();
header("Location: login.php");

?>

Re-send OTP after 30 seconds

If you want to resend the OTP after 30 seconds, first, we need to call a Javascript function using setTimeout method.

setTimeout(function () {
// [call AJAX here]
}, 30 * 1000)

Then we need to call an AJAX to resend the OTP. The following code goes in the [call AJAX here] section:

const form = document.getElementById("form-login")

const ajax = new XMLHttpRequest()
ajax.open("POST", "login", true)

ajax.onreadystatechange = function () {
	if (this.readyState == 4) {
		if (this.status == 200) {
			console.log(this.responseText)
		}
	}
}

const formData = new FormData(form)
ajax.send(formData)

Show timer

If you want to show the timer for 30 seconds. First, create a <div> where the timer text will be displayed.

<div id="timer"></div>

Then we need to call a setInterval function to call each second and decrement the value.

let seconds = 30

const timerInternval = setInterval(function () {
	seconds--
	document.getElementById("timer").innerHTML = seconds
	if (seconds <= 0) {
		stopInterval(timerInternval)
	}
}, 1000)

That’s how you can add 2 factor login authentication with timer in your website. If you want to know how to add “reset password” feature in your website, check our free tutorial here.

About Author

Adnan Afzal

Developer, technology evangalist, loves to read and write about new technologies. Madly in love with backend development. Follow him on social media.