INTRODUCTION
EXTRA SECURITY
Welcome to a tutorial on how to create a one time password (OTP) in PHP and MySQL. Need some extra security for your system? OTP is one of the ways to do so, and it can be used for literally anything – Resetting a user account, confirming an email address, payments, securing transactions, and so much more. So just how do we create a one-time password in PHP? This guide will walk you through the exact steps – Read on to find out.
I have included a zip file with all the source code at the end of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.
CONFESSION
AN HONEST DISCLOSURE
Quick, hide your wallets! I am an affiliate partner of Google, eBay, Adobe, Bluehost, Clickbank, and more. There are affiliate links and advertisements throughout this website. Whenever you buy things from the evil links that I recommend, I will make a commission. Nah. These are just things to keep the blog going, and allows me to give more good stuff to you guys - for free. So thank you if you decide to pick up my recommendations!
NAVIGATION
TABLE OF CONTENTS
Section A | Section B | Section C |
Extra | Closing |
SECTION A
THE DATABASE
First, we will need to establish a simple database table to store the generated OTP, timestamp, and stuff.
OTP TABLE
CREATE TABLE `otp` (
`id` int(11) NOT NULL,
`otp_pass` varchar(12) NOT NULL,
`otp_timestamp` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`otp_tries` tinyint(1) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `otp`
ADD PRIMARY KEY (`id`);
Field | Description |
id | The primary key, unique identifier. This can be the user ID, transaction ID, order ID – Whatever you want to track. |
otp_pass | The one-time password. |
otp_timestamp | Time at which the OTP is generated, used to calculate the expiry time. |
otp_tries | The number of challenge attempts. Used to stop brute force attacks. |
That is the gist of it, please feel free to make changes to fit your own project.
SECTION B
LIBRARY FILES
Next, we will need a PHP library to deal with the OTP magic and processes.
CONFIG FILE
This is just a config file to hold all your settings – Remember to change the database and OTP settings to your own.
OTP LIBRARY
pdo = new PDO(
$str, DB_USER, DB_PASSWORD, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]
);
return true;
}
// ERROR - DO SOMETHING HERE
// THROW ERROR MESSAGE OR SOMETHING
catch (Exception $ex) {
print_r($ex);
die();
}
}
function __destruct() {
// __destruct() : close connection when done
if ($this->stmt !== null) { $this->stmt = null; }
if ($this->pdo !== null) { $this->pdo = null; }
}
function exec($sql, $data=null) {
// exec() : run insert, replace, update, delete query
// PARAM $sql : SQL query
// $data : array of data
try {
$this->stmt = $this->pdo->prepare($sql);
$this->stmt->execute($data);
$this->lastID = $this->pdo->lastInsertId();
} catch (Exception $ex) {
$this->error = $ex;
return false;
}
$this->stmt = null;
return true;
}
function fetch ($sql, $cond=null, $sort=null) {
// fetch() : perform select query (single row expected)
// returns an array of column => value
// PARAM $sql : SQL query
// $cond : array of conditions
// $sort : custom sort function
$result = [];
try {
$this->stmt = $this->pdo->prepare($sql);
$this->stmt->execute($cond);
if (is_callable($sort)) {
while ($row = $this->stmt->fetch(PDO::FETCH_NAMED)) {
$result = $sort($row);
}
} else {
while ($row = $this->stmt->fetch(PDO::FETCH_NAMED)) {
$result = $row;
}
}
} catch (Exception $ex) {
$this->error = $ex;
return false;
}
// Return result
$this->stmt = null;
return count($result)==0 ? false : $result ;
}
/* [OTP FUNCTIONS] */
function randompass ($len) {
// randompass() : create a random password
// PARAM $len : random password length
// Change this alphabets set as needed. For example, if you want numbers only
// $alphabets = "0123456789";
$alphabets = "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789";
$count = strlen($alphabets) - 1;
$pass = "";
for ($i=0; $irandompass(OTP_LEN);
// Database entry
$sql = "REPLACE INTO `otp` (`id`, `otp_pass`, `otp_timestamp`, `otp_tries`) VALUES (?, ?, ?, ?)";
$data = [$id, $pass, date("Y-m-d H:i:s"), 0];
$ok = $this->exec($sql, $data);
// Result
return [
"status" => $ok ? 1 : 0,
"pass" => $ok ? $pass : ""
];
}
function challenge ($id, $pass) {
// challenge() : challenge the OTP
// Get the OTP entry
$sql = "SELECT * FROM `otp` WHERE `id`=?";
$data = [$id];
$otp = $this->fetch($sql, $data);
// OTP entry not found!?
if ($otp === false) {
$this->error = "OTP transaction not found.";
return false;
}
// Too many tries
if ($otp['otp_tries'] >= OTP_TRIES) {
$this->error = "Too many tries for OTP.";
return false;
}
// Expired
$validTill = strtotime($otp['otp_timestamp']) + (OTP_VALID * 60);
if (strtotime("now") > $validTill) {
$this->error = "OTP has expired.";
return false;
}
// Incorrect password
if ($pass != $otp['otp_pass']) {
// Add a strike to number of tries
$strikes = $otp['otp_tries'] + 1;
$sql = "UPDATE `otp` SET `otp_tries`=? WHERE `id`=?";
$data = [$strikes, $id];
$this->exec($sql, $data);
// Security lock down - Too many tries
if ($strikes >= OTP_TRIES) {
$this->lockdown();
}
// Return result
$this->error = "Incorrect OTP.";
return false;
}
// OK - CORRECT OTP
/* You can delete the OTP at this point if you want
* $sql = "DELETE FROM `otp` WHERE `id`=?";
* $data = [$id];
* $this->exec($sql, $data);
*/
return true;
}
function lockdown () {
// lockdown() : failed challenge multiple times
// @TODO - SECURITY LOCKDOWN
// Suspend the user account?
// Maybe temporary lock for a few hours to prevent spam?
// Send warning email + SMS?
// All up to your own system.
}
}
?>
THE EXPLANATION
Yikes! This library looks complicated at first, but there are actually 2 parts to it –
- Support functions that deal with the database.
- Actual functions that deal with the OTP.
So if you are using your own PHP framework, please feel free to remove the database support functions, and update the OTP to use your framework.
Function | Description |
__construct | The constructor. Automatically connects to the database when the OTP object is created. |
__destruct | The destructor. Automatically disconnects from the database when the OTP object is destroyed. |
exec | Runs a insert, replace, update, or delete SQL query. |
fetch | Perform a select query. |
Function | Description |
randompass | Generates a random password for the OTP. |
init | Initiates and creates a new OTP. |
challenge | Challenge the OTP. |
lockdown | Security lockdown. An empty function for you to fill in – This will run after the user has failed the OTP challenge too many times. |
SECTION C
IMPLEMENTATION STEPS
With the foundations now established, the last step is to just use the OTP library into the project. As everyone has a different use for OTP, we will only walk through a very generic “how to implement” in this section.
1) GENERATE THE OTP
init(999);
// (3) SEND THE ONE-TIME PASSWORD TO THE USER
// (3A) OK - SEND OTP TO USER
if ($result['status'] == 1) {
// Send message via email, SMS, messaging, or whatever communication gateway you use
echo "Please visit https://your-site.com/page/ to verify. This password is valid for " . OTP_VALID . " minutes - " . $result['pass'];
}
// ERROR
else {
echo "ERROR - ". $libOTP->error;
}
?>
The very first step of the process is to generate an OTP using the init()
function – Just pass in the unique ID that you want to track, and remember to send the OTP and verification page to the user.
2) LANDING PAGE – OTP CHALLENGE
OTP Challenge Demo Page
This is an example landing page for the user to enter the OTP – Which is just a simple HTML form.
3) OTP VERIFICATION
challenge($_POST['id'], $_POST['pass']);
} else {
$libOTP->error = "Invalid OTP password and ID";
}
// (3) RESULTS
echo json_encode([
"status" => $result ? 1 : 0,
"message" => $result ? "OK" : $libOTP->error
]);
?>
Finally, we only have to verify the password that the user has provided.
EXTRA
DOWNLOAD
Finally, here is the download link as promised.
QUICK START
Skipped the entire tutorial? Here is a quick introduction to how the folders are being organized in the zip file.
-
ROOT
The project root folder.-
lib
Where we store all the config and library files. -
sql
Contains the SQL for the OTP table.
-
So just import the SQL, update the settings in lib/config.php
, and start tracing from 1-initialize.php
.
SOURCE CODE DOWNLOAD
Click here to download the source code, I have released it under the MIT license, so feel free to build on top of it or use it in your own project.
CLOSING
WHAT’S NEXT?
Thank you for reading, and we have come to the end of this guide. I hope that it has helped you in your project, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!
The post 3 Steps to Create One Time Password (OTP) in PHP appeared first on Code Boxx.