Forgotten Password Recovery With PHP MySQL – Free Source Code

Welcome to a quick tutorial on how to create a forgotten password recovery script with PHP and MySQL. Having trouble with a system that requires manual password reset?

An automated password recovery system generally involves 3 steps:

  1. The user accesses the “forgot password” page and makes a reset request. 
  2. The system then generates a random hash and sends a reset confirmation link to the user’s registered email.
  3. Lastly, the user clicks on the confirmation link; The system verifies the hash and proceeds with the password reset.

That covers the overview of the process, and let us walk through an actual 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 The Database Reset Request
Reset Confirmation Useful Bits & Links The End

 

DOWNLOAD & NOTES

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

 

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.

 

QUICK NOTES

  • Create a test database and import the 1-database.sql file.
  • Update the database settings in 2a-common.php to your own.
  • 2b-forgot.php is the “forgot password” page, and will send a link to the user upon submission. Change the email settings in B4 to your own.
  • 2c-reset.php is the password reset confirmation page.

If you spot a bug, please feel free to comment below. I try to answer 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.

 

 

THE DATABASE

All right, let us now get started with a simple database to handle the users and password reset requests.

 

DUMMY USER TABLE

1-database.sql
CREATE TABLE `users` (
  `user_id` int(11) NOT NULL,
  `user_email` varchar(255) NOT NULL,
  `user_name` varchar(255) NOT NULL,
  `user_password` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `users` (`user_id`, `user_email`, `user_name`, `user_password`) VALUES
(1, 'john@doe.com', 'John Doe', '123456');

ALTER TABLE `users`
  ADD PRIMARY KEY (`user_id`),
  ADD UNIQUE KEY `user_email` (`user_email`),
  ADD KEY `user_name` (`user_name`);

ALTER TABLE `users`
  MODIFY `user_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;

This is just a dummy user table that we will use in this example – You should already have your own users table somewhere… Or feel free to build on top of this one.

 

PASSWORD RESET TABLE

1-database.sql
CREATE TABLE `password_reset` (
  `user_id` int(11) NOT NULL,
  `reset_hash` varchar(64) NOT NULL,
  `reset_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

ALTER TABLE `password_reset`
  ADD PRIMARY KEY (`user_id`);
  • user_id– The user who made the “forgot password” request.
  • reset_hash – Random hash that is generated upon making the reset request. Will also be sent to the user’s email for verification; If the hash in the reset link is the same as in the database, we can confirm it is a valid request.
  • reset_time – Time when the request is made, to prevent spamming.

 

 

FORGOT PASSWORD REQUEST

Moving on, let us build the forgot password page that will generate and send the reset link to the user via email.

 

COMMON DATABASE SCRIPT

2a-common.php
<?php
// (A) DATABASE SETTINGS
// ! CHANGE SETTINGS TO YOUR OWN
$dbhost = "localhost";
$dbname = "test";
$dbchar = "utf8";
$dbuser = "root";
$dbpass = "";
 
// (B) SETTINGS
$prvalid = 300; // Password reset is valid for 300 seconds
 
// (C) CONNECT TO DATABASE
try {
  $pdo = new PDO(
    "mysql:host=$dbhost;dbname=$dbname;charset=$dbchar",
    $dbuser, $dbpass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
  );
} catch (Exception $ex) {
  die($ex->getMessage());
}

This one should be self-explanatory, just a simple “shared common script” that connects to the database. Remember to change the settings to your own, and also, this should normally be in a core or class library… I am just leaving it as a “flat PHP file” for the sake of simplicity here.

 

 

FORGOT PASSWORD PAGE

2b-forgot.php
<!-- (A) PASSWORD RESET FORM -->
<form method="post" target="_self">
  Email:
  <input type="email" name="user_email" required value="john@doe.com"/>
  <input type="submit" value="Reset Password"/>
</form>
 
<!-- (B) PROCESS PASSWORD RESET REQUEST -->
<?php
if (isset($_POST['user_email'])) {
  // (B1) CONNECT TO DATABASE
  require "2a-common.php";
 
  // (B2) CHECK IF VALID USER
  $stmt = $pdo->prepare("SELECT * FROM `users` WHERE `user_email`=?");
  $stmt->execute([$_POST['user_email']]);
  $user = $stmt->fetch(PDO::FETCH_ASSOC);
  $result = is_array($user)
          ? "" 
          : $_POST['user_email'] . " is not registered." ;
 
  // (B3) CHECK PREVIOUS REQUEST (PREVENT SPAM)
  if ($result == "") {
    $stmt = $pdo->prepare("SELECT * FROM `password_reset` WHERE `user_id`=?");
    $stmt->execute([$user['user_id']]);
    $request = $stmt->fetch(PDO::FETCH_ASSOC);
    $now = strtotime("now");
    if (is_array($request)) {
      $expire = strtotime($request['reset_time']) + $prvalid;
      if ($now < $expire) { $result = "Please try again later"; }
    }
  }
 
  // (B4) CHECKS OK - CREATE NEW RESET REQUEST
  if ($result == "") {
    // RANDOM HASH
    $hash = md5($user['user_email'] . $now);
 
    // DATABASE ENTRY
    $stmt = $pdo->prepare("REPLACE INTO `password_reset` VALUES (?,?,?)");
    $stmt->execute([$user['user_id'], $hash, date("Y-m-d H:i:s")]);
 
    // SEND EMAIL - CHANGE TO YOUR OWN!
    $from = "admin <your@email.com>";
    $subject = "Password reset";
    $header = implode("\r\n", [
      "From: $from",
      "MIME-Version: 1.0",
      "Content-type: text/html; charset=utf-8"
    ]);
    $link = "http://localhost/2c-reset.php?i=".$user['user_id']."&h=".$hash;
    $message = "<a href='$link'>Click here to reset password</a>";
    if (!@mail($user['user_email'], $subject, $message, $header)) {
      $result = "Failed to send email!";
    }
  }
 
  // (B5) RESULTS
  if ($result=="") { $result = "Email has been sent - Please click on the link in the email to confirm."; }
  echo "<div>$result</div>";
}
?>

Yikes! This one looks confusing, but don’t be intimidated. Follow through part-by-part, and it is actually very straightforward:

  • A – Is nothing but a simple HTML form with only one email field.
  • B – When the HTML form is submitted, this PHP part will handle the checks and processing.
    • B1 – Remember the common script above? We start by making a connection to the database.
    • B2 – The first check, we get the email against the database, make sure it is a registered user.
    • B3 – For this second check, we get the previous password reset request (if any), and check the timestamp. This is to prevent people from abusing and spamming the system with too many reset requests.
    • B4 – Lastly, we deal with the actual reset request when all the checks are done. Generate a random hash, create a new reset entry in the database, and send the reset link to the user.

 

 

PASSWORD RESET CONFIRMATION

When the user clicks on the reset link in the email, this is where the actual password reset happens.

 

RESET CONFIRMATION PAGE

2c-reset.php
<?php
$result = "";
if (isset($_GET['i']) && isset($_GET['h'])) {
  // (A) CONNECT TO DATABASE
  require "2a-common.php";
 
  // (B) CHECK IF VALID REQUEST
  $stmt = $pdo->prepare("SELECT * FROM `password_reset` WHERE `user_id`=?");
  $stmt->execute([$_GET['i']]);
  $request = $stmt->fetch(PDO::FETCH_ASSOC);
  if (is_array($request)) {
    if ($request['reset_hash'] != $_GET['h']) { $result = "Invalid request"; }
  } else { $result = "Invalid request"; }
 
  // (C) CHECK EXPIRED
  if ($result=="") {
    $now = strtotime("now");
    $expire = strtotime($request['reset_time']) + $prvalid;
    if ($now >= $expire) { $result = "Request expired"; }
  }
 
  // (D) PROCEED PASSWORD RESET
  if ($result=="") {
    // RANDOM PASSWORD
    $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-=+?";
    $password = substr(str_shuffle($chars),0 ,8); // 8 characters
 
    // UPDATE DATABASE
    $stmt = $pdo->prepare("UPDATE `users` SET `user_password`=? WHERE `user_id`=?");
    $stmt->execute([$password, $_GET['i']]);
    $stmt = $pdo->prepare("DELETE FROM `password_reset` WHERE `user_id`=?");
    $stmt->execute([$_GET['i']]);
 
    // SHOW RESULTS (UPDATED PASSWORD)
    $result = "Password has been updated to $password. Please login and change it.";
  }
}
 
// (E) INVALID REQUEST
else { $result = "Invalid request"; }
 
// (F) OUTPUT RESULTS
echo "<div>$result</div>";
?>

Looks complicated again, but keep calm and look carefully.

  • A – Same old connect to the database first.
  • B – Notice that there are 2 $_GET parameters in the reset link above? $_GET['i'] is the user ID and $_GET['h'] is the security hash. We will check against the database to make sure that they match before proceeding with the actual reset.
  • C – Another check, just to make sure the request has not “timeout” and is still valid.
  • D – The actual password reset, simply generates a random password and shows it on the screen… But personally, I think it is better to email this to the user.
  • E – Happens when some dumbo tries to access this page without a valid $_GET['i'] and $_GET['h'].
  • F – Outputs the processing result.

 

 

USEFUL BITS & LINKS

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

 

BAREBONES ONLY – DO YOUR HOMEWORK

Before the grandmaster code ninja trolls start to spew acid – This is a barebones example… There are a lot of things that need and can be done better.

  • Cosmetics, of course.
  • Security – Add a CAPTCHA to further prevent spam.
  • Password encryption.
  • Better organize the scripts into libraries.
  • Enforce a temporary password, force the user to change it upon login.

So yep, I will just leave more links below that might further help you.

 

LINKS

 

THE END

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

8 thoughts on “Forgotten Password Recovery With PHP MySQL – Free Source Code”

  1. I used your code and it worked well, after I made it fit my page. It is still a work in process, =LINK REMOVED=
    It is the “forgot your password section”
    I was hoping to also use it to make a user – reset – their password , such that they can choose a password, but I can’t seem to allow user input into 2C-reset. Rather than a random password. Any thoughts?

  2. I was able to follow and use your code for the random new password. I am also trying to allow a user to enter in the password of their choosing. But can’t modify your code to do that. Do you have any thoughts?

    1. Update 2c-reset.php, here is a possible solution:

      1) Put the user ID and hash check (A to C) into a function check().
      2) Run check(), then show a change password form instead.
      3) When the form is submitted, run check() again, update the password in the database.

      But otherwise, I cannot help you with your personal project any further. Good luck.

      https://code-boxx.com/faq/#help

  3. Hello,
    Thank you for your amazing tutorials and code, it is helping me alot in learning PHP.
    I’m trying to join this code with your other tutorial’s, by integrating the various functions into one file.
    I see that the code is very similar, but how can i integrate both codes to avoid redundancy?
    I have successfully integrated the common code from your user registration and user login tutorials.

    1. Build your own set of libraries, you decide how. Don’t want to restrict the way you structure your future projects, read examples from all over the Internet:

      https://www.google.com/search?q=php+database+class
      https://www.tutorialspoint.com/php/php_object_oriented.htm
      https://code-boxx.com/simple-php-mvc-example/

      Or study my existing projects if you want. Download the core and user module. See lib/Core.php and how the modules use the core database functions.
      https://code-boxx.com/core-boxx-php-rapid-development-framework/

  4. I like your Tut and it is working but when I receive email password reset link it is in this text format.
    Can you please show how to set it to be html read that link is in the message ?

    Thank you

Leave a Comment

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