1

I've built a small, but working code of logging in an user based on his ID:

$user_to_login = get_user_by( 'id', 1 );

// Check if user exists
if( $user_to_login ) {
    wp_set_auth_cookie( $user_to_login->ID, False );

    /**
     * Fire up the login process.
     */
    do_action('wp_login', $user_to_login->name, $user_to_login );
}

I fire this on init:10 but I thought to myself "Huh, a lot of plugins actually start here and there might be processes which depend on an user object already.". When exactly is it safe / good to fire this function?

0

If the request is not authenticated by the time it enters init you can run your authentication logic if you wish.

If you want to be mindful of other plugins, you could run on init with a priority of 0 as an example (or a negative value).

Plugins (may) hook onto init at the default priority or a priority above the default and or another hook.

You won't catch all edge cases.

Keep in mind, setting wp_set_auth_cookie alone may not be enough.

The alternative is to hook into plugins_loaded.

This (plugins_loaded) is the earliest at which the method is_user_logged_in becomes available.

Internally is_user_logged_in looks as follows:

   function is_user_logged_in() {
       $user = wp_get_current_user();

       return $user->exists();
   }

I explain the mechanics of wp_get_current_user below:

The global user object global $current_user is first populated before init.

Specifically in wp-settings.php:


/**
 * Fires after the theme is loaded.
 *
 * @since 3.0.0
 */
do_action( 'after_setup_theme' );

// Set up current user.
$GLOBALS['wp']->init();

/**
 * Fires after WordPress has finished loading but before any headers are sent.
 *
 * Most of WP is loaded at this stage, and the user is authenticated. WP continues
 * to load on the {@see 'init'} hook that follows (e.g. widgets), and many plugins instantiate
 * themselves on it for all sorts of reasons (e.g. they need a user, a taxonomy, etc.).
 *
 * If you wish to plug an action once WP is loaded, use the {@see 'wp_loaded'} hook below.
 *
 * @since 1.5.0
 */
do_action( 'init' );

  • $GLOBALS['wp']->init(); calls WP::init which calls wp_get_current_user which calls _wp_get_current_user.

  • _wp_get_current_user populates global $current_user.

  • _wp_get_current_user fires the filter determine_current_user.

function _wp_get_current_user() {

    // ... shortened for brevity

    $user_id = apply_filters( 'determine_current_user', false );
    if ( ! $user_id ) {
        wp_set_current_user( 0 );
        return $current_user;
    }

    wp_set_current_user( $user_id );

    return $current_user;
}

There are two default (core) callbacks that hooked onto determine_current_user:

add_filter( 'determine_current_user', 'wp_validate_auth_cookie' );
add_filter( 'determine_current_user', 'wp_validate_logged_in_cookie', 20 );

This is essentially what validates the validity of the user's authentication and if valid, determine_current_user will return a corresponding user ID which finally gets passed to wp_set_current_user( $user_id ).


Therefore you might be best off hooking onto plugins_loaded:

function wpse_356655_custom_auth_callback() {

    // ... shortened for brevity

    $user_to_login = '...';

    wp_set_auth_cookie( $user_to_login->ID, true );

    // if you want is_user_logged_in to work you should set `wp_set_current_user` explicityly
    wp_set_current_user( $user_to_login->ID );

    do_action('wp_login', $user_to_login->name, $user_to_login );

}

add_action( 'plugins_loaded', 'wpse_356655_custom_auth_callback' );

You may also wish to look further into the wp_signon core function, which may also suit your needs.

Update #1

In relation to your comment here

I cannot say this is the best approach but it is viable.

function wpse_356655_custom_auth_callback() {

    // ... shortened for brevity

    $user_to_login = '...';

    wp_set_auth_cookie( $user_to_login->ID, true );

    // if you want is_user_logged_in to work you should set `wp_set_current_user` explicityly
    wp_set_current_user( $user_to_login->ID );

    // from within this callback, hook on to `init`
    add_action('init', function() use($user_to_login) {

        do_action('wp_login', $user_to_login->name, $user_to_login );

    });

}

add_action( 'plugins_loaded', 'wpse_356655_custom_auth_callback' );
  • Very, very insightful answer and I cannot stress enough the need for more people like you in the eco-system. Deep understanding of a system allows you to write good code that's considerate of others. – Daniel M Jan 18 at 1:31
  • Also, no, wp_signon doesn't cut it, because that's just a function to actually sign an user in with a password provided as well. It calls wp_set_auth_cookie is called. So far, in my testing, it hasn't failed. – Daniel M Jan 18 at 1:33
  • No worries, glad it was helpful. Re: wp_signon that is correct, not the suitable function in all cases, but sometimes - if working with credentials. That said, custom auth as per your method is equally fine. – Adam Jan 18 at 2:08
  • I discovered a sutle issue with hooking into plugins_loaded. The problem is that plugins that hook into wp_login or any other hooks provided by the 2 functions we use inside ours...**don't have a chance to do so, unless they fire before plugins_loaded. Does it make sense? We fire wp_login before anyone had a chance to hook in. – Daniel M Jan 18 at 2:49
  • @DanielM Yeah it makes sense. The question is, at what point do you want to make wp_login available? If we take a leaf from what other large plugins like WooCommerce and Easy Digital Downloads do (for example) — they utilise wp_loaded and init respectively. Take WooCommerce WC_Form_Handler::process_login, this method calls wp_signon which internally will eventually fire the wp_login hook BUT at this point WooCommerce choose to process WC_Form_Handler::process_login on the wp_loaded hook, which comes after init. See my update for a possible solution. – Adam Jan 18 at 6:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.