In the previous two tutorials of this Series. we built custom pages for user login and registration. The final piece to explore and replace in the login process is how WordPress handles password reset when a user forgets their password. In this tutorial, we’ll complete our Personalized login plugin by addressing this last step.
WordPress’ password reset functionality follows a standard method: a user initiates a reset by entering their username or emAIl, a temporary reset token is created and stored in their user Data, an email with a link containing this token is sent, and upon clicking the link, the user can choose a new password on a dedicated page. This process is handled by wp-login.php. As before, customizing this aspect will follow a similar approach to what we’ve done in earlier tutorials.
If you haven’t read the first two tutorials, it’s recommended to start from Part 1 and work through the series sequentially. You can write your own code based on the tutorials or download the complete source code from the linked GitHub repository.
Let’s begin by replacing the first screen in the password reset flow.
**Initiating WordPress Password Reset**
When users reach the login page but can’t remember their password, they start the WordPress password reset process. In Part 1 of this series, we added a “Forgot your password?” link at the bottom of the login form. By default, this link points to wp-login.php?Action=lostpassword, which presents the following screen:
To replace this with a custom page, we’ll create a function to redirect users to our custom page and hook that function to a WordPress action. Here, we have two options: using the ‘lost_password’ action (which fires before the page is rendered) or the ‘login_form_{action}’ action we used in the previous tutorial, this time with ‘login_form_lostpassword’. For efficiency, let’s opt for the latter.
First, let’s create a new custom WordPress password reset page:
**Step 1: Create a Reset Password Page**
Recall from Part 1 that we created a function using the ‘plugin_activated’ callback, which creates a WordPress page when the plugin is activated. We added the page definition to the end of the `$page_definitions` array. Since we need another page for selecting a new password during the WordPress reset process, we’ll add it now.
Here’s the entire array with the two adDiTional page definitions for clarity:
// Create information for plugin pages
$page_definitions = array(
‘member-login’ => array(‘title’ => __(‘Login’, ‘personalized-login’), ‘content’ => ”),
// … other pages …
‘forgot-password’ => array(‘title’ => __(‘Forgot Password?’, ‘personalized-login’), ‘content’ => ”),
‘reset-password’ => array(‘title’ => __(‘Reset Password’, ‘personalized-login’), ‘content’ => ”),
);
Now, let’s proceed to handle the submission of the WordPress account recovery form.
**Step 4: Handling the WordPress Account Recovery Form Submission**
With the password reset form created, we need to see what happens when users submit it. To properly handle errors without resorting to hacking WordPress, we’ll write some functions ourselves, leveraging helper functions from wp-login.php as much as possible.
We’ll add a new function to the ‘login_form_lostpassword’ action to handle the POST request. This function, like wp-login.php’s `retrieve_password` function, will find the user and initiate the password update process. Based on errors, it will redirect the user to the appropriate page: back to the ‘Forgot Password?’ form on errors, and to the login page upon success.
Add the following line to the constructor:
“`php
add_action(‘login_form_lostpassword’, array($this, ‘do_password_lost’));
“`
Then, create the function:
“`php
/**
* Initiates password reset.
*/
public function do_password_lost() {
if (‘POST’ === $_SERVER[‘REQUEST_METHOD’]) {
// … handle form submission …
}
// … handle redirects …
}
“`
Next, we’ll create a custom version of the password reset form using a shortcode. Add the following line to the plugin’s constructor:
“`php
add_shortcode(‘custom-password-reset-form’, array($this, ‘render_password_reset_form’));
“`
And create a function to render the form:
“`php
/**
* Shortcode for rendering the form to reset a user’s password.
*
* @param array $attributes Shortcode attributes.
* @param string $content Unused shortcode text content.
*
* @return string Shortcode output.
*/
public function render_password_reset_form($attributes, $content = null) {
// … render the custom password reset form …
}
“`
**Step 3: Handling the WordPress Password Reset Operation**
When users submit the password reset form by clicking the WordPress password reset button, its contents are sent to wp-login.php?action=resetpass, the same URL we used to redirect users to our custom password reset page.
While creating our own form, we could use a different URL, but by retaining the default URL and replacing the default functions for ‘login_form_resetpass’ and ‘login_form_rp’ (just for confirmation), we ensure no one accidentally ends up calling the default password reset version.
Add these two lines to the constructor:
“`php
add_action(‘login_form_rp’, array($this, ‘do_password_reset’));
add_action(‘login_form_resetpass’, array($this, ‘do_password_reset’));
“`
Then, create the function:
“`php
/**
* Resets a user’s password if the password reset form was submitted.
*/
public function do_password_reset() {
if (‘POST’ === $_SERVER[‘REQUEST_METHOD’]) {
// … handle form submission …
}
// … handle redirects …
}
“`
This function checks the request method, collects key and login parameters from the form data, and validates the password reset link using `check_password_reset_key`. It then checks for errors and redirects users accordingly, or proceeds with the password reset if all checks pass.
Now, we’ve completed the password reset feature, so we’ve fully customized the WordPress login experience from user registration to login and resetting a lost password. I Hope this series has provided you with enough tools to further customize it (e.g., by adding new steps to the password reset process) and better understand what’s happening inside wp-login.php.
Go ahead and customize more!