While working on an update for the Absolute Privacy WordPress plugin, I decided to re-vamp how the plugin handles authentication. The current version of the plugin (1.3), uses a custom wp_authenticate function which bypasses the standard authentication found in pluggable.php. This has essentially been the standard method of using the functions contained in the pluggable file.

Beginning with WordPress 2.8, an ‘authenticate’ filter was added which allowed for some flexibility when authenticating a user with out having to completely overwrite the wp_authenticate function. Unfortunately, using this filter wasn’t well documented.

Below is the wp_authenticate function as found in WordPress 3.0.1:


function wp_authenticate($username, $password) {

     $username = sanitize_user($username);
     $password = trim($password);

     $user = apply_filters('authenticate', null, $username, $password);

     if ( $user == null ) {
        // TODO what should the error message be? (Or would these even happen?)
        // Only needed if all authentication handlers fail to return anything.

     $user = new WP_Error('authentication_failed', __('<strong>ERROR</strong>: Invalid username or incorrect password.'));

     }

      $ignore_codes = array('empty_username', 'empty_password');
      if (is_wp_error($user) && !in_array($user->get_error_code(), $ignore_codes) ) {
            do_action('wp_login_failed', $username);
      }
      return $user;
}

The authenticate filter is found on line 6. It passes both the username and the password and assigns the result to $user. This is where we will add our custom authentication criteria. In our case, let’s do something simple. Let’s deny access if the $username is ‘bob’. Of course, you could be more functional and deny access by user role (like Absolue Privacy does), or what have you. In addition, let’s give a meaningful error message so Bob knows what’s going on.

First, let’s hook into the authenticate filter:

 add_filter( 'authenticate', 'my_custom_function', 10, 3 ); 

This is adding the ‘my_custom_function’ to the ‘authenticate’ filter, with a priority of 10, and passing 3 arguments (null, $username, and $password).

Now let’s write our function which will check the username:


function my_custom_function( $user, $username, $password ){

     $user = get_userdatabylogin( $username );  //we don't really need this, but you might

     if( $username == 'bob'  ) {  //if the username is bob

        $user = new WP_Error( 'denied', __("<strong>ERROR</strong>: We do not allow people with the name Bob into this site") );

     }

     return $user;

}

This function simply checks if the username is Bob. If it is, it assigns $user to be a new WP_Error with the name of “denied”. If you were to try this function out now, you’ll notice that someone with the username “Bob” is still allowed to login. Why? Because unfortunately the authentication sequence isn’t aborted if $user is assigned as a WP_Error.

To prevent the authentication sequence from continuing we must remove the username/password authentication action:

 remove_action('authenticate', 'wp_authenticate_username_password', 20); 

With this addtion, our custom function now becomes:


function my_custom_function( $user, $username, $password ){

     $user = get_userdatabylogin( $username );  //we don't really need this, but you might

     if( $username == 'bob'  ) {  //if the username is bob

        $user = new WP_Error( 'denied', __("<strong>ERROR</strong>: We do not allow people with the name Bob into this site") );
        remove_action('authenticate', 'wp_authenticate_username_password', 20);

     }

     return $user;

}

You have now successfully prevented Bob from logging into your WordPress powered site! For a little extra flare, let’s get the login box to shake “NO” when the error message appears. Create a new function below:


function my_custom_error_shake( $shake_codes ){

     $shake_codes[] = 'denied';
     return $shake_codes;
}

This function will add the ‘denied’ $WP_Error that we created into the list of error codes that cause the login box to shake. Finally, we’ll add the following filter if Bob tries to login:

add_filter('shake_error_codes', 'my_custom_error_shake');	//make the login box shake

So with the above addition, our final code looks like this:

add_filter( 'authenticate', 'my_custom_function', 10, 3 );

function my_custom_error_shake( $shake_codes ){
    $shake_codes[] = 'denied';
    return $shake_codes;
}

function my_custom_function( $user, $username, $password ){
    $user = get_userdatabylogin( $username );  //we don't really need this, but you might

    if( $username == 'bob'  ) {  //if the username is bob
        $user = new WP_Error( 'denied', __("<strong>ERROR</strong>: We do not allow people with the name Bob into this site") );
        remove_action('authenticate', 'wp_authenticate_username_password', 20);
        add_filter('shake_error_codes', 'my_custom_error_shake');	//make the login box shake
     }

     return $user;
}


Reply

jumoke

September 16, 201010:04 am

You guys on wordpress are so lucky. you have so much variety to choose from. We blogger peeps are limited. i get so jealous when ever i see something really good, but then alas, its for wordpress!

    Reply

    Seye Kuyinu

    October 18, 20107:00 am

    Jumoke, I still wonder why you are still on blogger. Like really! You are just utterly limited to too many things

Reply

5.11 tactical

September 16, 201010:18 am

wrodpress is truly a great platform, its got lot of tools to customize, thanks for sharing this information….

Reply

Jacob Gube

September 19, 20108:30 pm

Cheers for this, I managed to get it working on my own blog :)

Reply

André Morgan

September 30, 20101:25 pm

This is perfect for clients, instruction. Can a link be added to contact the administrator.

Reply

Pushpinder Bagga

October 14, 201011:36 pm

Thanks mate – that was really helpful.

Could we similarly block an IP from accessing the website?

Reply

Steve

October 20, 201012:38 am

Nice article. I’m left wondering one thing. Where does one put this final code? Is this placed in a plug-in or some place else? Ideally, some file that doesn’t get overwritten when WP is updated.

    Reply

    John Kolbert

    October 21, 20101:54 am

    Yep, it goes into a plugin or theme’s functions.php file

Reply

Carl

November 3, 201012:24 pm

Nice guide John, actually few days ago I have installed few of your plugins in my WordPress blog. I was happily surprised when I saw your name as I am visiting your blog quite often.

Reply

Brandon Bowman

November 9, 20105:30 pm

Great tutorial. Does anyone know how to change the code use First and Last name of the user to login into WordPress. So instead of two fields, there will be three, First, Last, Password. Thanks

Reply

Vladimir

November 21, 20106:36 am

Thanks for this post and useful code examples. It is a good addition to the codex documentation how to use ‘authenticate’ filter in WordPress.

Reply

Web Malama

February 10, 201112:54 am

Thanks John, this saved me a lot of time today and helped me create a nice plugin allowing my client’s site to first authenticate at an internal database and then in WordPress.

Well written and clear. Keep up the good work.

Reply

Nick

May 26, 201112:00 am

How about if you want your own authentication but also want to bypass the logon page. Our users will be authenticated by a IIS httpmodule and it will set a server variable that wordpress can look at. I then want wordpress to do the rest

Reply

Matt

July 1, 20116:48 am

John, this is great. Upon logging in, I wish to call a webservice to perform some custom authentication and then manually authenticate the user in WordPress – perhaps by creating a parallel WordPress user and logging in the parallel WordPress user – a mapping between our authentication and the WordPress login so to speak. I’m just unsure whether I should do this in the custom function or somewhere else and once passed our authentication, how to then manually log in the WordPress account. Any thoughts?

Reply

kannan

August 19, 20111:33 pm

This is a very good WP tweak info, Thanks Man

Comment on this post:

License: By commenting you are granting me a perpetual, non-exclusive license to reproduce, paraphrase, and display your words, name, and/or website on this domain. All comments subject to moderation, editing, or deletion at my discretion.

Important: Unfortunately, I'm unable to offer support via comments. However, feel free to hire me.

Entering code?

(optional)