WordPress.org

WordPress Developer Blog

How do Log in and Registration work for developers?

When developing user authentication systems for WordPress, it’s critical to have a profound understanding of the login and registration processes. This post will shed some light on the intricacies of these processes, and the underlying functions that ensure secure and seamless user experiences in WordPress sites. By the end of this documentation, developers will have a comprehensive idea of how Login and Registration work in WordPress, and how to hook into each step of the process to implement custom functionalities.

How does logging in work in WordPress?

To log into a WordPress site, a user needs to enter their credentials through the login form found at the /wp-login.php endpoint. This endpoint can be changed by using custom coding, but by default, this remains the login endpoint. This form is like a gateway, where users provide their username and password. Once the user submits this information, a POST type of request is sent to the server. This request is then handled by the wp-login.php file, which acts as the main entrance for the entire login procedure.

In wp-login.php, user credentials are validated using the wp_authenticate() function. If the provided username and password match the database records, the user is authenticated. Otherwise, a WP Error is triggered.

Authenticate Filter

Plugin developers can use the authenticate filter to intercept this process and introduce additional validation steps or alternative authentication methods. This enables the integration of external authentication providers or advanced security mechanisms. Below, you will find an example of how to add custom validation logic for the login process.

function wporg_custom_login_validation($user, $username, $password {
   // You can add your custom validation logic here
    if (some_custom_condition()) {
       return new WP_Error('custom_error', __('Custom validation failed.', 'your-text-domain'));
    }
    return $user;
}
add_filter('authenticate', 'wporg_custom_login_validation', 10, 3);

You can also add custom fields for your login page. For example, below is a demonstration of how to add a required checkbox that must be checked, before a user can log in.

function wporg_custom_login_field(){
?>
    <p>
        <label for="my_extra_field">I agree to the terms and conditons< />
        <input type="checkbox" value="1" class="input" id="custom_login_field" name="custom_login_field_name"/>
</label>
    </p> <?php
}
add_action('login_form','wporg_custom_login_field');

function wporg_check_checkbox($user, $password) {
    if( !isset($_POST['custom_login_field_name']) )
    {
        remove_action('authenticate', 'wp_authenticate_username_password', 20);
        $user = new WP_Error( 'denied', __('<strong>ERROR</strong>: Please agree to our terms and conditions to login.', 'your-text-domain') );
    }
    return $user;
}
add_filter( 'wp_authenticate_user', 'wporg_check_checkbox', 10, 3 );

Cookies are set

Once the authentication process succeeds, WordPress generates authentication cookies. These cookies are like digital markers that help maintain the user’s session. The two most important cookies for authentication in WordPress are:

  • wordpress_logged_in_{hash}
  • wordpress_sec_{hash}

Now, these newly created authentication cookies need to be put in the user’s browser. WordPress does that using the setcookie() function. This way, any further interactions between the user’s browser and the server include these cookies. It allows the server to continuously recognize and validate the user’s session, without having the user enter the username and password each time. 

Once these cookies are successfully set in the browser, the user will often be sent to the WordPress dashboard or the exact page they were trying to access before they are logged in. The default expiration time of these cookies is two days unless the Remember Me is checked on the login form; otherwise the cookies will be kept for 14 days.

The wordpress_logged_in_{hash} cookie: This cookie includes a special code (a hash) created from the user’s username, an expiration time, and a secret key. When this cookie is set in the browser, the system identifies the user as authenticated, and logged in.

The wordpress_sec_{hash} cookie: This one also holds a hash, but this time it’s crafted using the user’s session ID, an expiration timestamp, and the same secret key as before. The purpose of this cookie is to double-check the user’s authentication session, adding an extra layer of security to the process.

Customizing authentication cookies

The generation and handling of authentication cookies can be customized using actions and filters. 

The set_auth_cookie action enables developers to modify the behavior of cookie creation and insertion, enhancing session management or introducing custom cookie data. 

Below, you will find an example of how you can increase the default cookie expiration to seven days, and to 30 days if the Remember Me is checked by the user.

function wporg_auth_cookie_expiration_filter($expiration, $user_id, $remember) {
    if ($remember) {
        return strtotime('+30 days', 0); //30 days in second.
    } else{        Return 604800; //7 days in second.
}
add_filter('auth_cookie_expiration', 'wporg_auth_cookie_expiration_filter', 10, 3);

As mentioned above, throughout the user’s active session on the website, these authentication cookies are checked every time a user requests a new page. WordPress relies on a function known as wp_validate_auth_cookie() to carry out this essential task. It ensures the user’s session remains active and valid. If the cookies are still good (that is, not expired) and they’re just as they should be, the user is still logged in and can continue using the site. 

However, if the cookies are missing or outdated, the user will be asked to log in again, starting with the Login form. The code example below shows how to perform specific tasks based on whether a cookie is valid or not.

$user_id = get_current_user_id(); // Get the current user's ID

if ($user_id) {
    // User is logged in, validate the authentication cookies
    $auth_cookie = $_COOKIE[LOGGED_IN_COOKIE];     $auth_cookie_valid = wp_validate_auth_cookie($auth_cookie, 'logged_in');

    if ($auth_cookie_valid) {         // Authentication cookies are valid, perform actions accordingly
        echo __('Authentication cookies are valid.', 'your-text-domain');     } else {         // Authentication cookies are not valid, take appropriate actions
        echo __('Authentication cookies are not valid. Please log in again.', 'your-text-domain');     } } else {     // User is not logged in, handle the case accordingly     echo __('User is not logged in.', 'your-text-domain'); }

After the user successfully logged in, developers can use the is_user_logged_in() function to check whether a particular user is logged in or not, and then trigger a certain action accordingly. For example:

if (is_user_logged_in()) {
    // User is logged in, perform specific actions
    echo __('Welcome, logged-in user!', 'your-text-domain');
} else {
    // User is not logged in, perform other actions
    echo __('Please login to access this content.', 'your-text-domain');
}

How does registration work in WordPress?

To register on a WordPress site, an end-user generally needs to navigate to the registration page or form, often accessible via the /wp-login.php?action=register endpoint. The registration process typically involves entering basic details such as a username, email, password, and so on. If additional fields are needed during registration, they can be added using the register_form action hook. For example, an administrator with coding knowledge can customize the registration form by adding fields like ‘Phone number’ using the provided code snippet:

function wporg_custom_registration_form() {
   ?>
   <p>
        <label for="phone">Phone Number<br>
           <input type="text" name="phone" id="phone" class="input" value="">
        </label>
    </p>
    <?php
}
add_action('register_form', 'wporg_custom_registration_form');

After submitting the filled out registration form, a POST request is sent to wp-login.php file. The file manages the registration and validates the user input To perform custom validation on user-submitted data during registration, one can use the registration_errors filter. In the below example, the code verifies the above-added phone number entry. 


function wporg_custom_registration_validation($errors, $sanitized_user_login, $user_email) {
    if (empty($_POST['phone'])) {
        $errors->add('phone_error', __('Phone number is required.', 'textdomain'));
    }
    return $errors;
} add_filter('registration_errors', 'wporg_custom_registration_validation', 10, 3);

Using the user_register action hook, additional information collected from the registration process are stored as user metadata.  The below is code example is to store the phone number.


function wporg_save_phone_number($user_id) {
   if (isset($_POST['phone'])) {
        $phone = sanitize_text_field($_POST['phone']);
        update_user_meta($user_id, 'phone_number', $phone);
     }
}
add_action('user_register', 'wporg_save_phone_number');

The submitted user data undergoes a comprehensive process of validation and sanitization. WordPress leverages functions like sanitize_user() and sanitize_email() to guarantee that the provided username and email are devoid of potentially harmful content, ensuring a clean and secure input.

In the quest to ensure the uniqueness of email addresses, WordPress examines whether the provided email is already associated with an existing account. The email_exists() function comes to the forefront to ascertain whether the email is already registered.

With a focus on security, the user’s elected password undergoes hashing through a robust cryptographic algorithm. The wp_hash_password() function takes center stage in this process, transforming the plain text password into a fortified hash, bolstering the security of user credentials. Note that currently, WordPress uses the MD5 hashing technique for hashing the password. If one wants to implement custom password hashing, it can be easily done using the wp_hash_password filter. 


function wporg_custom_password_hash($password) {
    $hashed_password = my_custom_hash_function($password); 
//Here the my_custom_hash_function will be hashing function
    return $hashed_password;
}
add_filter('wp_hash_password', 'wporg_custom_password_hash');

Following successful validation, scrutiny of username availability, and affirmation of email uniqueness, a fresh user account comes into existence in the WordPress database via the wp_insert_user() function. This function integrates the user’s information seamlessly into the wp_users table.

Depending on the site’s configuration, the user might be required to activate their account through an activation link dispatched to their registered email address. WordPress harnesses the wp_send_new_user_notifications() function to disburse email notifications to both the user and the site administrators, apprising them of the new registration.

Similar to the login process, WordPress creates authentication cookies, constructing a session for the recently registered user. The wordpress_logged_in_{hash} and wordpress_sec_{hash} cookies are forged, housing critical user session data.

These authentication cookies are then embedded in the user’s browser using the setcookie() function. Just like the way it is done for a user while logging in. In this case, too, the wp_validate_auth_cookie() function is invoked consistently, undertaking the validation of these cookies with every page request, thereby protecting the user’s seamless engagement within the website.

For post-registration actions, such as sending notifications or performing additional database operations, one can utilize hooks like user_register. This allows you to trigger custom actions upon successful user registration. This helps one to tailor email content and notifications as per specific requirements. For example, below is a demonstration of how to customize the email notifications sent during registration:


function wporg_custom_registration_email($user_id) {
    $user = get_userdata($user_id);
    $to = $user->user_email;
    $subject = __('Welcome to Our Website', 'your-text-domain');
    $message = __('Thank you for registering on our website. This is a custom email notification.', 'your-text-domain');

    // Customize the email content here

    wp_mail($to, $subject, $message);
}
add_action('wp_new_user_notification', 'wporg_custom_registration_email');

Additional notes regarding login and registration

Any leading or trailing spaces in the password are automatically trimmed while creating a user, or when logging in. So, no spaces before or after the password will be considered. However, it’s important to note that spaces will only be considered as part of a password input if they are present within the actual password text.

For example, if the password is “Testtest  “, then the spaces will not be counted. But, if the password is “Test  test”, then the spaces will be considered, and while logging in, the exact number of spaces needs to be used to authenticate.

All emails are converted to lowercase, so no matter if you have an email with capital letters or small letters, those will always be converted to lower letters while saving the values in the database/comparing the values with the database.

Apostrophes are allowed within the email.

The password hashing algorithm of WordPress generates a different hash each time the password changes. So, even if you change the password with the existing one, another new hash will be generated, and will be saved as an entry in the wp_users table.

Multisite: The link in the password retrieval email will always point to the main site. For example, if any user of Sub-site A tries to log in with the username, and password of Sub-site B, he is automatically redirected to Sub-site A. When a password reset is initiated, the request is processed through the main site. Also, the user is asked to reset the password through the main site’s domain link. In the email, too, the name of that main site is used.

Resources mentioned in the post:

Props to @bph, @milana_cap and @webcommsat for feedback and review on this post.

Leave a Reply

Your email address will not be published. Required fields are marked *