- Protecting Passwords
- Registration
- Logging In
- Logging Out
- Managing Passwords
- Improving the Security
Managing Passwords
The site will have two pages for managing user passwords. One will be used to recover a forgotten password, and the other will change an existing password. Both pages are simple forms, but whereas users wouldn’t be logged in when recovering a forgotten password, they must be logged in to change their password.
Recovering Passwords
Because the user passwords aren’t being stored in an encrypted format, they can’t be decrypted and recovered. When users forget or lose their password, an option is to create a new password and send it to them in an email. The form to start this process is simple: it just takes an email address (Figure 4.11).
I will say in advance that there are two arguments against using this approach. I’ll discuss those at the end of the sequence. In Chapter 12, you’ll see an alternative approach that doesn’t involve emailing passwords.
- Create a new PHP script in your text editor or IDE to be named forgot_password.php and stored in the web root directory.
Include the standard stuff:
<?php require('./includes/config.inc.php'); require(MYSQL); $page_title = 'Forgot Your Password?'; include('./includes/header.html');
Create an array for storing errors:
$pass_errors = array();
Validate the email address:
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) { $q = 'SELECT id FROM users WHERE email="'. escape_data($_POST['email'], $dbc) . '"'; $r = mysqli_query($dbc, $q); if (mysqli_num_rows($r) === 1) { list($uid) = mysqli_fetch_array($r, MYSQLI_NUM); } else { $pass_errors['email'] = 'The submitted email address does not match those on file!'; }
If the page is accessed via a POST request, then the form has been submitted. In that case, the first thing the script should do is validate that a proper email address was provided. This is a two-step process. First, the filter_var() function confirms that the submitted value adheres to the email syntax. Then a query specifically confirms that this email address exists in the database. If it does, the user ID value is retrieved. If the email address doesn’t exist in the database, an error message is assigned to the array.
Complete the filter_var() conditional:
} else { $pass_errors['email'] = 'Please enter a valid email address!'; } // End of $_POST['email'] IF.
This error applies if the user doesn’t provide a syntactically valid email address.
Generate a new password:
if (empty($pass_errors)) { $p = substr(md5(uniqid(rand(), true)), 10, 15);
To generate the password, call the uniqid() function, which returns a unique ID. If it’s passed some value as its first argument, that value will be used as the prefix, thereby expanding the returned string. For the prefix value, invoke the rand() function. When a second argument of true is passed to uniqid(), a more random unique ID will be returned. This value will be sent through md5(), which will create a string 32 characters long, consisting of letters and numbers. From that string, take a substring starting with the eleventh character (because indexes start at 0) and going for 15 characters. The end result will be a completely random and unique password like 6e968eff0833110.
Update the new password in the database:
$q = "UPDATE users SET pass='" . password_hash($p, PASSWORD_BCRYPT) . "' WHERE id=$uid LIMIT 1"; $r = mysqli_query($dbc, $q); if (mysqli_affected_rows($dbc) === 1) {
The query uses the user ID value just fetched from the database to know which record to update. The generated password must also be run through the password_hash() function, because all passwords are stored in a hashed format.
Send the new password to the user:
$body = "Your password to log into <whatever site> has been temporarily changed to '$p'. Please log in using that password and this email address. Then you may change your password to something more familiar."; mail($_POST['email'], 'Your temporary password.', $body, 'From: admin@example.com');
This is a simple email message (Figure 4.12). You should make it more interesting.
Print a message and wrap up:
echo '<h1>Your password has been changed.</h1><p>You will receive the new, temporary password via email. Once you have logged in with this new password, you may change it by clicking on the "Change Password" link.</p>'; include('./includes/footer.html'); exit();
As with any process, you want to indicate to the user what should happen next (Figure 4.13). The exit() line terminates the script so that the form is not shown again.
If the database update couldn’t run, generate an error:
} else { // If it did not run OK. trigger_error('Your password could not be changed due to a system error. We apologize for any inconvenience.'); }
This else clause applies if the database query didn’t work, indicating a system error.
Complete the processing section of the script:
} // End of $uid IF. } // End of the main Submit conditional.
Create the form:
require_once('./includes/form_functions.inc.php'); ?><h1>Reset Your Password</h1> <p>Enter your email address below to reset your password.</p> <form action="forgot_password.php" method="post" accept-charset="utf-8"> <?php create_form_input('email', 'email', 'Email Address', $pass_errors); ?> <input type="submit" name="submit_button" value="Reset →" id="submit_button" class="btn btn-default" /> </form>
The form uses the same create_form_input() function to generate the single text input.
Complete the page:
<?php include('./includes/footer.html'); ?>
- Save and test the forgotten password script.
As I mentioned at the beginning of this sequence, there are two arguments against the approach taken here. First, it allows anyone to reset anyone’s password. Second, it sends a less secure password in an email (which is a less secure protocol). That being said, this approach may be sufficiently secure for some sites, and it’s certainly easy to implement.
In Part 4 of this book, I’ll present another solution. That approach sends the user an access link without changing the user’s existing password. That approach doesn’t have either of the above-mentioned issues, but it requires a bit more effort to implement.
Changing Passwords
Changing a password is kind of like a combination of the login and registration processes. The user should enter his current password as an extra precaution, then his new password, and finally a confirmation of the new password (Figure 4.14). The user must be logged in to perform this task.
- Create a new PHP script in your text editor or IDE to be named change_password.php and stored in the web root directory.
Include the standard stuff:
<?php require('./includes/config.inc.php'); redirect_invalid_user(); require(MYSQL); $page_title = 'Change Your Password'; include('./includes/header.html');
You’ll notice that just before the MySQL connection script is included, the script invokes the redirect_invalid_user() function so that only logged-in users can access the page.
Create an array for storing errors:
$pass_errors = array();
Check for the current password:
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!empty($_POST['current'])) { $current = $_POST['current']; } else { $pass_errors['current'] = 'Please enter your current password!'; }
As with the login form, this conditional only checks that there’s a password value. If the user is changing her password from a legitimate system-generated one, the current password wouldn’t pass the more strict regular expression.
Validate the new password:
if (preg_match('/^(\w*(?=\w*\d)(?=\w*[a-z])(?=\w*[A-Z])\w*){6,}$/', $_POST['pass1']) ) { if ($_POST['pass1'] == $_POST['pass2']) { $p = $_POST['pass1']; } else { $pass_errors['pass2'] = 'Your password did not match the confirmed password!'; } } else { $pass_errors['pass1'] = 'Please enter a valid password!'; }
This code is almost exactly like that in the registration script, except that errors are assigned to the $pass_errors array.
If everything is fine, validate the current password against the database:
if (empty($pass_errors)) { $q = "SELECT pass FROM users WHERE id={$_SESSION['user_id']}"; $r = mysqli_query($dbc, $q); list($hash) = mysqli_fetch_array($r, MYSQLI_NUM); if (password_verify($current, $hash)) {
To confirm that the current password is correct, a database query is run, similar to the login query except that it uses the user’s ID (from the session), instead of the email address.
Update the database with the new password:
$q = "UPDATE users SET pass='" . password_hash($p, PASSWORD_BCRYPT) . "' WHERE id={$_SESSION['user_id']} LIMIT 1"; if ($r = mysqli_query($dbc, $q)) {
This query is almost exactly like that in forgot_password.php.
Indicate to the user the successful change:
echo '<h1>Your password has been changed.</h1>'; include('./includes/footer.html'); exit();
The message is printed, the footer is included, and then the script is terminated using exit() so that the form isn’t shown again (Figure 4.15). You may also want to email the user to indicate the password change (without actually emailing the new password to the user).
If there was a problem, trigger an error:
} else { trigger_error('Your password could not be changed due to a system error. We apologize for any inconvenience.'); }
This else clause applies if the database update failed, which shouldn’t happen on a live, tested site.
Complete the processing section of the script:
} else { // Invalid password. $pass_errors['current'] = 'Your current password is incorrect!'; } } // End of empty($pass_errors) IF. } // End of the form submission conditional.
This else clause applies if the supplied current password doesn’t match the one in the database for the current user. In that case, an error is created that will be displayed next to the current password input.
Display the form:
require_once('./includes/form_functions.inc.php'); ?><h1>Change Your Password</h1> <p>Use the form below to change your password.</p> <form action="change_password.php" method="post" accept-charset="utf-8"> <?php create_form_input('current', 'password', 'Current Password', $pass_errors); create_form_input('pass1', 'password', 'Password', $pass_errors); echo '<span class="help-block">Must be at least 6 characters long, with at least one lowercase letter, one uppercase letter, and one number.</span>'; create_form_input('pass2', 'password', 'Confirm Password', $pass_errors); ?> <input type="submit" name="submit_button" value="Change →" id="submit_button" class="btn btn-default" /> </form>
The form has three password inputs. Each is generated using the create_form_input() function.
Complete the page:
<?php include('./includes/footer.html'); ?>
- Save and test the change password script.