PHP Tutorial: Reset Password form.

This is a short tutorial on how to create a simple Reset Password form with PHP and MySQL. In this article, we will go over the basics. We will also take a look at some of the security principals that need to be taken into consideration.

Database tables.

For the purposes of this guide, I have created two MySQL tables.

Our users table, which stores the user’s name, email address and password.

--
-- Table structure for table `users`
--

CREATE TABLE IF NOT EXISTS `users` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `email` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `password` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=2 ;

--
-- Dumping data for table `users`
--

INSERT INTO `users` (`id`, `name`, `email`, `password`) VALUES
(1, 'Joseph Sellars', '[email protected]', 'sgb8327t7823y89hbuu2bbqwegugdg3278gyd2uibiqeh');

As you can see, I have taken the liberty of creating a user called “Joseph Sellars” with the email address “[email protected]”. We will presume that this is John’s email address.

I have also created a table called password_reset_request, which will store information about any “forgot password” requests that have been made.

--
-- Table structure for table `password_reset_request`
--

CREATE TABLE IF NOT EXISTS `password_reset_request` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int(10) unsigned NOT NULL,
  `date_requested` datetime NOT NULL,
  `token` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;

In the table above, we are storing three key pieces of information:

  1. The ID of the user that this “forgot password” request is for.
  2. The date and time of the request. This is important if you want to limit the amount of requests that can be made for a particular user account over a certain period of time.
  3. A cryptographically secure token that will help us to verify the user making the request. Without this token, our “forgot password” form would be extremely easy to hack.

Note that data is only inserted into the password_reset_request table if a valid email address has been found in our users table.

The “forgot password” request.

Here are some PHP samples that you can use for your Reset Password system. In these examples, I will be using the PDO extension, as it gives us the ability to use prepared statements. I am not going to create the HTML forms as I am presuming that you are experienced enough to handle that part by yourself.

An example of handling a “forgot password” request using the PDO object in PHP:

//Connect to MySQL database using PDO.
$pdo = new PDO("mysql:host=$dbServer;dbname=$dbName", $dbUser, $dbPassword);

//Get the name that is being searched for.
$email = isset($_POST['email']) ? trim($_POST['email']) : '';

//The simple SQL query that we will be running.
$sql = "SELECT `id`, `email` FROM `users` WHERE `email` = :email";

//Prepare our SELECT statement.
$statement = $pdo->prepare($sql);

//Bind the $name variable to our :name parameter.
$statement->bindValue(':email', $email);

//Execute the SQL statement.
$statement->execute();

//Fetch our result as an associative array.
$userInfo = $statement->fetch(PDO::FETCH_ASSOC);

//If $userInfo is empty, it means that the submitted email
//address has not been found in our users table.
if(empty($userInfo)){
    echo 'That email address was not found in our system!';
    exit;
}

//The user's email address and id.
$userEmail = $userInfo['email'];
$userId = $userInfo['id'];

//Create a secure token for this forgot password request.
$token = openssl_random_pseudo_bytes(16);
$token = bin2hex($token);

//Insert the request information
//into our password_reset_request table.

//The SQL statement.
$insertSql = "INSERT INTO password_reset_request
              (user_id, date_requested, token)
              VALUES
              (:user_id, :date_requested, :token)";

//Prepare our INSERT SQL statement.
$statement = $pdo->prepare($insertSql);

//Execute the statement and insert the data.
$statement->execute(array(
    "user_id" => $userId,
    "date_requested" => date("Y-m-d H:i:s"),
    "token" => $token
));

//Get the ID of the row we just inserted.
$passwordRequestId = $pdo->lastInsertId();


//Create a link to the URL that will verify the
//forgot password request and allow the user to change their
//password.
$verifyScript = 'https://your-website.com/forgot-pass.php';

//The link that we will send the user via email.
$linkToSend = $verifyScript . '?uid=' . $userId . '&id=' . $passwordRequestId . '&t=' . $token;

//Print out the email for the sake of this tutorial.
echo $linkToSend;

A drill down of the code sample above.

  1. We connect to our MySQL database using the PDO extension.
  2. We retrieve the email address that was entered into our “Forgot Password” address. For the purposes of this example, I am assuming that it can be found in a POST variable called “email”.
  3. After that, we then check to see if the email address can be found in our users table. If we cannot find this email address, our code prints a simple message and kills the script. Obviously, you will probably want to handle this error in a more user-friendly manner. See: Form validation with PHP.
  4. We generated a random token using PHP’s openssl_random_pseudo_bytes function.
  5. We inserted the user’s ID, the current date and time and our random token into theĀ password_reset_request table.
  6. After that, we constructed a HTTP link using the token, the user’s ID and the primary key of the row that we just inserted. We used PDO:lastInsertId to get the ID of the row that we just inserted.
  7. Finally, printed the link out onto the page.

Send the HTML link to the user’s email address.

Obviously, you will want to send this link to the user’s email address. However, how you wish to send the email or format its display is completely up to you.

When the user opens their email and clicks on the link that we sent them, they will be brought to our forgot-pass.php script.

Validating the “forgot password” request.

It is here that we will validate the request and allow them to change their password or not.

//Connect to MySQL database using PDO.
$pdo = new PDO("mysql:host=$dbServer;dbname=$dbName", $dbUser, $dbPassword);

//The user's id, which should be present in the GET variable "uid"
$userId = isset($_GET['uid']) ? trim($_GET['uid']) : '';
//The token for the request, which should be present in the GET variable "t"
$token = isset($_GET['t']) ? trim($_GET['t']) : '';
//The id for the request, which should be present in the GET variable "id"
$passwordRequestId = isset($_GET['id']) ? trim($_GET['id']) : '';


//Now, we need to query our password_reset_request table and
//make sure that the GET variables we received belong to
//a valid forgot password request.

$sql = "
      SELECT id, user_id, date_requested 
      FROM password_reset_request
      WHERE 
        user_id = :user_id AND 
        token = :token AND 
        id = :id
";

//Prepare our statement.
$statement = $pdo->prepare($sql);

//Execute the statement using the variables we received.
$statement->execute(array(
    "user_id" => $userId,
    "id" => $passwordRequestId,
    "token" => $token
));

//Fetch our result as an associative array.
$requestInfo = $statement->fetch(PDO::FETCH_ASSOC);

//If $requestInfo is empty, it means that this
//is not a valid forgot password request. i.e. Somebody could be
//changing GET values and trying to hack our
//forgot password system.
if(empty($requestInfo)){
    echo 'Invalid request!';
    exit;
}

//The request is valid, so give them a session variable
//that gives them access to the reset password form.
$_SESSION['user_id_reset_pass'] = $userId;

//Redirect them to your reset password form.
header('Location: create-password.php');
exit;

In the code above:

  1. We retrieved the user’s ID, the password request ID and the secure token from the URL. If the user clicks on the link that we sent them, then these variables should be present in the $_GET array.
  2. After that, we queried our MySQL table to make sure that these variables are valid and that they match up with a valid Forgot Password request. If the request is not valid, we simply kill the script.
  3. Finally, if the request is valid, we assign their user ID to a session variable called “user_id_reset_pass” before redirecting them to a session-protected page that will allow them to create a new password. On that page, you can have a create password form. Once the user submits their new password, simply update the password field for the user ID found in “user_id_reset_pass” and then ask them to login with their new password.

Hopefully, this tutorial to be useful!