Authentication in WordPress 2.8
Use Case
I’ve spent a lot of time working with the WordPress authentication system. I took over the OpenID plugin for WordPress two years ago, and was hired by Vidoop last May to work on the DiSo Project full time. Last summer, Matt Mullenweg invited me to talk at WordCamp SF 2008 about OAuth. As you can see in my slidedeck, it was a lot of smoke and mirrors at that point… we didn’t have OAuth in WordPress, although it was on the roadmap for 2.7.
We’ve had an OAuth plugin for a little while that Stephen Paul Weber wrote, but it wasn’t until a couple of months ago that I finally sat down to polish it up. The first use-case we wanted to tackled was XML-RPC, so I got to work with Joseph Scott. Having OAuth authentication with XML-RPC would allow for blog clients like MarsEdit or the WordPress iPhone app to communicate with your blog without having to share your WordPress password.
Problem
It wasn’t very long before we butted up against my biggest complaint about the WordPress authentication system – it is very “username/password” centric. There are places in the authentication code where it bails out prematurely if the username or password are missing. This isn’t a problem if your plugin simply wants to authenticate the user against a different password store like LDAP; in fact that works quite well.
The problem is that there are a number of authentication systems widely deployed in the wild (SAML, OpenID, OAuth, etc) that do not fit the standard model of username and password. You can look at the OpenID plugin to see some of the more interesting things that need to be done in order to make it work on the various versions of WordPress. However, when it came to hooking OAuth into the WordPress XML-RPC endpoint, there simply was no way to hack around it… we had to change some of the underlying assumptions.
It’s worth noting one additional requirement we had. Because the wp_authenticate()
function, which does most of the
heavy lifting for WordPress authentication, resides in pluggable.php
it is possible for a plugin to replace the
function entirely and authenticate the user however they want. The problem with this solution is that many
authentication mechanisms don’t know if they should be invoked without examining the request. If wp_authenticate
is
replaced, and then the plugin determines it shouldn’t intervene, then it’s already too late. There is no way to pass
the function call back to the standard version of wp_authenticate()
. This is actually the case for all functions in
pluggable.php
. One possible solution is to create wrapper functions for everything, which I initially advocated.
Instead, Peter Westwood came up with a better solution using a well-established model, which we ended up using for
the new authentication system.
Solution
It took far more planning than actual coding, but we finally developed a solution that breaks the dependence on a
username and password, but maintains backward compatibility with existing plugins that hook into the authentication
code. WordPress 2.8 includes a new filter called authenticate which is passed three parameters: a mixed value (either
a WP_User
object, a WP_Error
object, or null
), along with the username and password (either or both of which may
be null
). All of the standard WordPress authentication logic has been moved into two functions that implement this
filter, both with relatively low priority.
-
wp_authenticate_username_password()
(priority 20) includes the standard logic for authenticating using a standard username and password. It still calls thewp_authenticate_user
filter, so plugins that rely on that should be fine. This function also performs the check for an empty username or password. -
wp_authenticate_cookie()
(priority 30) is only added into the filter chain when the user is authenticating viawp-login.php
and does the normal checking for the WordPress authentication cookie.
Both of these functions first check to see if the first parameter passed in is a valid WP_User
object, and immediately
stop if it is. This allows plugins to add their own functions into the filter chain which populate the WP_User
object
using whatever means they see fit. WordPress still takes care of setting the authentication cookie when appropriate, so
plugins need only worry with authenticating the user and returning a valid WP_User
object.
So what will this look like for plugins? Well, the OAuth plugin for WordPress isn’t finished yet, but the function
below should be pretty close to the final version. While there is of course a lot more code for actually implementing
OAuth, this is the only hook into the WordPress authentication system needed to make it work. Note that this function
doesn’t care about the username and password parameters that are available from the authenticate
hook… other plugins
may use them.
add_filter('authenticate', 'oauth_authenticate');
/**
* If the current request was signed using a valid OAuth access token, verify
* the request and return the associated user.
*
* @param WP_User|WP_Error|null $user authenticated user
* @return WP_User|WP_Error|null OAuth authenticated user, if request was signed
*/
function oauth_authenticate($user) {
if (Auth_OAuth_Signer::requestIsSigned()) {
$oauth_server = oauth_server();
$user_id = $oauth_server->verify();
if ($user_id !== false) {
$user = new WP_User($user_id);
}
}
return $user;
}
For Plugin Authors
If you’re currently hooking into the WordPress authentication system, especially if you’re providing a custom
implementation of wp_authenticate()
, take a look at the new authenticate
hook. If you are relying on the
wp_authenticate
action hook, you should also look closely to see if the new hook will do what you need. We left the
wp_authenticate
hook in place for now, but I’m pretty sure it’s no longer necessary and will likely be removed in
future releases. If you are using the wp_authenticate_user
hook exclusively, then you’re probably fine, but it’s
probably still a good idea to take a look at the new stuff.
So, OAuth in WordPress?
We made additional changes to the WordPress XML-RPC code to make it use the new authentication system appropriately, so it is now possible to hook OAuth into WordPress without any core modifications. We do in fact have a basic OAuth plugin that works with the trunk version of WordPress. However, I don’t think I’m going to push to have the OAuth code included in WordPress 2.8 for two reasons:
-
the OAuth libraries are in flux right now. There have been two main PHP libraries that people have used for OAuth, both with their own strengths and weaknesses. I’m currently working with the oauth-php community to combine these libraries using the best parts from each, and a new clean architecture. This effort can be found on github. (This library also requires PHP5 which is a deal breaker for WordPress… not sure how we’ll manage that.)
-
Because OAuth has the potential to be such an important part of how third party clients interact with a WordPress blog, I want to make sure we get this right. Personally, I’d feel much more comfortable getting some real world experience with this code in a slightly more constrained environment by releasing it as a plugin first. Once we’ve done that and are comfortable with how it integrates into WordPress (plugin API, admin interface, database schema, etc), I’m all for making it a core part of WordPress.
Comments and responses
I certainly agree that WordPress is pretty hefty as it is. However, as more and more services are using OAuth for API access, it begins to make a lot of sense that it should be a core capability of the platform. PollDaddy, an Automattic property, launched a new API today which does not use OAuth. Perhaps they would have used OAuth if it were available in WP core? Who knows.
I definitely think OAuth in WP core makes a lot more sense than OpenID in WP core… whether they realize it or not, there are far more users that would make use of OAuth if it were available to plugin authors.
(This comment turned into a bit of a brain dump, so I apologise in advance.)
When I attacked this problem in a different system (which is unfortunately not open source) I solved it by identifying three separate pluggable components:
-
Authentication frontend. This is responsible for showing some UI, which is usually a HTML form. This is nothing more than a standard action, controller, or whatever such things are called in your framework that happens to call into the “create session” and “destroy session” methods on a particular configured session manager.
-
Session manager. On each request, this component is asked “who is the remote user?” and responds with a user object. It also can optionally provide “start session” and “end session” methods if it supports session state, but since HTTP auth is stateless anything HTTP auth like does not provide these methods. This was implemented as a plugin class with a specific interface.
-
Username/password backend. Used by frontends that deal in usernames and passwords (which, in this case, were either a username/password web form or a basic authentication frontend) to actually check a username/password pair, since username/password is a common enough case for it to be worth having an extra layer for. This was again implemented as a plugin class with a specific interface.
The default case is a HTML forms username/password-based frontend for logging in, a standard “ask the user and then close the session” frontend for logging out, a cookie-based session manager (which supports session state) and a user/password backend that looks at the local DB. You can swap out the user/password backend to support LDAP or whatever.
To add OpenID support for end-users, you use an OpenID frontend, which can either replace or supplement the username/password frontend. The rest of the stack remains the same as username/password.
For an API that uses OAuth, you use a stateless session manager that can validate OAuth arguments and some other external, OAuth-specific gunk to do the request token endpoint and so on. You don’t need any frontend in the traditional sense, but you do need to provide some “frontend” to do the OAuth request token endpoint and so forth. Since it’s not username/password based, you don’t need a username/password backend.
It looks like this Wordpress approach conflates the frontend with the session manager, unless I’m missing something. This presumably means that the thing that does the username/password authentication is creating the session cookie and is therefore tightly coupled with the cookie-based authenticator. It’s also not obvious to me how I’d implement Basic auth support if the username/password thing can only deal with username/password submitted via an HTML form. Maybe I’m misunderstanding, though.
One thing missing from my approach is that in the OAuth case you generally have two authenticated parties: the application that’s making the request and the user the request is on behalf of. I didn’t ever solve this problem, and I don’t think it’s really addressed by your solution either unless I’m missing it.
It seems like the solution in both cases is to split the concept of the remote user in two; there’s the user that’s making the request and the user they’re acting as. In all except the OAuth case they’re the same. It does seem kinda lame to bleed OAuth-specific concepts into the main API, but this would in theory be useful if you wanted to implement some other delegated auth scheme.
There is definitely a little bit I left out because it wasn’t necessarily relevant to the post… wp_authenticate
is sort of a manager for your backend authentication methods. Rather than switching out one for another as you describe, you basically add your authentication method somewhere in the chain and the first one to authenticate the user wins.
During a typical user login flow, wp_authenticate
is not called directly by the front-end handler. Instead, wp_signon
is responsible for taking the HTML form post from wp-login.php
, calling wp_authenticate
to validate the credentials, and then setting the authentication cookie for the user. When using OAuth with XML-RPC, wp_authenticate
is called directly (with an empty username and password), so there is no state management.
There are probably still a few unnecessary layers in the WordPress authentication code as a result of multiple redesigns in the past. I’m sure those will get cleaned out at some point after they’ve been formally deprecated for a time, but it’s really not too bad now. For basic auth, you’d just have a custom frontend handler that calls wp_authenticate
, but doesn’t bother with the session cookie.
As for the problem of users versus applications authenticating… I have definitely spent a lot of time thinking about this. There are notes within the WordPress auth code that make reference to a conceptually distinct “logged in user” and “current user”, though I can’t actually find anywhere in the code where these are separated… perhaps just a remnant of an old design. Right now the OAuth plugin simply authenticates the application as the user with full privileges… it’s not ideal, but it’s absolutely no worse than doing XML-RPC with the user’s password. However, because the WordPress permissions system is tied to the WP_User
class, my plan is to create a custom WP_User
subclass that has the ID of the actual user, but with limited permissions which match the scope of the OAuth token. Having wp_authenticate
simply return a WP_User
object makes this very clean.
The OAuth plugin link ( http://wordpress.org/extend/plugins/oauth )
doesn`t seem to work
Will’s been doing great work here.
I’m excited at the prospect of having OAuth support in core WordPress. I see this as an important foundation piece that will allow WordPress to expand it’s abilities as an application platform.
@Ramon: the plugin isn’t in the WordPress directory yet… that’s intentional. It’s not not quite ready for users.
@Khürt: I think you’re misunderstanding what OAuth is for… it’s not for authenticating to a website yourself, it’s for API authentication. What you’re describing is still OpenID, whether you initiate with a big button or by typing a URL.
@Stephen: actually no, we haven’t gotten AtomPub working yet. I guess I just forgot :). I’ll definitely try and get those changes in before 2.8 releases.
@Will: similar to @Khurt, does OAuth’s chaining of authentication (which sounds a lot like Apache’s logic) make it easier to swap in different autentications (such as LDAP, which actually isn’t as clean as you imply) or the possibility of chaining an OpenID authentication-check there?
To be more brief: does it ismplify the rest of the auth stuff?
Hi Will,
thanks for your OAuth approach in WordPress. As we can see on Twitter there is a big demand for this kind of solutions, because user/password is very 1.0. ;)
Best Regards from German Twitter Trends.
Hi WIll, Thank you for this write up. I have an LDAP authentication plugin that currently relies on replacing wp_authenticate(). I did not notice the new filter until this morning as my plugin continued to work after the transition from 2.7.x to 2.8.x.
I do have one question for you that I am having troubles answering. It seems that the filters are applied in order of priority. For example, authenticating with username/password in WP is set as a priority 20 while using a cookie is set at 30. I assume my integration should use 10 as that seems to be the default for added functionality.
I also grasp the idea of deferring to higher priority plugins: if ( is_a($user, 'WP_User') ) { return $user; }
However, when it comes to authentication a chain of multiple authentication methods which can say yea/nay to a username/password combination is inherently insecure. For instance, if my plugin attempts to log the user in with LDAP and fails, it should fail permanently, not give the same credentials a shot at the local database.
This widens the effective attack target and essentially creates two passwords (or more) that can access one username's account.
In my current architecture I have handled this by rewriting wp_authenticate as I see fit. I allow users to specify a login mode that either permits failed logins to hit the wp system for another try or fails permanently.
However, using filters it seems that it will simply keep moving down the chain regardless of what I do. Is this true? Am I missing something obvious?
Thanks in advance, Clif
@Stephen Paul Weber
Totally agree!
We recently had to relaunche 2 projects (Tourist.de / linksilo.de ) due to massiv Problems with WP Core.
Keep the Core smart! (+:
I think Oauth is not necessary anymore, because Facebook Connect is coming more and more. So do i use Facebook connect on my blog at: http://www.androidspiele.net
What do you think?
I know I am too late on this, but do you have a working version of the oAuth authentication for WP as posted in this http://www.slideshare.net/willnorris/wordpress-oauth-presentation
I am working on something related and wanted oAuth, any help will be appreciated. Since you do not have a subscribe to comments here I would appreciate a reply by email.
I have seen some plugins us a filter on init() to force authentication. I am trying to force users to have to had authenticate as well as the authentication coming from my app.
Seem to be running into issues between filter on init and authenticate mostly I think because I don't understand what the functions should do wordpress wise with setting cookies for rest to work.
Any ideas?