
Why Ask when We Can Know?
We can automatically recognise a returning customer by their email address, so why would we ask the question whether a visitor has shopped with us before? Well, that’s the way WooCommerce Checkout does it by default if a customer is not yet logged in. Like so: “Returning customer? Click here to login.”
To make the context for this post clear, we do not allow guest checkout at authors-books. If your business allows guest checkout, this post may not be relevant to you. Then again, perhaps it is? That’s for you to decide.
Also, we’re using Classic Checkout (not WooCommerce Checkout Blocks). So again, this post may not be of interest to you. But, once again, maybe it is?
A Visitor who may be a Customer
In this post we use the term visitor for someone who comes to authors-books but is not logged in. The visitor may or may not be a returning customer.
So at Checkout: First is the Email Address
So firstly, we move the email address field to be the first field in the checkout form. This is simply achieved thanks to the post WooCommerce: Move Email Field to Top @ Checkout by Business Bloomer.
We ask the Visitor for their Email Address
We ask the visitor for an email address. After all, there’s no way to process an order without an email address and our JavaScript (JS) prevents further progress without an email address. So, what does our JS need to do?
- Ascertain that we’re on the Checkout page.
- Ascertain that the visitor is not logged in.
- Hide the WooCommerce standard returning customer question.
- Add in red “Please start here” to the email address label.
- Treat Enter/Return as Tab so the checkout form is not submitted.
- Only accept a validly formatted email address.
Try it Live!
Assuming you’re visiting and thus not logged in, click/tap the link authors-books. In the new tab opened, choose any book genre, then for any book, click ‘Add to cart’. Scroll and click ‘Proceed to checkout –>’. Enter the email address that you see in the screen shot with the form titled ‘Checkout Log In’ (below). Now press the Tab key (or Enter/Return or just click another field). You see that the email has been recognised as that of a returning customer. If only you had the credentials then you could be logged in and that account’s name and address details would be filled in on the checkout form!
For a serious full test and a small expense, why not buy a book? There are some excellent offerings. After the small purchase (and the book will be delivered to you) you are a customer and you know the password for your email, so you can return to authors-books and go the last step to see checkout form populated. Of course no need to complete that second purchase, but why not anyway?
Note on the technology of the implementation
For the purist we may be regarded as committing two sins.
- Firstly, our JS code includes some jQuery. We look forward to the day when we won’t be using jQuery, but for now we’ll sleep at night.
- Secondly, we are using admin-ajax.php rather than the REST API. Yes, we agree we should change to the REST API as soon as practical.
Is the Visitor at Checkout Logged in? (JS code)
// Are we trying to recognise a customer email? (const recog_email = true ;)
//Is the user NOT logged in?
if ( recog_email && ! $('body').hasClass('logged-in')) {
// Hide the returning customer notice
selector = "div.woocommerce-form-login-toggle"
element = document.querySelector( selector );
if ( typeof element !== 'undefined' && element ) {
element.style.display = "none";
}
// Change the label on the email field to say please start here
label = 'Email address <span class="required" aria-hidden="true">*</span> <span style="color: red;">(please start here)</span>' ;
changeLabel( 'billing_email', label) ;
// Treat Enter/Return like Tab on the email field
selector = "#billing_email" ;
element = document.querySelector( selector );
element.setAttribute("onKeyDown", "ModifyEnterKeyPressAsTab( '#billing_first_name' );");
// On the billing email losing focus
$( "#billing_email" ).blur(function(){
// do we have a value to check?
if($(this).val()){
// Is this a valid email address
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ ;
if ( ! emailRegex.test($(this).val())) {
// find the label for the email field and change it
label = 'Please enter a valid email address <span class="required" aria-hidden="true">*</span>' ;
changeLabel( 'billing_email', label ) ;
$("#billing_email").focus() ;
} else {
is_email_a_customer() ;
// END of Is this a valid email address
}
} else {
label = 'Please enter an email address <span class="required" aria-hidden="true">*</span>' ;
changeLabel( 'billing_email', label ) ;
$("#billing_email").get(0).focus({ focusVisible: true }) ;
// END of do we have a value to check?
}
// END of $("#billing_email").blur()
});
//END Is the user NOT logged in?
}
// https://www.geeksforgeeks.org/javascript/javascript-find-the-html-label-associated-with-a-given-input-element-in-js/
// Function to find the associated label for a given input
function findLabel(inputElement) {
const inputId = inputElement.id;
// Get the input's ID
const label = document.querySelector(`label[for="${inputId}"]`);
// Find label with matching 'for' attribute
return label;
}
// Change the label if we find the element by id
function changeLabel( id, new_label ) {
const inputElement = document.getElementById( id );
selector = "#" + id ;
if ( typeof inputElement !== 'undefined' && inputElement ) {
const labelElement = findLabel(inputElement);
labelElement.innerHTML = new_label ;
} else {
missing_selector( selector );
}
}
function ModifyEnterKeyPressAsTab( selector ) {
if (window.event && window.event.keyCode == 13) {
window.event.preventDefault() ;
element = document.querySelector( selector );
element.focus() ;
return false;
}
}Is the Email Address that of a Returning Customer?
Once more we’re guided by Business Bloomer with the post WooCommerce: Get Customer ID From An Email Address. So we make an AJAX call using the suggested code and if the returned ID is greater than zero then the email belongs to a customer (or other user role) so we can request and check the password for that user. They are in all likelihood a returning customer, but even if they are not we can verify their password and log them in.
Ask the Returning Customer for their Password
Now we want an uncluttered display just to ask for the customer password as shown here:

This simplified log in form is still the standard WooCommerce log in form whose selector is “form.woocommerce-form.woocommerce-form-login.login”, but modified as follows by JS code:
- Hide ‘Remember Me’ checkbox (We don’t like “Remember Me” as mentioned below).
- Inhibit Submit for the form. We leave only three ways out of this form:
- Enter the correct password.
- Choose ‘Lost your password?’.
- Abandonment by choosing a main menu item, such as ‘Shop’.
- Catch Enter/Return on the password field and treat it as Tab.
- Set a flag to know if the value has changed in the password field.
- Set a flag to know that we clicked the show password eye. That way be know that the password is not necessarily being offered yet but just viewed by the customer.
- Make the log in form visible. (Normally this happens when someone says they are a Returning Customer.)
- Hide the Checkout form.
- Change the ‘Username or Email Address’ label to say ‘Email Address (fixed as given)’.
- Set the Email Address field and disable it.
- Change the Log In form header to ‘Checkout Log In’.
- Change the Log in form invitation to ” You have shopped with us before….”
- Change the password field label to distinguish password alone or password with 2FA code appended.
Recognise Returning Customer and ask for their Password ( JS code)
function is_email_a_customer() {
//Is this a customer email address?
billing_email = $("#billing_email").val() ;
nonce = TO_CUSTOM_JS.nonce ;
$.ajax({
type : "post",
dataType : "json",
url : ajaxurl,
data : {action: "is_known_email", email: billing_email, nonce: nonce },
success: function(response) {
if(response.type == "success") {
// do we know this email?
if (response.user_id > 0) {
// hide rememberme on login form
$( 'label' ).has( '#rememberme' ).css( "display", "none" ) ;
// inhibit the submit on logon form
selector = "form.woocommerce-form.woocommerce-form-login.login" ;
element = document.querySelector( selector );
if ( typeof element !== 'undefined' && element ) {
element.setAttribute( "onsubmit", "event.preventDefault(); return false;") ;
}
// catch Enter/Return and make it a Tab on password field
selector2 = "input#password.input-text.woocommerce-Input" ;
element = document.querySelector( selector2 );
if ( typeof element !== 'undefined' && element ) {
// focus to login button
selector = "button.woocommerce-button.button.woocommerce-form-login__submit" ;
element.setAttribute("onKeyDown", "ModifyEnterKeyPressAsTab('" + selector + "');");
// when focus comes to the password field, reset the show password flag if it's true
element.setAttribute( "onfocus", "UndoPWclick() ;") ;
} else {
missing_selector( selector );
}
// know if password data has been entered
element.setAttribute( "onchange", "pwEntered( true ) ;") ;
//set flag if show password clicked
selector3 = "button.show-password-input" ;
element = document.querySelector( selector3 );
if ( typeof element !== 'undefined' && element ) {
element.setAttribute( "onclick", "ShowPWclick( true , false ) ;") ;
}
// Get rid of any Alert notice box
selector = '[id^="anb-id-"]' ;
element = document.querySelector( selector );
if ( typeof element !== 'undefined' && element ) {
element.style.display = "none";
}
//make login form visible
selector = "form.woocommerce-form.woocommerce-form-login.login" ;
element = document.querySelector( selector );
if ( typeof element !== 'undefined' && element ) {
element.style.display = "block";
//$( selector ).get(0).focus({ focusVisible: true }) ;
}
// hide the checkout form
selector = "form.checkout.woocommerce-checkout" ;
element = document.querySelector( selector );
if ( typeof element !== 'undefined' && element ) {
element.style.display = "none";
}
// Change the email/username label
label = 'Email address (fixed as given)' ;
changeLabel( 'username', label ) ;
//set email/username in login form
value = response.email ;
$("input#username").val( value );
$("input#username").attr("disabled", true);
// Change the login form header
selector = "h1.entry-title" ;
element = document.querySelector( selector );
if ( typeof element !== 'undefined' && element ) {
element.textContent += " Log In";
}
// Change the login form Invitation
// https://stackoverflow.com/questions/3813294/how-to-get-element-by-innertext
const paras = document.querySelectorAll("p") ;
// https://reactgo.com/javascript-find-is-not-a-function/
let para_shopped = [...paras].find(function( para ) {
let test = para.textContent.includes("If you have shopped with us before") ;
return test ;
}) ;
para_shopped.textContent = shopped_before ;
// Change the password label
if ( response.twofa ) {
label = 'Show or enter password. Append 2FA code please.' ;
} else {
label = 'Show or enter password please' ;
}
label += ' <span class="required" aria-hidden="true">*</span>' ;
changeLabel( 'password', label ) ;
$( '#password' ).get(0).focus({ focusVisible: true }) ;;
password_for_email( selector2, para_shopped ) ;
} else {
selector = '#billing_first_name' ;
element = document.querySelector( selector );
if ( typeof element !== 'undefined' && element ) {
$( selector ).get(0).focus({ focusVisible: true }) ;
} else {
missing_selector( selector );
}
// END of do we know this email?
}
// END of is_known_email response.type success
}
// END of is_known_email function(response)
}
// END of Is this a customer email address?
});
}
// See Static Variables in JavaScript
// https://stackoverflow.com/questions/1535631/static-variables-in-javascript
function ShowPWclick( show = false, reset = false ) {
if ( typeof ShowPWclick.clicked == 'undefined' ) {
ShowPWclick.clicked = false ;
}
if ( show ) ShowPWclick.clicked = ! ShowPWclick.clicked ;
if ( reset ) ShowPWclick.clicked = false ;
return ShowPWclick.clicked ;
}
function UndoPWclick() {
// When the password receives focus back from Show password
if ( ShowPWclick() ) {
ShowPWclick( false , true ) ;
}
}
function pwEntered ( entered = false, reset = false ) {
if ( typeof pwEntered.pwd == 'undefined' ) {
pwEntered.pwd = false;
}
if ( entered ) pwEntered.pwd = true;
if (reset ) pwEntered.pwd = false;
return pwEntered.pwd ;
}Check the Password and Log In the Customer
The blur event on the password field alerts us to visitor interaction, but we don’t want to check the password if all the visitor is doing is clicking the eye (show password) and has not entered any new password value. However, the blur event happens before the click on the eye, so we cannot go ahead to check the password until we know the visitor actually intends that. Our solution, we delay whether or not to check the password to allow any click of the eye to be noted.
Below is our AJAX code for authenticating the password supplied. Although there is now a standard Wordpress function to authenticate the password for a user email address we need to go via wp_signon() in case Wordfence 2FA is required for the customer. If the credentials are correct, we go ahead and log in the customer in in the same AJAX function. Our implementation is using admin-ajax.php.
If the user_id returned from the AJAX call is greater than 0 then the credentials were correct and the visitor has been logged in. To make the login clear, we display an alert message to that effect. When the customer acknowledges the alert, we reload the checkout form which now, as for any logged in customer, has the details pre-filled. Magic.
Timely Check Password and Log in the Customer (JS and PHP AJAX code)
/* JS code */
function password_for_email( selector, para ) {
$( selector ).blur( function() {
// do we have a password to check?
if($( selector ).val()){
window.setTimeout(password_or_show, 1000, selector, para);
} else {
element = document.querySelector( selector );
element.setAttribute("type", "password") ;
// END of do we have a password to check?
}
// END of Has the user entered the correct password
}) ;
}
let password_or_show = function( selector, para ) {
no_wfls_message();
// Was this a click on show password?
let PWclick = ShowPWclick() ;
// has the password data been changed
let PWnew = pwEntered() ;
if ( PWnew || ! PWclick) {
element = document.querySelector( selector );
element.setAttribute("type", "password") ;
// Assume correct to change the label in time
changeLabel( 'password', 'Valid password. Thank you.') ;
para.style.display = "none" ;
// Is this the correct password?
nonce = TO_CUSTOM_JS.nonce ;
$.ajax({
type : "post",
dataType : "json",
url : ajaxurl,
data : {action: "is_password_for_email", password: $( selector ).val() , email: billing_email, nonce: nonce },
success: function(response) {
if(response.type == "success") {
// password (and 2FA if in use) correct
// user has been logged in : reload checkout
// https://codeworks.me/blog/how-to-reload-a-page-in-javascript/
alert("Customer email " + billing_email + " logged in. Please press OK or Close to continue the checkout.") ;
window.location.reload(true);
}
if(response.type == "error") {
let err_msg = response.message ;
changeLabel( 'password', 'Wrong credentials. Try again?') ;
para.textContent = shopped_before ;
para.style.display = "block";
// Focus the password
$( selector ).get(0).focus({ focusVisible: true }) ;
}
}
// END of Is this the correct password?
}) ;
// Reset password entered
pwEntered( false , true ) ;
// END of Have we just shown the password?
}
// Reset the show PW click
ShowPWclick( false , true ) ;
}
/* PHP AJAX code */
// known email?
function is_email_known() {
$email = $_REQUEST['email'];
$user = get_user_by( 'email', $email );
$nonce = $_REQUEST['nonce'];
$valid_nonce = wp_verify_nonce( $nonce, DSW_NONCE);
if ( $valid_nonce ) {
$user_id = $user === false ? 0 : $user->ID ;
$result['type'] = "success";
$result['user_id'] = $user_id ;
$result['email'] = $email ;
$result['twofa'] = is_wordfence_2fa_active_for_user( $user_id ) ;
ajax_result($result);
} else {
$subject = "Invalid Nonce Provided" ;
$message = "is_email_known Email: $email Nonce: $nonce" ;
$sent = wp_mail( ADMIN_EMAIL, $subject, $message );
$log_message = $subject . ' message is: ' . $message . ' Was sent? ' . $sent;
write_log($log_message);
die();
}
}
/**
* Programmatically check if a specific WordPress user has active Wordfence 2FA.
*
* @param int $user_id The ID of the user to check.
* @return bool True if Wordfence 2FA is active, false otherwise.
*/
function is_wordfence_2fa_active_for_user( $user_id ) {
global $wpdb;
// Wordfence stores 2FA records in the '{prefix}wfls_2fa_secrets' table
$table_name = $wpdb->prefix . 'wfls_2fa_secrets';
// Verify the table actually exists before querying it to prevent fatal errors
if ( $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table_name ) ) !== $table_name ) {
return false;
}
// Query the database to check if a row exists for this user ID
$query = $wpdb->prepare(
"SELECT COUNT(*) FROM `$table_name` WHERE `user_id` = %d",
$user_id
);
$count = $wpdb->get_var( $query );
// If the count is greater than 0, 2FA is active
$active = (int) $count > 0;
return $active ;
}
// password for email?
// How to check username/password without signing in the user
// https://wordpress.stackexchange.com/questions/61353/how-to-check-username-password-without-signing-in-the-user
// How to login with email only no username? ^1 answered May 21, 2022 at 12:24 philsbury
// https://developer.wordpress.org/reference/functions/wp_authenticate_email_password/
// https://wordpress.stackexchange.com/questions/51678/how-to-login-with-email-only-no-username
function is_email_password() {
$password = $_REQUEST['password'];
$email = $_REQUEST['email'];
$nonce = $_REQUEST['nonce'];
$valid_nonce = wp_verify_nonce( $nonce, NONCE_ACTION);
if ( $valid_nonce ) {
// Pass your credentials through the global filter chain so Wordfence intercepts it
$credentials = array(
'user_login' => $email, // Can be email or username
'user_password' => $password,
'remember' => false
);
// This automatically runs wp_authenticate_email_password() AND Wordfence 2FA checks
$auth = wp_signon( $credentials, is_ssl() );
//$auth = wp_authenticate_email_password(NULL, $email, $password);
if ( is_wp_error( $auth ) ) {
// If Wordfence intercepts for 2FA, it returns a specific error or handles a redirect
$result['type'] = "error";
$result['message'] = $auth->get_error_message();
} else {
$result['type'] = "success";
// Correct password and 2FA if active
$auth_id = $auth->ID ;
$result['user_id'] = $auth_id ;
// log the user in
// https://developer.wordpress.org/reference/functions/wp_set_current_user/
wp_set_current_user( $auth_id, $auth->user_login );
wp_set_auth_cookie( $auth_id );
do_action( 'wp_login', $auth->user_login, $auth );
}
ajax_result($result);
} else {
$subject = "Invalid Nonce Provided" ;
$message = "is_email_password Email: $email Nonce: $nonce" ;
$sent = wp_mail( ADMIN_EMAIL, $subject, $message );
$log_message = $subject . ' message is: ' . $message . ' Was sent? ' . $sent;
write_log($log_message);
die();
}
}Wordfence Compatibility Enhancements
authors-books runs Wordfence Premium and this has required some enhancements, importantly for accounts with 2FA active.
- Wordfence intercepts some login messages and obfuscates the user identity. We don’t want to see these messages, so we hide them. (See no_wfls_message() in the above JS code).
- In the original implementation we were using wp_authenticate_email_password however this is not intercepted by Wordfence. If one uses wp_signon with an email address in the credentials, this will run wp_authenticate_email_password and Wordfence 2FA checks. (See reference to Wordfence 2FA checks in the PHP code above).
- The way to pass the 2FA code in this case is to append it to the password, so we want to:
- Know that the user has 2FA required. (See is_wordfence_2fa_active_for_user() in the PHP code above).
- Change the label on the password field to ask for the password with the 2FA code appended.
Risk? Not really. Fallback is trivial.
What to do if WooCommerce changes so that this approach doesn’t work? Easy. This all hangs around the test to see if we’re trying to recognise the visitor and is the visitor logged in. So we just initialise the constant recog_email as false and we’re back to standard WooCommerce checkout, whatever that may be.
Our Dislike and Hiding of “Remember Me”
We had to understand what “Remember me” actually did. So why ask someone to check the box if it’s unclear what its purpose is? Or indeed, what its risks are? So we’ve hidden “Remember me” everywhere by CSS.


























