I decided to try and put together a custom login and password reset form for our users without using wp-login.php or a plugin.
After hours of searching the web and trying various codes provided by tutorials and blogs. I was able to successfully login and logout with various error notifications for the user trying to login and logout. I was even able to use jQuery to switch between the login form and password reset form. The action on both forms were using wp-login.php to handle each submission and validation with error reporting on my page. (I copied and renamed wp-login.php to validation.php, as well as updating all of the internals of validation.php to point to validation.php instead of wp-login.php) All of this was done without a user visiting wp-login.php until it came down to the email sent to the user with a correct user name or email trying to reset their password. Their email arrived, but it provided the link back to wp-login.php to reset their password.
Thus began my search for a custom password reset form to avoid using wp-login.php.
Below is my original setup until I got to the point of modifying my page to work with a custom reset form that both handled and validated my login/logout and password reset functionality.
My original login page (set as a static home page)
<?php
/** Template Name: Login - Static Home Page */
get_header(); ?>
<body>
<div class="loginpage">
<div class="loginbox">
<?php
$loginbox = 'show';
$resetbox = 'hide';
$access = $_GET['access'];
$logout = $_GET['logout'];
$login = $_GET['login'];
$reset = $_GET['reset'];
$userstatus1 = $_GET['userstatus1']; // Invalid Username
$userstatus2 = $_GET['userstatus2']; // Invalid Email Address
$lostempty = $_GET['lostempty'];
if ( $userstatus1 == 'true' || $userstatus2 == 'true' || $lostempty == 'true' ) {
$loginbox = 'hide';
$resetbox = 'show';
}
if ( $access == 'restricted' ) { ?>
<div class="notices red">
ACCESS RESTRICTED, PLEASE LOGIN
</div>
<?php } elseif ( $logout == 'success' ) { ?>
<div class="notices green">
SUCCESSFULLY LOGGED OUT
</div>
<?php } elseif ( $login == 'failed' ) { ?>
<div class="notices yellow">
INCORRECT USERNAME OR PASSWORD
</div>
<?php } elseif ( $login == 'empty' ) { ?>
<div class="notices yellow">
PLEASE ENTER A USERNAME AND PASSWORD
</div>
<?php } elseif ( $reset == true ) { ?>
<div class="notices blue">
PASSWORD RESET, CHECK YOUR EMAIL FOR MORE INFORMATION
</div>
<?php } elseif ( $userstatus1 == true ) { ?>
<div class="notices yellow">
ERROR 201: INVALID USERNAME OR EMAIL ADDRESS
</div>
<?php } elseif ( $userstatus2 == true ) { ?>
<div class="notices yellow">
ERROR 202: INVALID USERNAME OR EMAIL ADDRESS
</div>
<?php } elseif ( $lostempty == true ) { ?>
<div class="notices yellow">
PLEASE ENTER A VALID USERNAME OR EMAIL ADDRESS
</div>
<?php }
?>
<div id="loginbox" class="lbwrap <?php echo $loginbox ?>">
<div class="lbheader">
AUTHORIZED USER LOGIN
</div>
<div class="lbcontent">
<?php global $user_ID, $user_identity; if (!$user_ID) { // not logged in show login form ?>
<form method="post" action="<?php bloginfo('url') ?>/validation.php" class="wp-user-form">
<div class="username">
<div class="lilabelwrap">
<label for="user_login"><?php _e('Username'); ?>: </label>
</div>
<div class="liinput">
<div class="inputwrap">
<input type="text" name="log" value="<?php echo esc_attr(stripslashes($user_login)); ?>" size="20" id="user_login" tabindex="11" />
</div>
</div>
</div>
<div class="password">
<div class="lilabelwrap">
<label for="user_pass"><?php _e('Password'); ?>: </label>
</div>
<div class="liinput">
<div class="inputwrap">
<input type="password" name="pwd" value="" size="20" id="user_pass" tabindex="12" />
</div>
</div>
</div>
<div class="login_fields">
<div class="rememberme">
<label for="rememberme">
<input type="checkbox" name="rememberme" value="forever" checked="checked" id="rememberme" tabindex="13" /> Remember Me
</label>
</div>
<div class="inputbuttons">
<?php do_action('login_form'); ?>
<input type="submit" name="user-submit" value="<?php _e('Login'); ?>" tabindex="14" class="user-submit" />
<input type="button" name="user-reset" value="<?php _e('Lost Password'); ?>" tabindex="15" class="user-reset" id="pwreset" />
<input type="hidden" name="redirect_to" value="<?php echo $_SERVER; ?>/dashboard/" />
<input type="hidden" name="user-cookie" value="1" />
</div>
</div>
</form>
<div class="clear"></div>
<?php } else { // is logged in ?>
<?php if (is_user_logged_in()) {
wp_redirect(home_url( '/dashboard/' ));
exit;
} ?>
<?php } ?>
</div>
</div>
<div id="resetbox" class="lbwrap <?php echo $resetbox ?>">
<div class="lbheader">
PASSWORD RESET
</div>
<div class="lbcontent">
<p>Enter your Username or Email Address to continue</p>
<form method="post" action="<?php echo site_url('validation.php?action=lostpassword', 'login_post') ?>" class="wp-user-form">
<div class="user_name">
<div class="liinput">
<div class="inputwrap">
<input type="text" name="user_login" value="" size="20" id="user_login" tabindex="10001" />
</div>
</div>
</div>
<div class="login_fields">
<div class="inputbuttons">
<?php do_action('login_form', 'resetpass'); ?>
<input type="submit" name="user-submit" value="<?php _e('Start Reset'); ?>" class="user-submit" tabindex="10002" />
<input type="button" name="user-cancel" value="<?php _e('Cancel'); ?>" tabindex="10003" class="user-cancel" id="pwcancel" />
<input type="hidden" name="redirect_to" value="/?reset=true" />
<input type="hidden" name="user_cookie" value="1" />
</div>
</div>
</form>
</div>
<div>
</div>
</div>
</body>
<?php get_footer(); ?>
My jQuery used to switch between forms:
( function( $ ) {
//
$('#pwreset').click(function () {
var l = $("#loginbox");
var r = $("#resetbox");
l.removeClass('show');
l.addClass('hide');
r.removeClass('hide');
r.addClass('show');
}
)
//
$('#pwcancel').click(function () {
var l = $("#loginbox");
var r = $("#resetbox");
l.removeClass('hide');
l.addClass('show');
r.removeClass('show');
r.addClass('hide');
}
)
} )( jQuery );
Now for the functions controlling my error reporting via $_GET Parameters in my functions.php file:
add_action( 'admin_init', 'redirect_non_logged_users' );
function redirect_non_logged_users() {
if ( !is_user_logged_in() ) {
wp_redirect( home_url( '/?access=restricted' ) );
exit;
};
}
/**/
add_action( 'wp_login_failed', 'user_login_fail' ); // hook failed login
function user_login_fail( $username ) {
$referrer = $_SERVER['HTTP_REFERER']; // where did the post submission come from?
// if there's a valid referrer, and it's not the default log-in screen
if ( !empty($referrer) && !strstr($referrer,'wp-login') && !strstr($referrer,'wp-admin') ) {
wp_redirect(home_url( '/?login=failed' ));
exit;
}
}
/**/
add_action( 'authenticate', 'check_username_password', 1, 3); // hook authenticate
function check_username_password( $login, $username, $password ) {
$referrer = $_SERVER['HTTP_REFERER'];
if( $username == "" || $password == "" ){
wp_redirect(home_url( '/?login=empty' ));
exit;
}
}
/**/
add_action('lostpassword_post', 'validate_reset', 99, 3);
function validate_reset(){
if(isset($_POST['user_login']) && !empty($_POST['user_login'])){
$email_address = $_POST['user_login'];
if(filter_var( $email_address, FILTER_VALIDATE_EMAIL )){
if(!email_exists( $email_address )){
wp_redirect(home_url( '/?userstatus1=true' ));
exit;
}
} else {
$username = $_POST['user_login'];
if ( !username_exists( $username ) ){
wp_redirect(home_url( '/?userstatus2=true' ));
exit;
}
}
}else{
wp_redirect(home_url( '/?lostempty=true' ));
exit;
}
}
The first function was what I used to check if a user was logged in who tried to access any page but the login page and at the top of those pages I put:<?php redirect_non_logged_users(); ?>
redirecting them back to the login page with a $_GET Parameter of Access Restricted.
The second and third function controlled validation for empty user names or passwords when trying to login with the login form. The last function controlled the validation and error reporting for the password reset form, checking for an empty submission, or an invalid email or user name.
All of this code worked completely fine until I decided to modify the page for the sake of the password reset form so that I could do two things: 1) Not to use wp-login.php (my copied and renamed version is validation.php) to validate and handle my forms for password reset so that my page theme flowed flawlessly without a hint of a wordpress page or form and 2) Not to have my password reset emails have a link back to wp-login.php while also being able to customize the email(s) sent to the user.
After two days of searching, I stumbled upon a few tutorials and blog posts that royally screwed up my page or didn’t work anymore because of wordpress updates and changes in how functions/hooks were handled.
Below is what I am working with now:
<?php
/** Template Name: Login - Static Home Page **MODIFIED VERSION** */
global $wpdb, $user_ID;
function tg_validate_url() {
global $post;
$page_url = esc_url(get_permalink( $post->ID ));
$urlget = strpos($page_url, "?");
if ($urlget === false) {
$concate = "?";
} else {
$concate = "&";
}
return $page_url.$concate;
}
get_header(); ?>
<body>
<div class="loginpage">
<div class="loginbox">
<?php
$loginbox = 'show';
$resetbox = 'hide';
$access = $_GET['access'];
$logout = $_GET['logout'];
$login = $_GET['login'];
$reset = $_GET['reset'];
$userstatus1 = $_GET['userstatus1']; // Invalid Username
$userstatus2 = $_GET['userstatus2']; // Invalid Email Address
$lostempty = $_GET['lostempty'];
$actionrpw = $_GET['action'];
if ( $userstatus1 == 'true' || $userstatus2 == 'true' || $lostempty == 'true' ) {
$loginbox = 'hide';
$resetbox = 'show';
}
if ( $actionrpw == 'reset_pwd' ) {
$loginbox = 'hide';
$resetbox = 'hide';
}
if ( $access == 'restricted' ) { ?>
<div class="notices red">
ACCESS RESTRICTED, PLEASE LOGIN
</div>
<?php } elseif ( $logout == 'success' ) { ?>
<div class="notices green">
SUCCESSFULLY LOGGED OUT
</div>
<?php } elseif ( $login == 'failed' ) { ?>
<div class="notices yellow">
INCORRECT USERNAME OR PASSWORD
</div>
<?php } elseif ( $login == 'empty' ) { ?>
<div class="notices yellow">
PLEASE ENTER A USERNAME AND PASSWORD
</div>
<?php } elseif ( $reset == true ) { ?>
<div class="notices blue">
PASSWORD RESET, CHECK YOUR EMAIL FOR MORE INFORMATION
</div>
<?php } elseif ( $userstatus1 == true ) { ?>
<div class="notices yellow">
ERROR 201: INVALID USERNAME OR EMAIL ADDRESS
</div>
<?php } elseif ( $userstatus2 == true ) { ?>
<div class="notices yellow">
ERROR 202: INVALID USERNAME OR EMAIL ADDRESS
</div>
<?php } elseif ( $lostempty == true ) { ?>
<div class="notices yellow">
PLEASE ENTER A VALID USERNAME OR EMAIL ADDRESS
</div>
<?php } elseif ( $actionrpw == 'reset_success' ) { ?>
<div class="notices green">
PASSWORD RESET, CHECK YOUR EMAIL FOR YOUR NEW PASSWORD
</div>
<?php }
?>
<div id="loginbox" class="lbwrap <?php echo $loginbox ?>">
<div class="lbheader">
AUTHORIZED USER LOGIN
</div>
<div class="lbcontent">
<?php global $user_ID, $user_identity; if (!$user_ID) { // not logged in show login form ?>
<form method="post" action="<?php bloginfo('url') ?>/validation.php" class="wp-user-form">
<div class="username">
<div class="lilabelwrap">
<label for="user_login"><?php _e('Username'); ?>: </label>
</div>
<div class="liinput">
<div class="inputwrap">
<input type="text" name="log" value="<?php echo esc_attr(stripslashes($user_login)); ?>" size="20" id="user_login" tabindex="11" />
</div>
</div>
</div>
<div class="password">
<div class="lilabelwrap">
<label for="user_pass"><?php _e('Password'); ?>: </label>
</div>
<div class="liinput">
<div class="inputwrap">
<input type="password" name="pwd" value="" size="20" id="user_pass" tabindex="12" />
</div>
</div>
</div>
<div class="login_fields">
<div class="rememberme">
<label for="rememberme">
<input type="checkbox" name="rememberme" value="forever" checked="checked" id="rememberme" tabindex="13" /> Remember Me
</label>
</div>
<div class="inputbuttons">
<?php do_action('login_form'); ?>
<input type="submit" name="user-submit" value="<?php _e('Login'); ?>" tabindex="14" class="user-submit" />
<input type="button" name="user-reset" value="<?php _e('Lost Password'); ?>" tabindex="15" class="user-reset" id="pwreset" />
<input type="hidden" name="redirect_to" value="<?php echo $_SERVER; ?>/dashboard/" />
<input type="hidden" name="user-cookie" value="1" />
</div>
</div>
</form>
<div class="clear"></div>
<?php } else { // is logged in ?>
<?php if (is_user_logged_in()) {
wp_redirect(home_url( '/dashboard/' ));
exit;
} ?>
<?php } ?>
</div>
</div>
<?php if(isset($_GET['key']) && $_GET['action'] == "reset_pwd") {
$reset_key = $_GET['key'];
$user_login = $_GET['login'];
$user_data = $wpdb->get_row($wpdb->prepare("SELECT ID, user_login, user_email FROM $wpdb->users WHERE user_activation_key = %s AND user_login = %s", $reset_key, $user_login));
$user_login = $user_data->user_login;
$user_email = $user_data->user_email;
if(!empty($reset_key) && !empty($user_data)) {
$new_password = wp_generate_password(7, false);
//echo $new_password; exit();
wp_set_password( $new_password, $user_data->ID );
//mailing reset details to the user
$message = __('Your new password for the account at:') . "\r\n\r\n";
$message .= get_option('siteurl') . "\r\n\r\n";
$message .= sprintf(__('Username: %s'), $user_login) . "\r\n\r\n";
$message .= sprintf(__('Password: %s'), $new_password) . "\r\n\r\n";
$message .= __('You can now login with your new password at: ') . get_option('siteurl') . "\r\n\r\n";
if ( $message && !wp_mail($user_email, 'Password Reset Request', $message) ) {
$redirect_to = get_bloginfo('url')."/?action=no_mail";
wp_safe_redirect($redirect_to);
exit();
} else { ?><?php
$redirect_to = get_bloginfo('url')."/?action=reset_success";
wp_safe_redirect($redirect_to);
exit();/*?>
<div id="content" role="main">
<div id="password-reset">
<h3>Password Reset</h3>
<div class="newpass">
<p>Your new password is <?php echo $new_password; ?></p>
<p>You will receive confirmation email shortly.</p>
</div>
</div>
</div>*/ ?>
<?php /*exit;*/ }
} else {
$redirect_to = get_bloginfo('url')."/?action=no_valid_key";
wp_safe_redirect($redirect_to);
exit();
}
}
if($_POST['action'] == "tg_pwd_reset"){
if ( !wp_verify_nonce( $_POST['tg_pwd_nonce'], "tg_pwd_nonce")) {
exit("No trick please");
}
if(empty($_POST['user_input'])) {
$redirect_to = get_bloginfo('url')."/?action=no_username_email_address";
wp_safe_redirect($redirect_to);
exit();
}
//We shall SQL escape the input
$user_input = $wpdb->escape(trim($_POST['user_input']));
if ( strpos($user_input, '@') ) {
$user_data = get_user_by_email($user_input);
if(empty($user_data)) { //delete the condition $user_data->caps[administrator] == 1, if you want to allow password reset for admins also
//echo "<div class='error'>Invalid E-mail address!</div>";
//exit();
$redirect_to = get_bloginfo('url')."/?action=bad_email";
wp_safe_redirect($redirect_to);
exit();
}
} else {
$user_data = get_userdatabylogin($user_input);
if(empty($user_data) || $user_data->caps[administrator] == 1) { //delete the condition $user_data->caps[administrator] == 1, if you want to allow password reset for admins also
//echo "<div class='error'>Invalid Username!</div>";
//exit();
$redirect_to = get_bloginfo('url')."/?action=bad_user";
wp_safe_redirect($redirect_to);
exit();
}
}
$user_login = $user_data->user_login;
$user_email = $user_data->user_email;
$key = $wpdb->get_var($wpdb->prepare("SELECT user_activation_key FROM $wpdb->users WHERE user_login = %s", $user_login));
if(empty($key)) {
//generate reset key
$key = wp_generate_password(20, false);
$wpdb->update($wpdb->users, array('user_activation_key' => $key), array('user_login' => $user_login));
}
//mailing reset details to the user
$message = __('Someone requested that the password be reset for the following account:') . "\r\n\r\n";
$message .= get_option('siteurl') . "\r\n\r\n";
$message .= sprintf(__('Username: %s'), $user_login) . "\r\n\r\n";
$message .= __('If this was a mistake, just ignore this email and nothing will happen.') . "\r\n\r\n";
$message .= __('To reset your password, visit the following address:') . "\r\n\r\n";
$message .= tg_validate_url() . "action=reset_pwd&key=$key&login=" . rawurlencode($user_login) . "\r\n";
if ( $message && !wp_mail($user_email, 'Password Reset Request', $message) ) {
//echo "<div class='error'>Email failed to send for some unknown reason.</div>";
//exit();
$redirect_to = get_bloginfo('url')."/?action=no_mail";
wp_safe_redirect($redirect_to);
exit();
} else {
//echo "<div class='success'>We have just sent you an email with Password reset instructions.</div>";
//exit();
$redirect_to = get_bloginfo('url')."/?action=success_email";
wp_safe_redirect($redirect_to);
exit();
}
} else { ?>
<div id="resetbox" class="lbwrap <?php echo $resetbox ?>">
<div class="lbheader">
PASSWORD RESET
</div>
<div class="lbcontent">
<p>Enter your Username or Email Address to continue</p>
<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); //$post_meta = get_post_meta($post->ID); ?>
<?php the_content(); ?>
<form class="user_form" id="loginform" action="" method="post">
<input type="text" id="name_input" class="text" name="user_input" value="" />
<input type="hidden" name="action" value="tg_pwd_reset" />
<input type="hidden" name="tg_pwd_nonce" value="<?php echo wp_create_nonce("tg_pwd_nonce"); ?>" />
<input type="submit" id="submitbtn" class="reset_password" name="submit" value="Reset Password" />
</form>
<div id="result"></div> <!-- To hold validation results -->
<script type="text/javascript">
jQuery("#loginform").submit(function() {
jQuery('#result').html('<span class="loading">Validating...</span>').fadeIn();
var input_data = jQuery('#loginform').serialize();
jQuery.ajax({
type: "POST",
url: "<?php echo get_permalink( $post->ID ); ?>",
data: input_data,
success: function(msg){
jQuery('.loading').remove();
jQuery('<div>').html(msg).appendTo('div#result').hide().fadeIn('slow');
}
});
return false;
});
</script>
<?php endwhile; endif; } ?>
</div>
</div>
</div>
</div>
</body>
<?php get_footer(); ?>
The above code was compiled from using the code provided by these 3 sources:
http://www.tutorialstag.com/wordpress-custom-password-reset-page-template.html
https://github.com/hakkens/davehakkens/blob/master/reset-password.php
https://erika.codes/wordpress/custom-password-reset-template/
The last two of these look to be variations of the first link provided by tutorialstag.com, hence the tg_ before the function names in all three source scripts.
The only other changes I have made are to the functions.php file and I have commented out the last function I have provided above that references the handling of submission errors to the password reset form since this new form uses jQuery to report errors and post/validate the handling of the form, even though is does no error reporting at all.
At this time, the behavior of the page is as such; the login form works and is handled by the copied wp-login.php renamed validation.php and reports errors for empty input submission (password and user name) as well as incorrect user name or password submissions via the functions in my functions.php file. The jQuery that switches the forms from the login form to the password reset form works and so does the cancel button on the reset password form that reopens the login form and hides the password reset form. The problems arise from the password reset form. See that list below:
-
Submitting an empty input field (no user name or email) reports no errors or page redirects with $_GET parameters for error reporting.( ie. Does not reload the page with /?erorr=error_message) and appends the login page, inside the container div on the login page. So my login page is appended inside the container div (div#result) on my login page, a picture in picture type of situation.
-
Submitting a non-existing user name or email reports no errors or page redirects with $_GET parameters and again, appends my login page on the inside of the container div (div#result) on my login page.
-
After submitting an empty field or invalid user name or email but upon submission of an existing user name or email, it does not redirect to a $_GET Success parameter or any message about an email being sent. It only sits there with the data you input into the input field with the valid user’s user name or email address and appends the login page html to the inside of the div (div#result) container like the issues in #1 and #2. It does however send you the password reset email with a link back to the page (not using wp-login.php or my renamed version called validation.php). Upon clicking on that link, it does bring you back to the login page with a $_GET parameter of /?action=reset_success and displays the initial login form while sending you a 2nd email with your new password listed.
-
Refreshing the page and inputing a valid user name or email without submitting an empty input or invalid user name or email only refreshes the page, sends the reset email, and then goes back to the password reset form with the valid user’s user name or email address just sitting there while appending the login page inside the container div (div#result). Odd behavior that I am sure is the results of the jQuery .html(msg).appendTo() what not in the jQuery code at the bottom of the page.
The $redirect_to works for /?action=no_valid_key and /?action=reset_success when clicking on the link in the email.
(Side note, I had the option of either showing a $_GET parameter of /?action=reset_success with the login form and a success message or some html that showed the user their new generated password after clicking the link and landing back on the login page.)
Can anyone help me in correcting what is going on, or the very least pointing me in the direction I need to research so that I can figure it out myself?