Forgetting your password is inevitable. The more profiles we create online, the more chances we have of getting our logins confused. Having an easy-to-use method for users to regain access to their account is an important part of running a successful web-based service.
In this how-to we’re going to implement a system that (1) requests the user’s email address, (2) creates a unique “magic link”, (3) sends the user an email and (4) automatically logs the user into their account when they visit the magic link.
To request the user’s email address we’re going to use the page templates feature of WordPress to create a page that contains a simple form like this…
In your active theme’s directory, create a file called forgot-password.php
and add the following to the page…
↴
<?php
/*
Template Name: Forgot Password
*/
…After saving, you’ll be able to create a new page from your WordPress dashboard and assign the Forgot Password
page template option.
Next we’ll want to make sure that only users who are NOT logged in can interact with the form we’re going to build. We’ll use is_user_logged_in()
to send logged in users to another page via wp_redirect()
…
↴
<?php
/*
Template Name: Forgot Password
*/
//ALREADY LOGGED IN?
if(is_user_logged_in()){
//REDIRECT
wp_redirect( esc_url( home_url() ) );
exit;
}
…Now that we know the current user is not logged in, we don’t have to worry about any accidental form submissions.
In addition to get_header()
and get_footer()
, we want to setup variables we’ll be using throughout the page (including the form itself) as well as the main container for the content of the page — via the #page-content
div…
↴
<?php
/*
Template Name: Forgot Password
*/
//ALREADY LOGGED IN?
if(is_user_logged_in()){
//REDIRECT
wp_redirect( esc_url( home_url() ) );
exit;
}
get_header();
//VAR SETUP
$user_login = isset( $_POST['user_login'] ) ? $_POST['user_login'] : '';
$form_output = '<form method="post" action="'.get_permalink().'">'.
'<input type="text" name="user_login" id="user_login" value="'.$user_login.'" placeholder="[email protected]" />'.
'<input type="hidden" name="action" value="reset" />'.
'<input type="submit" value="→" class="button" id="submit" />'.
'</form>';
//PAGE CONTENT START
echo '<div id="page-content">';
//CHECK IF MAGIC LINK EXPIRED...
if (isset($_GET['ref']) && "magicfail" == $_GET['ref']) {
//EXPIRED MESSAGE OUTPUT
echo '<label for="user_login"><h3>Link Expired</h3><p>Request a new link below...</p></label>'.$form_output;
//CHECK IF FORM SUBMITTED (action == reset)
} else if( isset( $_POST['action'] ) && 'reset' == $_POST['action'] ) {
// ...future code placeholder...
//DEFAULT OUTPUT...
} else {
echo '<label for="user_login">'.
'<h3>Forgot password?</h3>'.
'<p>We\'ll email a magic link to login...</p>'.
'</label>'.$form_output;
}
echo '</div>';
get_footer();
…The $user_login
variable is where we’ll be storing the email address when our form is submitted. $form_output
is essentially just a basic HTML form stored as a variable. We’re putting this into a PHP variable to enable us to echo the form at various parts of the process without having to make sure any edits we make to the form are applied throughout the page template.
After we open the #page-content
section, there are a couple if
checks before the “DEFAULT OUTPUT…” is echoed: (1) if the magic link expired — more on this later — and (2) if the form has been submitted, which we’ll cover next.
When the form is submitted, there are a few things we’ll want to check for before we send an email: (1) the email field is not empty, (2) the email is actually an email using is_email()
, and (3) the email represents an actual user in our system via email_exists()
.
Let’s update the “…future code placeholder…” from the code example above…
↴
//VAR SETUP...
$user_magic_email = trim($user_login);
//EMAIL EMPTY ERROR
if( empty( $user_magic_email ) ) {
$error_message = 'Please enter an email address to reset your password...';
//INVALID EMAIL ERROR
} else if( !is_email( $user_magic_email )) {
$error_message = 'Invalid e-mail address.';
//NO USER FOUND
} else if( !email_exists( $user_magic_email ) ) {
$error_message = 'That email address is not in our system.';
//NO ERRORS...
} else {
$success_message = '<h3>Check your inbox!</h3><p>You should receive an email from us soon...</p>';
/* ...another future code placeholder... */
}
//ERROR MESSAGE OUTPUT
if( isset( $error_message ) ) {
echo '<label for="user_login"> '. $error_message .'</label>'.$form_output;
//SUCCESS MESSAGE OUTPUT
} else if( isset( $success_message ) ) {
echo '<label for="user_login">'. $success_message .'</label>';
}
…If there are any errors at the end of our if checks, we’ll set an $error_message
variable for output inside the <label>
tag. If there are “NO ERRORS…“, we can set a $success_message
variable and move on to creating a magic link.
The process of creating a magic link essentially boils down to 4 steps: (1) generate a unique value via wp_generate_password()
, (2) get the user’s ID from their email address with get_user_by()
, (3) updating their profile with the generated value using update_user_meta()
, and (4) combine elements to create a unique URL with home_url()
.
Like before, let’s update the “…another future code placeholder…” from the code example above…
↴
//CREATE MAGIC_LINK_ID
$magic_link_id = wp_generate_password(20);
//GET USER ID
$user = get_user_by( 'email', $user_magic_email );
$user_id = $user->ID;
//ATTACH MAGIC_LINK_ID TO USER_META
update_user_meta($user_id, 'magic_link_id', $magic_link_id);
//CREATE A MAGIC_LINK_URL
$magic_link_url = home_url() . '/?magic=' . $magic_link_id . '&id=' . $user_id;
…Notice inside the wp_generate_password()
function we’re using “20” to set how many characters we want the $magic_link_id
variable to contain. There are additional options with this function that also allow the use of extra special characters for added control.
The main item we’ll be using next is the $magic_link_url
variable, which is what we’ll send to our user via email.
Since we’re working with WordPress, there’s a built-in mailing function called wp_mail() that we can use with just a few simple lines…
↴
$to = '[email protected]';
$subject = 'Magic Link Request';
$body = 'Here is your magic link to login:'.$magic_link_url;
$headers = array('Content-Type: text/html; charset=UTF-8');
wp_mail( $to, $subject, $body, $headers );
…Notice we’re including $magic_link_url
inside the $body
variable. This is a very simple example to demonstrate how this function works, but there other settings and options available.
PRO TIP: Another option to send emails is with a service called Postmark. Postmark uses their servers to send emails in a responsible manner that greatly reduces the risk of being flagged as spam. For details, check out the Postmark API documentation.
If we add everything together, this is what our final forgot-password.php
template file will look like…
↴
<?php
/*
Template Name: Forgot Password
*/
//ALREADY LOGGED IN?
if(is_user_logged_in()){
//REDIRECT
wp_redirect( esc_url( home_url() ) );
exit;
}
get_header();
//VAR SETUP
$user_login = isset( $_POST['user_login'] ) ? $_POST['user_login'] : '';
$form_output = '<form method="post" action="'.get_permalink().'">'.
'<input type="text" name="user_login" id="user_login" value="'.$user_login.'" placeholder="[email protected]" />'.
'<input type="hidden" name="action" value="reset" />'.
'<input type="submit" value="→" class="button" id="submit" />'.
'</form>';
//PAGE CONTENT START
echo '<div id="page-content">';
//CHECK IF MAGIC LINK EXPIRED...
if (isset($_GET['ref']) && "magicfail" == $_GET['ref']) {
//EXPIRED MESSAGE OUTPUT
echo '<label for="user_login"><h3>Link Expired</h3><p>Request a new link below...</p></label>'.$form_output;
//CHECK IF FORM SUBMITTED (action == reset)
} else if( isset( $_POST['action'] ) && 'reset' == $_POST['action'] ) {
//VAR SETUP...
$user_magic_email = trim($user_login);
//EMAIL EMPTY ERROR
if( empty( $user_magic_email ) ) {
$error_message = 'Please enter an email address to reset your password...';
//INVALID EMAIL ERROR
} else if( !is_email( $user_magic_email )) {
$error_message = 'Invalid e-mail address.';
//NO USER FOUND
} else if( !email_exists( $user_magic_email ) ) {
$error_message = 'That email address is not in our system.';
//NO ERRORS...
} else {
$success_message = '<h3>Check your inbox!</h3><p>You should receive an email from us soon...</p>';
//CREATE MAGIC_LINK_ID
$magic_link_id = wp_generate_password(20);
//GET USER ID
$user = get_user_by( 'email', $user_magic_email );
$user_id = $user->ID;
//ATTACH MAGIC_LINK_ID TO USER_META
update_user_meta($user_id, 'magic_link_id', $magic_link_id);
//CREATE A MAGIC_LINK_URL
$magic_link_url = home_url() . '/?magic=' . $magic_link_id . '&id=' . $user_id;
$to = '[email protected]';
$subject = 'Magic Link Request';
$body = 'Here is your magic link to login:'.$magic_link_url;
$headers = array('Content-Type: text/html; charset=UTF-8');
wp_mail( $to, $subject, $body, $headers );
}
//ERROR MESSAGE OUTPUT
if( isset( $error_message ) ) {
echo '<label for="user_login"> '. $error_message .'</label>'.$form_output;
//SUCCESS MESSAGE OUTPUT
} else if( isset( $success_message ) ) {
echo '<label for="user_login">'. $success_message .'</label>';
}
//DEFAULT OUTPUT...
} else {
echo '<label for="user_login">'.
'<h3>Forgot password?</h3>'.
'<p>We\'ll email a magic link to login...</p>'.
'</label>'.$form_output;
}
echo '</div>';
get_footer();
Once the email has been sent, all we need to do next is write a function that will sign in the user when they visit the magic link.
In order for the magic link to work, we need to (1) create a function that runs before the page content loads, (2) listens for the unique parameters we attached to the magic link URL, (3) checks the unique id in the URL with the unique id attached to the user, and (4) sign in the user if everything matches.
We’ll start by adding the following to the functions.php
file of the theme you’re working with — if you don’t have this file, create one…
↴
function magic_link_sign_in() {
/* ...future code placeholder... */
}
add_action('init', 'magic_link_sign_in');
…here we’re creating a function called
and using magic_link_sign_in()
add_action()
to add our function to the 'init'
action hook. This is a WordPress hook that fires after some core WordPress functions are loaded, but before the actual page is rendered, making it a good time to run this function.
The URL we created and sent in the email uses the “magic” and “id” parameters, so that’s what we’re going to listen for using isset()
and $_GET[]
…
↴
function magic_link_sign_in() {
//CHECK IF THE URL PARAMETERS ARE PRESENT
if (isset($_GET['magic']) && isset($_GET['id'])) {
/* ...future code placeholder... */
}
}
add_action('init', 'magic_link_sign_in');
…once we’re inside this if statement, we know there’s a magic id and user id that we can use to verify the request to login.
In order to confirm the request to login, we need to check that the unique magic link in the URL matches the unique magic link we assigned to the user in earlier steps.
↴
function magic_link_sign_in() {
//CHECK IF THE URL PARAMETERS ARE PRESENT
if (isset($_GET['magic']) && isset($_GET['id'])) {
$magic_link_id = $_GET['magic'];
$user_id = $_GET['id'];
$magic_id = get_user_meta($user_id, 'magic_link_id', true);
//IF MAGIC LINK IDs MATCH...
if ($magic_link_id == $magic_id) {
/* ...future code placeholder... */
//MAGIC LINK EXPIRED...
} else {
//REDIRECT
wp_redirect(home_url('/forgot-password/?ref=magicfail') );
exit;
}
}
}
add_action('init', 'magic_link_sign_in');
…Using the id from the $_GET[]
parameter, we can use
to get the get_user_meta()
and see if they match via 'magic_link_id'
==
.
If the IDs don’t match, we’re going to redirect the user to the forgot-password page and attach the
so we can display the ?ref=magicfail
//EXPIRED MESSAGE OUTPUT
from previous steps.
Now that we know the magic id in the URL matches the magic id attached to the user, we can sign them into their account. Replace the “…future code placeholder…” from the previous step with the following…
↴
//SIGN NEW USER IN
$user = get_user_by('id', $user_id);
if( $user ) {
wp_set_current_user( $user_id, $user->user_login );
wp_set_auth_cookie( $user_id );
do_action( 'wp_login', $user->user_login, $user );
}
//REDIRECT
wp_redirect( home_url('/?ref=magic-link') );
exit;
…First we’re getting the user object with get_user_by()
. The user object gives us what we need to use some very handy functions for signing in a user to a WordPress site:
wp_set_current_user()
— Changes the current user by ID or name.wp_set_auth_cookie()
— Sets the authentication cookies.
'wp_login'
— Action hook that runs after a user has successfully logged in.Finally, once the user is logged in we can redirect them to another page. In this example we’re sending them to the home page with ?ref=magic-link
added so that we know how they got there and have the option of displaying a “Welcome back” message.
After we add the above steps together, our final
function will look like this…magic_link_sign_in()
↴
function magic_link_sign_in() {
//CHECK IF THE URL PARAMETERS ARE PRESENT
if (isset($_GET['magic']) && isset($_GET['id'])) {
$magic_link_id = $_GET['magic'];
$user_id = $_GET['id'];
$magic_id = get_user_meta($user_id, 'magic_link_id', true);
//IF MAGIC LINK IDs MATCH...
if ($magic_link_id == $magic_id) {
//SIGN NEW USER IN
$user = get_user_by('id', $user_id);
if( $user ) {
wp_set_current_user( $user_id, $user->user_login );
wp_set_auth_cookie( $user_id );
do_action( 'wp_login', $user->user_login, $user );
}
//REDIRECT
wp_redirect( home_url('/?ref=magic-link') );
exit;
//MAGIC LINK EXPIRED...
} else {
//REDIRECT
wp_redirect(home_url('/forgot-password/?ref=magicfail') );
exit;
}
}
}
add_action('init', 'magic_link_sign_in');
That should be everything you need to create a custom “Forgot Password?” page for your WordPress site. If you have any questions or comments, my DMs are open @theMOLITOR