Quantcast
Channel: Blog – Stormpath User Identity API
Viewing all articles
Browse latest Browse all 278

5 WordPress Hacks We Used to Build Authentication

$
0
0

Stormpath just announced the release of our Stormpath WordPress plugin, which allows you to use Stormpath inside of your WordPress website. While writing this plugin, we had to “hack” the native WordPress authentication system to get our SDK working seamlessly. I’m excited to share some of the hacks we were able to leverage!

We had one goal when writing this plugin, use as much of the internal WordPress functionality we could to keep it as simple as possible. The plugin uses the same login and registration forms, as well as the same forgot and reset password workflows to make a seamless transition between using the built-in authentication with the Stormpath authentication.

Early in the build, we realized that the native WordPress login system wasn’t going to make things as easy as we’d hoped. In order to accomplish this, we had to use a collection of hooks and filters to tap into the authentication and user registration processes.

Add a Function to the Authenticate Filter

The authenticate filter is called when a user posts the form found at wp-login.php. If you open this file, you will find it’s huge (I really mean it… this thing has just under 1000 lines including comments) and it handles everything from registration to password reset. The part of this file that we are interested in is what happens when the login form is submitted.

Ultimately, a function is added to the authenticate filter that calls wp_authenticate_username_password OR wp_authenticate_email_password. This is the gold that we were hoping for. Now we know that the functions we need to work with are these two, which we’ll combine into a single function.

// Default authentication filters
add_filter( 'authenticate', 'wp_authenticate_username_password',  20, 3 );
add_filter( 'authenticate', 'wp_authenticate_email_password',     20, 3 );

The next part of this is the key to the whole system. We want to completely overwrite the functions for authentication, so we no longer need the functions that core WordPress calls. Let’s remove the filters after we add ours.

add_filter( 'authenticate', [ $this->authenticate, 'authenticate' ], 10, 3 );

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

Now, we are fully using our own authentication method when someone tries to log into our WordPress install. This all still uses the default login screens that come with core WordPress to make it seamless.

Custom Code for Password Updates

We now have the ability to have a user log into the system, but what happens when they update their profile? For our system, we need to know when they update their password so we can update our system with the change. We found that if we used the profile_update action we could hook into that and run some custom code to get the updated password.

add_action( 'profile_update', [ $this->authenticate, 'profile_update' ], 10, 2 );

This action will provide you with two variables, $userId and $oldData. You’ll notice there is no reference to the new data that is submitted, but that’s ok because we still have access to the POST variables. There are a few updates we have to do inside of the profile_update function.

First you must verify the nonce since we are posting a field and working with these values. It can be a little odd locating the nonce field we need to use to verify, but once you find it, you will see that it is update-user_$userId where $userId is the first property. In order to follow the coding standards for WordPress, you must sanitize the input and slash it. Once you combine the whole setup, you get the following:

if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'update-user_' . $userId ) ) {
  wp_die( 'nonce not valid' );
}

After the verification of the nonce passes you can safely continue with the rest of the process that you setup during the user profile changing you need when a user profile is changed.

Hack the Forgot Password Workflow

Much like the profile_updated action, the after_password_reset function is used when the forgot password workflow is finished so you can update your user object with the new password. The difference in this action is what is passed to the function. Here we are passing the new password the user entered. This is nice for us as we don’t need to verify a nonce, and we can simply use the variables from the function. Add this action:

add_action( 'after_password_reset', [ $this->authenticate, 'password_changed' ], 10, 2 );

Then take the two properties, find your user object in your database, and then update the password accordingly.

Mod User Registration From Within WordPress

This may be the best action of all! It uses the same registration form from within the WordPress admin panel but lets us access everything we need to create our own user. The internals of the user_register action is a little crazy. It happens after a user is already registered inside of the WordPress database and then will be triggered so we can hook into it. The action will provide only a user id of the user that just registered. Let’s go ahead and register the action we want to provide when a user registers and is stored in the WordPress database.

add_action( 'user_register', [ $this->authenticate, 'user_registered' ], 10, 1 );

Since we still maintain some reference to the user inside of the WordPress database, we need to get the WP_User object that was just registered.

$user = new WP_User( $wpUserId );

Next, you need to verify the nonce. We do this exactly like the last time, but using create-user as the action in the verify method.

if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce_create-user'] ) ), 'create-user' ) ) {
  wp_die( 'nonce not valid' );
}

Now, this is the part that hung me up for a while. I needed to get the user’s password so we could store it in the Stormpath system. I went through many different iterations, trying to figure out if I could somehow reverse engineer the password hash. Ultimately, I dug into the post variables and found that I still had access to them in my action. Perfect. That allowed me to get the Password field and store it in a parameter after sanitizing it. Now that I had that, I could create a Stormpath user object and store it.

$account->email = $wpuser->user_email;
$account->password = sanitize_text_field( wp_unslash( $_POST['pass1'] ) );
$account->givenName = $wpuser->user_firstname;
$account->surname = $wpuser->user_lastname;
$account->username = $wpuser->user_login;

Simple Error Messaging at Login

The last filter is a simple one that’s not needed to get authentication functional but does help you display different messages to your user when they go to login in with invalid info. At Stormpath, we believe in not providing hints on what is correct and incorrect during the login. Saying this like “The password you entered for the user is invalid” gives a hacker too much information. By default, WordPress will do this kind of thing, so we wanted a way to change it. With the login_errors filter, we can set custom messages for error codes, and even set our own error codes to display on the login form for other messages from the Stormpath system. Here is what our actual function looks like for the Stormpath WordPress plugin:

public function login_errors( $errors ) {
  global $errors;
  $err_codes = $errors->get_error_codes();

  $error = $errors->get_error_message();

  if ( in_array( 'invalid_username', $err_codes ) ) {
     $error = '<strong>ERROR</strong>: Invalid username or password.';
  }

  if ( in_array( 'invalid_email', $err_codes ) ) {
     $error = '<strong>ERROR</strong>: Invalid username or password.';
  }

  if ( in_array( 'incorrect_password', $err_codes ) ) {
     $error = '<strong>ERROR</strong>: Invalid username or password.';
  }

  if ( in_array( 'authentication_failed', $err_codes ) ) {
     $error = '<strong>ERROR</strong>: Invalid username or password.';
  }

  if ( in_array( 'stormpath_error', $err_codes ) ) {
     $error = '<strong>ERROR</strong>: There was an error logging you in. Please let the administrator know you received a ' . $errors->get_error_data()['code'] . ' code during login.';
  }

  return $error;
}

We have a few checks to see which error we are talking about and then set the error message. In the future, I plan to add the shake option, a feature that will shake the login form when there is an error. Some of the error codes in our list won’t trigger a shake, but again, because WordPress gives us a way to hook into virtually everything, we can add shakeable error codes via a filter.

//

I hope that these filters and actions give you some insight in how we hacked the authentication system of WordPress to create a plugin to do Stormpath authentication seamlessly. There are many other filters and actions that you can hook into to add your own functionality and you can find them on the WordPress Codex. I would love to hear about your experience with any fun actions and filters you set up on your own. Shoot me a tweet @bretterer with details about it.

// Brian
?>

The post 5 WordPress Hacks We Used to Build Authentication appeared first on Stormpath User Identity API.


Viewing all articles
Browse latest Browse all 278

Trending Articles