- Defining Helper Functions
- 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 a user must be logged in to change their password and wouldn't be logged in to recover a forgotten one.
Recovering Passwords
Because the user passwords are not being stored in an encrypted format, they cannot be decrypted and recovered. When the user forgets or loses their password, the only option then 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.15).
- Create a new PHP script in your text editor or ID to be named forgot_password.php and stored in the Web root directory.
- Include the standard stuff:
<?php require ('./includes/config.inc.php'); $page_title = 'Forgot Your Password?'; include ('./includes/header.html'); require (MYSQL);
- 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="'. mysqli_real_ escape_string ($dbc, $_POST['email']) . '"'; $r = mysqli_query ($dbc, $q); if (mysqli_num_rows($r) == 1) { // Retrieve the user ID. list($uid) = mysqli_fetch_array ($r, MYSQLI_NUM); } else { // No database match made. $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. The first thing it 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, then the user ID value is retrieved. If it doesn't exist in the database, an error message is assigned to the array.
- Complete the filter_vars() conditional:
} else { // No valid address submitted. $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)) { // If everything's OK. $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 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 zero) and going for fifteen characters. The end result will be a completely random and unique password like 6e968eff0833110.
- Add the new password to the database:
$q = "UPDATE users SET pass='" . get_password_hash($p) . "' WHERE id=$uid LIMIT 1"; $r = mysqli_query ($dbc, $q); if (mysqli_affected_rows($dbc) == 1) { // If it ran OK.
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 get_password_hash() function.
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 very simple email message (Figure 4.16). You should make it more interesting.
- Print a message and wrap up:
echo '<h3>Your password has been changed.</h3><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.17). 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 ('./includes/form_functions.inc.php'); ?><h3>Reset Your Password</h3> <p>Enter your email address below to reset your password.</p> <form action="forgot_password.php" method="post" accept-charset= "utf-8"> <p><label for="email"><strong>Email Address</strong></label> <br /><?php create_form_input('email', 'text', $pass_errors); ?> </p> <input type="submit" name="submit_button" value="Reset &rarr" id="submit_button" class="formbutton" /> </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.
Changing Passwords
Changing a password is kind of like a combination of the login and registration processes. The user should enter their current password as an extra precaution, plus their new password, and a confirmation of their new password (Figure 4.18). 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(); $page_title = 'Change Your Password'; include ('./includes/header.html'); require (MYSQL);
You'll notice that just before the header 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();
- Validate the current password:
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if (!empty($_POST['current'])) { $current = mysqli_real_escape_string ($dbc, $_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 their 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,20}$/', $_POST['pass1']) ) { if ($_POST['pass1'] == $_POST['pass2']) { $p = mysqli_real_escape_string ($dbc, $_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)) { // If everything's OK. $q = "SELECT id FROM users WHERE pass='" . get_password_ hash($current) . "' AND id={$_SESSION['user_id']}"; $r = mysqli_query ($dbc, $q); if (mysqli_num_rows($r) == 1) { // Correct
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 their email address.
- Update the database with the new password:
$q = "UPDATE users SET pass='" . get_password_hash($p) . "' WHERE id={$_SESSION['user_id']} LIMIT 1"; if ($r = mysqli_query ($dbc, $q)) { // If it ran OK.
This query is almost exactly like that in forgot_password.php
. - Indicate to the user the successful change:
echo '<h3>Your password has been changed.</h3>'; 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.19). You may also want to email the user to indicate the password change (without actually emailing the new password to them).
If there was a problem, trigger 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 update failed, which really shouldn't happen on a live, tested site.
Complete the processing section of the script:
} else { $pass_errors['current'] = 'Your current password is incorrect!'; } // End of current password ELSE. } // End of $p 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 added that will be displayed next to the current password input.
- Display the form:
require ('./includes/form_functions.inc.php'); ?><h3>Change Your Password</h3> <p>Use the form below to change your password.</p> <form action="change_password.php" method="post" accept-charset="utf-8"> <p><label for="pass1"><strong>Current Password</strong> </label><br /><?php create_form_input('current', 'password', $pass_errors); ?></p> <p><label for="pass1"><strong>New Password</strong></label> <br /><?php create_form_input('pass1', 'password', $pass_errors); ?> <small>Must be between 6 and 20 characters long, with at least one lowercase letter, one uppercase letter, and one number.</small></p> <p><label for="pass2"><strong>Confirm New Password</strong> </label><br /><?php create_form_input('pass2', 'password', $pass_errors); ?></p> <input type="submit" name="submit_button" value="Change &rarr" id="submit_button" class="formbutton" /> </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.