Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

3 Steps Forget Password Recovery Script With PHP

INTRODUCTION
AUTOMATED PASSWORD RECOVERY

Welcome to a tutorial on how to create a forgotten Password recovery script in PHP and MySQL. Congratulations on building a user authentication system for your project, code ninja. But here comes the dreaded human part where people forget their passwords… Pretty sure that manually resetting passwords is not the best solution, and so, we shall walk through how to automate that process in this guide – In a step-by-step and secure manner, of course. 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

Prelude
Overview & Assumptions

Step 1
The Database

Step 2
Library Files

Step 3
HTML Pages

Extra
Useful Bits

Extra
Source Code Download

Closing
What’s Next?

PRELUDE
OVERVIEW & ASSUMPTIONS

Before we dive into the code part, here is an overview of the reset process, and some assumptions that I have made – So that you know what to expect from this guide.

ASSUMPTIONS

Everyone should already have a user system, or it will not make any sense to want a password recovery mechanism… So in this guide, we will purely touch on password reset only. If you do not have a user system yet, I will leave a link in the extras section below to another guide on how to create one.

Also, I shall assume that most of you guys are already code ninjas who are comfortable with PHP, MySQL, HTML, CSS, and Javascript – We will not go into the tiny boring details such as “how to import SQL files”.

PROJECT FOLDERS

If you have downloaded the zip file, here is a quick introduction to how the folders are being organized. If you want to follow along step-by-step, then start by creating the following folders.

  • ROOT The project root folder, where we put the landing pages.
    • lib Where we store the config and library files.
    • sql Import these to create the necessary database tables. Delete afterward, not required for the project.

PROCESS OVERVIEW

Brace yourselves. This is going to be a very complicated process that involves 2 parts:

  • The user requests for password recovery. Enters the email address into an HTML form, and a confirmation link will be sent.
  • The user clicks on the confirmation link in the email. A random new password will be generated and sent to the user.

Done.

STEP 1
THE DATABASE

Now that we are done with the overview, let us start by building the foundations of the system – The database tables.

DUMMY USER TABLE

If you have not created a users table yet, here is a simple one that we shall use for this tutorial.

sql/users.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, '[email protected]', '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;
FieldDescription
user_idThe user ID. Primary key, auto increment.
user_emailThe user’s email address. Unique.
user_nameThe user’s full name.
user_passwordThe user’s password. Please take note – This is just a plain text field, do your own password encryption in PHP!

PASSWORD RESET REQUEST TABLE

sql/password.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`);
FieldDescription
user_idPrimary and foreign key. The user who made the reset request.
reset_hashA randomly generated security hash. Used to prevent unauthorized people from resetting the password.
reset_timeA timestamp to prevent spammers from creating multiple reset requests in a short amount of time.

STEP 2
THE LIBRARY FILES

Moving on, let us build the PHP library files that will deal with the password reset request.

CONFIG FILE

lib/config.php

This one should be self-explanatory. Just remember to change the database and password reset settings to your own.

DATABASE LIBRARY

lib/lib-db.php
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 createDB($db="", $user=DB_USER, $pass=DB_PASSWORD) {
  // createDB() : create a new database
  // PARAM $db : name of new database
  //       $user : user/owner of new database
  //       $password : password

    $sql = "CREATE DATABASE `" . $db . "`;";
    $sql .= "CREATE USER '" . $user . "'@'localhost' IDENTIFIED BY '" . $pass . "';";
    $sql .= "GRANT ALL ON `" . $db . "`.* TO '" . $user . "'@'localhost';";
    $sql .= "FLUSH PRIVILEGES;";
    try {
      $this->pdo->exec($sql);
    } catch (Exception $ex) {
      $this->error = $ex;
      return false;
    }
    return true;
  }

  function createTable($name="", $fields) {
  // createTable() : create a new table
  // PARAM $name : name of table
  //       $fields : array of fields

    $sql = "CREATE TABLE " . $name . " (";
    foreach ($fields as $f) {
      $sql .= $f . ",";
    }
    $sql = substr($sql, 0, -1) . ");";
    try {
      $this->pdo->exec($sql);
    } catch (Exception $ex) {
      $this->error = $ex;
      return false;
    }
    return true;
  }

  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 fetchAll ($sql, $cond=null, $key=null, $value=null) {
  // fetchAll() : perform select query (multiple rows expected)
  // PARAM $sql : SQL query
  //       $cond : array of conditions
  //       $key : sort in this $key=>data order, optional
  //       $value : $key must be provided. If string provided, sort in $key=>$value order. If function provided, will be a custom sort.

    $result = [];
    try {
      $this->stmt = $this->pdo->prepare($sql);
      $this->stmt->execute($cond);
      // Sort in given order
      if (isset($key)) {
        if (isset($value)) {
          if (is_callable($value)) {
            while ($row = $this->stmt->fetch(PDO::FETCH_NAMED)) {
              $result[$row[$key]] = $value($row);
            }
          } else {
            while ($row = $this->stmt->fetch(PDO::FETCH_NAMED)) {
              $result[$row[$key]] = $row[$value];
            }
          }
        } else {
          while ($row = $this->stmt->fetch(PDO::FETCH_NAMED)) {
            $result[$row[$key]] = $row;
          }
        }
      }
      // No key-value sort order
      else {
        $result = $this->stmt->fetchAll();
      }
    } catch (Exception $ex) {
      $this->error = $ex;
      return false;
    }
    // Return result
    $this->stmt = null;
    return count($result)==0 ? false : $result ;
  }

  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 ;
  }

  function fetchCol ($sql, $cond=null) {
  // fetchCol() : yet another version of fetch that returns a flat array
  // I.E. Good for one column SELECT `col` FROM `table`

    $this->stmt = $this->pdo->prepare($sql);
    $this->stmt->execute($cond);
    $result = $this->stmt->fetchAll(PDO::FETCH_COLUMN, 0);
    return count($result)==0 ? false : $result;
  }

  function start () {
  // start() : auto-commit off

    $this->pdo->beginTransaction();
  }

  function end ($commit=1) {
  // end() : commit or roll back?

    if ($commit) { $this->pdo->commit(); }
    else { $this->pdo->rollBack(); }
  }
}
?>

Holy cow! This one looks like it is capable of causing permanent head damage (PHD). But keep calm and study closely – This is actually just a “standard” PDO database library that is used to do various SQL yoga.

FunctionDescription
__constructThe constructor. Will automatically connect to the database when the object is created.
__destructThe destructor. Will automatically close the database connection when the object is destroyed.
createDBCreate a new database.
createTableCreate a new table.
execRuns an insert, replace, update, or delete SQL query.
fetchAllRuns a select SQL query. Returns a numeric array of the results – Good for multiple row queries.
fetchRuns a select SQL query. Returns an associative array of the results – Good for single row queries.
fetchColRuns a specific select SQL query on a single column only. Returns a numeric array of the results.

USERS LIBRARY

lib/lib-users.php
fetch($sql, [$email]);
    if ($user == false) {
      $this->error = "$email is not registered";
      return false;
    }

    // (2) Check for previous reset requests
    // Fights spam, stops people from brute force reset request
    // @TODO - Remove this if you don't need it
    $sql = "SELECT * FROM `password_reset` WHERE `user_id`=?";
    $request = $this->fetch($sql, [$user['user_id']]);
    $now = strtotime("now");
    if (is_array($request)) {
      $expire = strtotime($request['reset_time']) + (PR_VALID * 60);
      if ($now error = "Please try again later";
        return false;
      }
    }

    // (3) Create new reset request entry
    $sql = "REPLACE INTO `password_reset` (`user_id`, `reset_hash`, `reset_time`) VALUES (?, ?, ?)";
    $hash = md5($now . $user['user_id'] . PR_VALID); // Generates random hash - algo is up to you
    $data = [$user['user_id'], $hash, date("Y-m-d H:i:s")];
    if ($this->exec($sql, $data) == false) {
      return false;
    }

    // (4) Email user reset link
    // @TODO - Formulate a nice email
    $subject = "Password Reset";
    // Remember that the user ID and hash needs to be in the query string!
    $message = "Click here to reset your password.";
    if (@mail($user['user_email'], $subject, $message)) {
      return true;
    } else {
      $this->error = "Error sending email";
      return false;
    }
  }

  function resetB ($id, $hash) {
  // resetB() : Second part - Verify and reset password
  // PARAM $id : user ID
  //       $hash : security hash

    // (1) Verify given user ID and hash
    $sql = "SELECT * FROM `password_reset` p LEFT JOIN `users` u USING (`user_id`) WHERE p.`user_id`=? AND p.`reset_hash`=?";
    $request = $this->fetch($sql, [$id, $hash]);
    if ($request == false) {
      $this->error = "Invalid request";
      return false;
    }

    // (2) Check if still valid
    $now = strtotime("now");
    $expire = strtotime($request['reset_time']) + (PR_VALID * 60);
    if ($now >= $expire) {
      $this->error = "Invalid request";
      return false;
    }

    // (3) Remove reset request
    $sql = "DELETE FROM `password_reset` WHERE `user_id`=?";
    if (!$this->exec($sql, [$id])) {
      return false;
    }

    // (4) Generate new random password
    // @TODO - Change the password strength if you want
    // @TODO - Password encryption
    $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-=+?";
    $password = substr(str_shuffle($chars),0 ,8); // 8 characters
    $sql = "UPDATE `users` SET `user_password`=? WHERE `user_id`=?";
    if (!$this->exec($sql, [$password, $id])) {
      return false;
    }

    // (5) Email
    // @TODO - Formulate a nice email
    $subject = "New Password";
    $message = "Your new password is " . $password;
    if (@mail($request['user_email'], $subject, $message)) {
      return true;
    } else {
      $this->error = "Error sending email";
      return false;
    }
  }
}
?>

Holy Bos Taurus! This one looks like another trouble script. But again, there are only 2 functions here that will deal with the password reset. If you take some time to walk through section-by-section, they are actually very straightforward.

FunctionDescription
resetAWill deal with the first part of the reset request. Validates the given email address, creates a reset request database entry, and sends the reset link to the user via email.
resetBWill deal with the second part of the reset request. Validates the given user ID and security hash. Generates a new random password and sends it to the user via email.

STEP 3
HTML LANDING PAGES

Finally, all we need is to create the HTML landing pages.

RESET PASSWORD REQUEST PAGE

1-request.php
resetA($_POST['user_email']);
  $message = $pass ? "OK - Check your email." : $libUser->error;
}

// (B) SHOW HTML ?>


  
    
      Password Reset Request Demo
    $message
"; } ?>

Yep, this is but a simple page with 2 parts:

  • The bottom part is nothing but a regular Joe HTML form to collect the user’s email address.
  • When the user submits the form, the top part will use the libraries to handle the reset request (verify email, send reset link via email).

 

RESET CONFIRMATION PAGE

2-reset.php
resetB($_GET['u'], $_GET['h']);
  $message = $pass ? "OK - Check your email." : $libUser->error;
} else {
  $message = "Invalid credentials";
}

// (B) SHOW HTML ?>


  
    
      Password Reset Request Demo
    $message";
    } ?>
  

This is the page that users will land on after they click on the link in their email. Again, there are 2 parts to it:

  • The top part will use the libraries to handle the reset request (verify id, hash, send the new password via email).
  • The bottom half is nothing but HTML to show if the reset is successful or not.

 

 

EXTRA
USEFUL BITS

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

 

USER LOGIN AND AUTHENTICATION

Have not created your own user system yet? Here’s how to do it:

3 Steps Simple PHP Login Page (With MySQL Database)

 

PASSWORD

Don’t leave the password in plain text! At least put a simple lock on it:

4 Ways to Encrypt, Decrypt and Verify Passwords in PHP

 

EXTRA
DOWNLOAD

Finally, here is the download link to the source code as promised.

 

QUICK START

Skipped the entire tutorial? Here are a few quick steps to set up the example:

  • Download and unzip into your project folder.
  • Create your dummy database and import the files in the sql folder.
  • Update the database and password settings in lib/config.php.
  • Access 1-request.php and trace the code from there.

 

 

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 to better understand, 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 Forget Password Recovery Script With PHP appeared first on Code Boxx.



This post first appeared on Xxxxxxxxx, please read the originial post: here

Share the post

3 Steps Forget Password Recovery Script With PHP

×

Subscribe to Xxxxxxxxx

Get updates delivered right to your inbox!

Thank you for your subscription

×