One very important feature of any good membership website is a password reset system because some users are bound to forget their password. In this tutorial, I outline the steps involved in recovering a user’s password; we will also be implementing such a system using PHP and a MySQL database in this tutorial.
The whole process of implementing such a system can be broken down into 3 main steps. To ease the explanation, let’s analyze these steps in terms of the forms that we will present for the user to fill:
- Login Form: This form takes the username and password combination of a user and logs them in if they are registered on the system. On this form we provide a “Forgot your password?” link in case the user forgot their password and need to reset it.
- Email Form: If the user has forgotten their password, they can click on the “Forgot your password?” link on the login page to reset it. Clicking on this link will take them to another page that prompts them to enter the email. When the email address they provide is not in our users table in the database, we will display and error message which says “No such user exists on our system”. If on the other hand the user exists, we will generate a unique token (a unique random string) and store this token together with that email address in the password_resets table in the database. Then we will send them an email that has that token in a link. When they click on the link in the email we sent them, they will be sent back to our website on a page that presents them with another form.
- New password Form: Once the user is back on our website again, we will grab the token that comes from the link and store it in a session variable. Then we will present them with a form that asks them to enter a new password for their account on our website. When the new password is submitted, we will query the password_resets table for the record that has that token that just came from the link in the mail. If the token is found on the password_resets table, then we are confident that they user is who they are and they clicked on the link in their mail. At this point now we grab the user’s email from the password_resets (remember we had saved the token alongside their email address) table and use that email fetch the user from the users table and update their password.
Hope that’s clear enough. If not then just stick around and it will become clearer as we implement.
Implementation
Create a database called password_recovery and in that database, create two tables namely users and password_resets with the following fields:
users:
1 2 3 4 5 6 7 8 9 10 | +----+-----------+--------------+------------+ | field | type | specs | +----+-----------+--------------+------------+ | id | INT(11) | | | username | VARCHAR(255) | | | email | VARCHAR(255) | UNIQUE | | password | VARCHAR(255) | | +----------------+--------------+------------+ |
password_resets:
1 2 3 4 5 6 7 8 9 | +----+-----------+--------------+------------+ | field | type | specs | +----+-----------+--------------+------------+ | id | INT(11) | | | email | VARCHAR(255) | | | token | VARCHAR(255) | UNIQUE | +----------------+--------------+------------+ |
Now create a project folder called password-recovery and make sure this folder is in your server directory (htdocs folder or www folder). In that folder, create three files namely: login.php, enter_email.php, new_pass.php:
Each of these three files represent the three steps we outlined earlier. Open each of them and paste the following codes in them:
login.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <?php include('app_logic.php'); ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Password Reset PHP</title> <link rel="stylesheet" href="main.css"> </head> <body> <form class="login-form" action="login.php" method="post"> <h2 class="form-title">Login</h2> <!-- form validation messages --> <?php include('messages.php'); ?> <div class="form-group"> <label>Username or Email</label> <input type="text" value="<?php echo $user_id; ?>" name="user_id"> </div> <div class="form-group"> <label>Password</label> <input type="password" name="password"> </div> <div class="form-group"> <button type="submit" name="login_user" class="login-btn">Login</button> </div> <p><a href="enter_email.php">Forgot your password?</a></p> </form> </body> </html> |
enter_email.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <?php include('app_logic.php'); ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Password Reset PHP</title> <link rel="stylesheet" href="main.css"> </head> <body> <form class="login-form" action="enter_email.php" method="post"> <h2 class="form-title">Reset password</h2> <!-- form validation messages --> <?php include('messages.php'); ?> <div class="form-group"> <label>Your email address</label> <input type="email" name="email"> </div> <div class="form-group"> <button type="submit" name="reset-password" class="login-btn">Submit</button> </div> </form> </body> </html> |
new_pass.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Password Reset PHP</title> <link rel="stylesheet" href="main.css"> </head> <body> <form class="login-form" action="new_password.php" method="post"> <h2 class="form-title">New password</h2> <!-- form validation messages --> <?php include('messages.php'); ?> <div class="form-group"> <label>New password</label> <input type="password" name="new_pass"> </div> <div class="form-group"> <label>Confirm new password</label> <input type="password" name="new_pass_c"> </div> <div class="form-group"> <button type="submit" name="new_password" class="login-btn">Submit</button> </div> </form> </body> </html> |
In each of these files, you see that we are including three files which we haven’t yet created, namely the app_logic.php , messages.php,file and main.css. The first handles all the logic of our application such as querying the database, sending email to user and more; the second displays feedback messages to the user such as when they enter a wrong email, the third is the styling of the application.
Create these files in the password-recovery folder. In the main.css file, add this styling code:
main.css:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | body { background: #3b5998; font-size: 1.1em; font-family: sans-serif; } a { text-decoration: none; } form { width: 25%; margin: 70px auto; background: white; padding: 10px; border-radius: 3px; } h2.form-title { text-align: center; } input { display: block; box-sizing: border-box; width: 100%; padding: 8px; } form .form-group { margin: 10px auto; } form button { width: 100%; border: none; color: white; background: #3b5998; padding: 15px; border-radius: 5px; } .msg { margin: 5px auto; border-radius: 5px; border: 1px solid red; background: pink; text-align: left; color: brown; padding: 10px; } |
app_logic.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | <?php session_start(); $errors = []; $user_id = ""; // connect to database $db = mysqli_connect('localhost', 'root', '', 'password-reset-php'); // LOG USER IN if (isset($_POST['login_user'])) { // Get username and password from login form $user_id = mysqli_real_escape_string($db, $_POST['user_id']); $password = mysqli_real_escape_string($db, $_POST['password']); // validate form if (empty($user_id)) array_push($errors, "Username or Email is required"); if (empty($password)) array_push($errors, "Password is required"); // if no error in form, log user in if (count($errors) == 0) { $password = md5($password); $sql = "SELECT * FROM users WHERE username='$user_id' OR email='$user_id' AND password='$password'"; $results = mysqli_query($db, $sql); if (mysqli_num_rows($results) == 1) { $_SESSION['username'] = $user_id; $_SESSION['success'] = "You are now logged in"; header('location: index.php'); }else { array_push($errors, "Wrong credentials"); } } } /* Accept email of user whose password is to be reset Send email to user to reset their password */ if (isset($_POST['reset-password'])) { $email = mysqli_real_escape_string($db, $_POST['email']); // ensure that the user exists on our system $query = "SELECT email FROM users WHERE email='$email'"; $results = mysqli_query($db, $query); if (empty($email)) { array_push($errors, "Your email is required"); }else if(mysqli_num_rows($results) <= 0) { array_push($errors, "Sorry, no user exists on our system with that email"); } // generate a unique random token of length 100 $token = bin2hex(random_bytes(50)); if (count($errors) == 0) { // store token in the password-reset database table against the user's email $sql = "INSERT INTO password_reset(email, token) VALUES ('$email', '$token')"; $results = mysqli_query($db, $sql); // Send email to user with the token in a link they can click on $to = $email; $subject = "Reset your password on examplesite.com"; $msg = "Hi there, click on this <a href=\"new_password.php?token=" . $token . "\">link</a> to reset your password on our site"; $msg = wordwrap($msg,70); $headers = "From: info@examplesite.com"; mail($to, $subject, $msg, $headers); header('location: pending.php?email=' . $email); } } // ENTER A NEW PASSWORD if (isset($_POST['new_password'])) { $new_pass = mysqli_real_escape_string($db, $_POST['new_pass']); $new_pass_c = mysqli_real_escape_string($db, $_POST['new_pass_c']); // Grab to token that came from the email link $token = $_SESSION['token']; if (empty($new_pass) || empty($new_pass_c)) array_push($errors, "Password is required"); if ($new_pass !== $new_pass_c) array_push($errors, "Password do not match"); if (count($errors) == 0) { // select email address of user from the password_reset table $sql = "SELECT email FROM password_reset WHERE token='$token' LIMIT 1"; $results = mysqli_query($db, $sql); $email = mysqli_fetch_assoc($results)['email']; if ($email) { $new_pass = md5($new_pass); $sql = "UPDATE users SET password='$new_pass' WHERE email='$email'"; $results = mysqli_query($db, $sql); header('location: index.php'); } } } ?> |
Here you see three blocks of if statements. These statements handle three actions namely user login, receiving reset email and receiving new password. In the second block, after receiving the user email address, the user is being redirected to a pending.php page. This page simply displays a message telling the user that an email has been sent to their email address which they can use to reset their password.
Create pending.php in the root folder of our project and add this code inside it:
pending.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?php include('app_logic.php'); ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Password Reset PHP</title> <link rel="stylesheet" href="main.css"> </head> <body> <form class="login-form" action="login.php" method="post" style="text-align: center;"> <p> We sent an email to <b><?php echo $_GET['email'] ?></b> to help you recover your account. </p> <p>Please login into your email account and click on the link we sent to reset your password</p> </form> </body> </html> |
messages.php is a file that holds the code snippet for displaying error messages on the form. Open it up and paste this code inside it:
messages.php:
1 2 3 4 5 6 7 8 9 | <?php if (count($errors) > 0) : ?> <div class="msg"> <?php foreach ($errors as $error) : ?> <span><?php echo $error ?></span> <?php endforeach ?> </div> <?php endif ?> |
Now open this project up on your browser at http://localhost/password-recovery/login.php and play with it.
Note: We used PHP’s mail() function to send email to user. This function cannot send mails from localhost. It can only do so using a server that is hosted on the internet. However we can use a test mail application to simulate the sending of emails if you want to have a demo on your local system.
Conclusion
Thank you for following this tutorial to the end. I hope the explanation was clear enough and you learned something that can be of help to you in you web development. If you have any issues or concerns, don’t forget to drop them in the comments below and I’ll get to you.
Source: