WordPress: How To Add Your Own Authentication Criteria

This tutorial shows you how to hook into WordPress’ authentication system. You can now use more than a simple username/password combination!

While working on an update for the Absolute Privacy WordPress plugin, I decided to revamp 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 without 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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
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 the $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 Absolute 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:

1
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:

01
02
03
04
05
06
07
08
09
10
11
12
13
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 the $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 log in. 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:

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

With this addition, our custom function now becomes:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
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:

1
2
3
4
5
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:

1
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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
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;
}

Leave a Reply

Your email address will not be published.