Very Simple OTP In PHP MySQL (Free Download)

Welcome to a quick tutorial on how to create a one-time password (OTP) in PHP and MySQL. Need extra security for ordering, confirmation, payments, or transactions?

An OTP process in PHP typically involves:

  • Generating a random OTP on request. Save it into the database, and send it to the user.
  • The user accesses a challenge page and enters the OTP. Verify and proceed if the given OTP is valid.

That’s the gist of it, let us walk through an actual example – Read on!

 

 

TABLE OF CONTENTS

 

DOWNLOAD & NOTES

Here is the download link to the example code, so you don’t have to copy-paste everything.

 

EXAMPLE CODE DOWNLOAD

Source code on GitHub Gist

Just click on “download zip” or do a git clone. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

SORRY FOR THE ADS...

But someone has to pay the bills, and sponsors are paying for it. I insist on not turning Code Boxx into a "paid scripts" business, and I don't "block people with Adblock". Every little bit of support helps.

Buy Me A Coffee Code Boxx eBooks

 

 

PHP MYSQL ONE-TIME PASSWORD

All right, let us now get into the details of building an OTP system in PHP and MYSQL.

 

TUTORIAL VIDEO

 

PART 1) DATABASE TABLE

1-database.sql
CREATE TABLE `otp` (
  `email` varchar(255) NOT NULL,
  `pass` varchar(255) NOT NULL,
  `timestamp` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
ALTER TABLE `otp`
  ADD PRIMARY KEY (`user_email`);
Field Description
email Primary key. This can also be the user ID, transaction ID, order ID – Whatever you want to add an OTP to.
pass The one-time password.
timestamp Time at which the OTP is generated. Used to prevent brute force attacks and add an expiry time.

That’s all. Feel free to make changes as required.

 

 

PART 2) PHP OTP LIBRARY

2A) LIBRARY INIT

2-otp.php
<?php
class OTP {
  // (A) CONSTRUCTOR - CONNECT TO DATABASE
  private $pdo = null;
  private $stmt = null;
  public $error = "";
  function __construct() {
    $this->pdo = new PDO(
      "mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=".DB_CHARSET,
      DB_USER, DB_PASSWORD, [
      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
      PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    ]);
  }
 
  // (B) DESTRUCTOR - CLOSE CONNECTION
  function __destruct() {
    if ($this->stmt !== null) { $this->stmt = null; }
    if ($this->pdo !== null) { $this->pdo = null; }
  }
 
  // (C) HELPER - RUN SQL QUERY
  function query ($sql, $data=null) : void {
    $this->stmt = $this->pdo->prepare($sql);
    $this->stmt->execute($data);
  }
  // ...
}

// (F) DATABASE SETTINGS - CHANGE TO YOUR OWN!
define("DB_HOST", "localhost");
define("DB_NAME", "test");
define("DB_CHARSET", "utf8mb4");
define("DB_USER", "root");
define("DB_PASSWORD", "");

// (G) OTP SETTINGS
define("OTP_VALID", "15"); // otp valid for n minutes
define("OTP_LEN", "8");    // otp length

// (H) NEW OTP OBJECT
$_OTP = new OTP();

This library probably looks intimidating to the newbies. But keep calm and look closely.

  • (A, B, H) When $_OTP = new OTP() is created, the constructor will automatically connect to the database. The destructor closes the connection when done.
  • (C) query() A simple helper function to run an SQL query.
  • (F) Database settings – Remember to change to your own.
  • (G) A bunch of OTP settings, change these if you want.

 

 

2B) OTP REQUEST

2-otp.php
// (D) GENERATE OTP
function generate ($email) {
  // (D1) CHECK EXISTING OTP REQUEST
  $this->query("SELECT * FROM `otp` WHERE `email`=?", [$email]);
  $otp = $this->stmt->fetch();
  if (is_array($otp) && (strtotime("now") < strtotime($otp["timestamp"]) + (OTP_VALID * 60))) {
    $this->error = "You already have a pending OTP.";
    return false;
  }
 
  // (D2) CREATE RANDOM OTP
  $alphabets = "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789";
  $count = strlen($alphabets) - 1;
  $pass = "";
  for ($i=0; $i<OTP_LEN; $i++) { $pass .= $alphabets[rand(0, $count)]; }
  $this->query(
    "REPLACE INTO `otp` (`email`, `pass`) VALUES (?,?)",
    [$email, password_hash($pass, PASSWORD_DEFAULT)]
  );
 
  // (D3) SEND VIA EMAIL
  if (@mail($email, "Your OTP",
    "Your OTP is $pass. Enter at <a href='http://localhost/3b-challenge.php'>SITE</a>.",
    implode("\r\n", ["MIME-Version: 1.0", "Content-type: text/html; charset=utf-8"])
  )) { return true; }
  else {
    $this->error = "Failed to send OTP email.";
    return false;
  }
}

As in the introduction above, “the first step” involves the user requesting an OTP.

  • (D1) Check if the user already has a pending OTP. Don’t send another one until the current has expired – This also prevents the chances of brute force attacks.
  • (D2) Generate a random OTP, and save it into the database.
  • (D3) Send the OTP to the user via email.

 

 

2C) OTP CHALLENGE & VERIFY

2-otp.php
// (E) CHALLENGE OTP
function challenge ($email, $pass) {
  // (E1) GET THE OTP ENTRY
  $this->query("SELECT * FROM `otp` WHERE `email`=?", [$email]);
  $otp = $this->stmt->fetch();
 
  // (E2) CHECK - NOT FOUND
  if (!is_array($otp)) {
    $this->error = "The specified OTP request is not found.";
    return false;
  }
 
  // (E3) CHECK - EXPIRED
  if (strtotime("now") > strtotime($otp["timestamp"]) + (OTP_VALID * 60)) {
    $this->error = "OTP has expired.";
    return false;
  }
 
  // (E4) CHECK - INCORRECT PASSWORD
  if (!password_verify($pass, $otp["pass"])) {
    $this->error = "Incorrect OTP.";
    return false;
  }
 
  // (E5) OK - DELETE OTP REQUEST
  $this->query("DELETE FROM `otp` WHERE `email`=?", [$email]);
  return true;
}

After the user clicks on the link and inputs the OTP – This function will do the verification.

  • (E1 to E4) Very straightforward, get the OTP from the database and do the necessary checks.
  • (E5) Clear the OTP and return true if valid.

The end. Yep, this library literally only has 2 “actual functions”.

 

PART 3) OTP HTML PAGES

3A) REQUEST FOR OTP

3a-request.php
<!-- (A) OTP REQUEST FORM -->
<form method="post" target="_self">
  <label>Email</label>
  <input type="email" name="email" required value="jon@doe.com">
  <input type="submit" value="Request OTP">
</form>
 
<?php
// (B) PROCESS OTP REQUEST
if (isset($_POST["email"])) {
  require "2-otp.php";
  $pass = $_OTP->generate($_POST["email"]);
  echo $pass ? "<div class='note'>OTP SENT.</div>" : "<div class='note'>".$_OTP->error."</div>" ;
}
?>

  1. Just a dummy HTML form to request for the OTP.
  2. When the form is submitted, we use $_OTP->generate(EMAIL) to create and send the OTP to the user.

P.S. If you have an existing user system, you can just use the current signed-in user. Example – $_OTP->generate($_SESSION["user"]["email"]).

 

 

3B) OTP CHALLENGE PAGE

3b-challenge.php
<form method="post" target="_self">
  <label>Email</label>
  <input type="email" name="email" required value="jon@doe.com">
  <label>OTP</label>
  <input type="password" name="otp" required>
  <input type="submit" value="Go">
</form>
 
<?php
// (B) PROCESS OTP CHALLENGE
if (isset($_POST["email"])) {
  require "2-otp.php";
  $pass = $_OTP->challenge($_POST["email"], $_POST["otp"]);
  // @TODO - DO SOMETHING ON VERIFIED
  echo $pass ? "<div class='note'>OTP VERIFIED.</div>" : "<div class='note'>".$_OTP->error."</div>" ;
}
?>

  1. The user clicks on the link in the email, lands on this page – Enters the OTP into this form.
  2. This shouldn’t be a mystery anymore. Use $_OTP->challenge(EMAIL, OTP) to verify the entered password against the database. Complete this on your own – Proceed to do whatever is required on verification.

P.S. If you have an existing user system once again, you can remove the email field on the page and use the current user. Example – $_OTP->challenge($_SESSION["user"]["email"], $_POST["otp"]).

 

EXTRAS

That’s all for this guide, and here is a small section on some extras and links that may be useful to you.

 

MORE SECURITY

Before the “security experts” start to scream “not safe” – Of course, there is no such thing as an impervious security system. This is only a very simple example, a starting point, a barebones system. At the very least, I will recommend to:

  • Add a tries TINYINT(1) field to the database.
  • In function challenge(), add a strike whenever the password is wrong – tries = tries + 1.
  • When the number of tries hits a limit, do something – if (tries == 3) { LOCK ACCOUNT? MANUALLY CALL ADMIN? SUSPEND TRANSACTION? }
  • Enforce the use of HTTPS on your website/app.

 

LINKS & REFERENCES

 

THE END

Thank you for reading, and we have come to the end of this guide. I hope that it has helped you with your project, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!

5 thoughts on “Very Simple OTP In PHP MySQL (Free Download)”

  1. EDIT – SUMMARY
    This miserable twat copy-and-pasted code from above, then insisted “your code has errors” and “my Dreamweaver is not broken”. Could not even give a single runtime error message, don’t even know what “runtime” means. Took offense and resolved to lowly personal attacks when proven wrong.

    Wake up your bl**dy idea – Dreamweaver only supports up to PHP 7.1 at the time of writing. That is already way past its end of life. Go figure it out yourself.

    https://code-boxx.com/faq/#nobad
    https://code-boxx.com/faq/#nolike
    https://code-boxx.com/faq/#badatti

  2. Hi, This post was very useful
    Please can you post some thing similar but with a file that lets the otp be sent to a client email
    For example, a client signs up on my website as a user and next time he wants to login he is requested to check is email for an OTP before he can proceed.. i did try but i keep getting an error saying email not found on database… when i make a test run. Please help
    Thank you

Leave a Comment

Your email address will not be published. Required fields are marked *