Simple One Time Password (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 some extra security for resetting a user account, confirming an email address, payments, or securing transactions?

Creating and verifying an OTP in PHP is actually a straightforward process:

  • Generate a random one-time password on request. Save it into the database, and send it to the user via email (or SMS).
  • The user accesses a challenge page and enters the OTP. Verify and proceed if the given OTP is valid.

But just how exactly are one-time passwords done in PHP? Let us walk you through an example in this guide – Read on!

ⓘ I have included a zip file with all the source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.

 

 

TABLE OF CONTENTS

 

DOWNLOAD & NOTES

Firstly, here is the download link to the example code as promised.

 

QUICK NOTES

  • Create a test database and import the 1-database.sql file.
  • Change the database settings in 2-otp.php to your own, you may also want to fill up all the @TODO gaps too.
  • Launch 3a-request.php to generate an OTP request, 3b-challenge.php for the challenge.
If you spot a bug, feel free to comment below. I try to answer short questions too, but it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming.

 

SCREENSHOT

 

EXAMPLE 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.

 

 

PHP MYSQL ONE-TIME PASSWORD

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

 

PART 1) DATABASE TABLE

1-database.sql
CREATE TABLE `otp` (
  `user_email` varchar(255) NOT NULL,
  `otp_pass` varchar(255) NOT NULL,
  `otp_timestamp` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `otp_tries` tinyint(1) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
ALTER TABLE `otp`
  ADD PRIMARY KEY (`user_email`);
Field Description
user_email The primary key. This can actually 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.

 

 

PART 2) PHP OTP LIBRARY

2A) LIBRARY INIT

2-otp.php
<?php
class OTP {
  // (A) CONSTRUCTOR - CONNECT TO DATABASE
  protected $pdo = null;
  protected $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) {
    $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) ONE-TIME PASSWORD SETTINGS
define("OTP_VALID", "15"); // valid for x minutes
define("OTP_TRIES", "3"); // max tries
define("OTP_LEN", "8"); // password length

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

Before the newbies foam in the mouth, keep calm, and let’s go through the library section by section.

  • (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) @TODO - CHECK IF EMAIL IS VALID USER?
  $this->query("SELECT * FROM `users` WHERE `user_email`=?", [$email]);
  if (!is_array($this->stmt->fetch())) {
    $this->error = "$email is not a valid user";
    return false;
  } */
 
  // (D2) CHECK FOR EXISTING OTP REQUEST
  $this->query("SELECT * FROM `otp` WHERE `user_email`=?", [$email]);
  if (is_array($this->stmt->fetch())) {
    // @TODO - ALLOW NEW REQUEST IF EXIPRED?
    // $validTill = strtotime($otp["otp_timestamp"]) + (OTP_VALID * 60);
    // if (strtotime("now") > $validTill) { DELETE OLD REQUEST } else { ERROR }
    $this->error = "You already have a pending OTP.";
    return false;
  }
 
  // (D3) CREATE RANDOM PASSWORD
  $alphabets = "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789";
  $count = strlen($alphabets) - 1;
  $pass = "";
  for ($i=0; $i<OTP_LEN; $i++) { $pass .= $alphabets[rand(0, $count)]; }
  $this->query(
    "REPLACE INTO `otp` (`user_email`, `otp_pass`) VALUES (?,?)",
    [$email, password_hash($pass, PASSWORD_DEFAULT)]
  );
 
  // (D4) SEND VIA EMAIL
  // @TODO - FORMAT YOUR OWN "NICE EMAIL" OR SEND VIA SMS.
  $subject = "Your OTP";
  $body = "Your OTP is $pass. Enter it at http://site.com/3b-challenge.php within ".OTP_VALID." minutes.";
  if (@mail($email, $subject, $body)) { 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 – This function deals with that. The core parts are pretty much:

  • (D3) Generate a random password and save it into the database.
  • (D4) Send the random password to the user.

That’s all. But you will want to complete this function on your own:

  • (D1) If the OTP is open to registered users only – Do your own check here.
  • (D2) Allow the user to request another OTP when a previous one has expired? Or does the user have to manually contact an admin?
  • (D4) Create your own “nice email”, or integrate your own SMS gateway here.

 

 

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 `user_email`=?", [$email]);
  $otp = $this->stmt->fetch();
 
  // (E2) OTP ENTRY NOT FOUND
  if (!is_array($otp)) {
    $this->error = "The specified OTP request is not found.";
    return false;
  }
 
  // (E3) TOO MANY TRIES
  if ($otp["otp_tries"] >= OTP_TRIES) {
    $this->error = "Too many tries for OTP.";
    return false;
  }
 
  // (E4) EXPIRED
  $validTill = strtotime($otp["otp_timestamp"]) + (OTP_VALID * 60);
  if (strtotime("now") > $validTill) {
    $this->error = "OTP has expired.";
    return false;
  }
 
  // (E5) INCORRECT PASSWORD - ADD STRIKE
  if (!password_verify($pass, $otp["otp_pass"])) {
    $strikes = $otp["otp_tries"] + 1;
    $this->query("UPDATE `otp` SET `otp_tries`=? WHERE `user_email`=?", [$strikes, $email]);
 
    // @TODO - TOO MANY STRIKES
    // LOCK ACCOUNT? REQUIRE MANUAL VERIFICATION? SUSPEND FOR 24 HOURS?
    // if ($strikes >= OTP_TRIES) { DO SOMETHING }
 
    $this->error = "Incorrect OTP.";
    return false;
  }
 
  // (E6) ALL OK - DELETE OTP
  $this->query("DELETE FROM `otp` WHERE `user_email`=?", [$email]);
  return true;
}

After the user receives the OTP and clicks on the link, this function will deal with “the second step” – Which is to verify the OTP. Don’t think this needs much explanation… Trace through on your own, it is pretty much a whole load of checks.

P.S. You will want to complete (E5) on your own. What happens when the user enters the wrong OTP too many times? Lock their account? Suspend for a period of time?

 

 

 

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 regular 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.

 

 

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.

 

EXTRA BITS & LINKS

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

 

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 “Simple One Time Password (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 *