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;
}



jumoke
September 16, 201010:04 amYou 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!
Seye Kuyinu
October 18, 20107:00 amJumoke, I still wonder why you are still on blogger. Like really! You are just utterly limited to too many things
5.11 tactical
September 16, 201010:18 amwrodpress is truly a great platform, its got lot of tools to customize, thanks for sharing this information….
Jacob Gube
September 19, 20108:30 pmCheers for this, I managed to get it working on my own blog
André Morgan
September 30, 20101:25 pmThis is perfect for clients, instruction. Can a link be added to contact the administrator.
Pushpinder Bagga
October 14, 201011:36 pmThanks mate – that was really helpful.
Could we similarly block an IP from accessing the website?
Steve
October 20, 201012:38 amNice 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.
John Kolbert
October 21, 20101:54 amYep, it goes into a plugin or theme’s functions.php file
Carl
November 3, 201012:24 pmNice 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.
Brandon Bowman
November 9, 20105:30 pmGreat 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
Vladimir
November 21, 20106:36 amThanks for this post and useful code examples. It is a good addition to the codex documentation how to use ‘authenticate’ filter in WordPress.
Web Malama
February 10, 201112:54 amThanks 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.
Nick
May 26, 201112:00 amHow 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
Will Norris
January 9, 20125:34 amtotally doable… that is exactly what the Shibboleth plugin does.
Matt
July 1, 20116:48 amJohn, 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?
kannan
August 19, 20111:33 pmThis is a very good WP tweak info, Thanks Man