I was recently added to the WordPress API team and this post contains my thoughts about the recent authentication discussion.
WordPress have a reasonably robust authentication system built in, the username and password system and it would be possible to use it along with Basic Auth to allow for API authentication. Please forgive any typos in advance; this was long and I didn’t really have the time to fully proof it.
Authentication, Identity and Authorization
While Authentication is very important there is also Authorization to consider. Here’s a nice blog post from Apigee on the difference between three (3) terms: Identity, Authentication and Authorization (IMO Apigee are the leading experts on web API design at the moment). In a nutshell here’s what they terms mean:
The Term | What it Means |
---|---|
Identity | Who is making the request? |
Authentication | Are they really who they say they are? |
Authorization | Are they allowed to do what they are trying to do? |
And as they point out we may not need them all but what we need is the point of this post.
As a side note they say "Take Twitter’s API; open for looking up public information about a user, but other operations require authentication." What this says to me is an API key would be ideal for most read activities but most write activities should require Authentication.
Authorization without Authentication
As much as we need Authentication I think we need Authorization even more. There are some API actions we’ll happily allow anyone to do such as download the list of our most popular posts and we don’t need to authenticate for that, we only need to authorize.
Why authorize? Why not just allow open access? So we can track who we authorized in case, for example we need to rate-limit their usage or even revoke their access.
About SSL
Let me get this out of the way sooner than later. Anything that requires SSL is a non-starter just as requiring PHP 5.3 for WordPress 3.7 is a non-starter. Need I say more on this point?
However we could allow support for SSL, assuming that for what we implement the SSL and non-SSL solutions are compatible.
Mainstream Options for API Security
Let’s discus the variety of methods for securing an API; some mainstream and some a bit esoteric. Bottom line is that most informed people seem to say "Don’t role your own." So with that in mind I believe we have these options:
Option | Discussion |
---|---|
Generally considered the best balanced security option for mainstream web apps where security and ease of interaction for users is balanced. But can be complex to implement, especially on the client end, and requires SSL to be secure. | |
Not as good as OAuth 2 but super easy for the client to implement OTOH it is not secure unless SSL is used. | |
More secure than Basic Auth but still not fully secure. Quite a pain for the client to implement.. | |
Well-tested and doesn’t require SSL but is non-standard (ignoring "defacto-" standards) and still requires an API key. | |
Very simple for the client to implement and as secure the Capabilities tied to the API key, i.e. if it can only see public data and not update then it’s "secure enough". Fully secure if used with SSL. Assuming users can’t change passwords with the API key then it’s more secure than Basic Auth because user credential are never in a position to be compromised. |
(Did I miss anything?)
Given the available options it would seem to me that OAuth 2, Digest Auth and even Amazon Auth are non-starters as a requirement for use of a JSON API in WordPress core because of the complexity each of them heave onto the API client developer, at least if one of these is the option for accessing the JSON API.
Basic Auth vs. API Keys
Which leaves the unsecure Basic Auth and mildly secure API Keys. So review the pros and cons of using Basic Auth – which is tied to the WordPress user’s username and password in the current version of the JSON API – and API keys:
Pros | Cons | |
---|---|---|
|
|
|
API Keys |
|
|
It seems to me from this comparison that API keys are the only reasonable option for allowing JSON API access to much of WordPress. However they are only appropriate for some use-cases and not even as-is they are not as a complete solution. Let’s discuss the rest of the solution for the use-cases in which I think they apply.
It also seems to me that tying API access to users accounts could easily create an explosion of complexity and significant user experience problems as users see their logins hacked by unsecure usage and then are locked out of or even loose their blogs.
API Roles and Capabilities
One of the ways in which API Keys might be acceptable without Authentication is that some things can be made freely available holders of API keys if we add in "API Roles and Capabilities."
Just like User Roles that are assigned a collection of Capabilities we could add "API Roles" that also have "API Capabilities". These Capabilities could be used to determine the Authorization status for each (what I’ll name) an "API Service" when requested.
Note: I’m defining an "API Service" as a URL + an HTTP method (GET, POST, etc.) and I’m calling the collection of Authorizations for all API Services as a "Authorization Profile."
I’ve reviewed the code for the WP_Role
, WP_Roles
and WP_User
classes and I think the first two could be used without modification. If so then we only introduce a WP_API_Request
class. And depending on the opinion of others the WP_API_Request
class could be standalone or the WP_User
class could be refactored to extend from an abstract WP_Auth
class thereby allowing the new WP_API_Request
class to also extend from WP_Auth
.
We could then decide on a convention that any Capability name prefixed with 'api_'
is a capability for an API Service and we add a function current_api_request_can()
or just api_request_can()
. Armed with api_request_can()
we could write code like the following (note that api_request_can()
assumes 'api_'
as a prefix and thus does not require it to be passed):
Are We Adding Too Much Code?
Although a comment was made that "we don’t want a huge chunk of code just for authentication" I would suggest that even if it were to be a large amount of code, which I doubt there would be, it shouldn’t matter how much code we add as long as that code doesn’t require significant maintenance and more importantly does not impose significant complexity onto the admin user in terms of "more options."
-
Assume that in Settings > General we add only one (1) single checkbox with the label "Enable JSON API" which by default we leave unchecked.
-
Once the user has explicitly chosen to enable the API (the equivalent of activating the plugin we have today) a single "Tools > JSON API" option is added.
-
The Tools/JSON API admin page can use tabs to organize the information so it would not be overwhelming, if even needed.
-
To offer the user the list of API keys we can reuse/modify the Taxonomy add/edit functionality assuming we add a
'user_api_key'
taxonomy to allow us to store, lookup and manage API keys related to Users who would "own" the API keys. -
Another tab for the Tools/JSON API admin page could potentially offer the ability to add and manage API Roles and another tab for API Capabilities. Or not, we could require these be managed programmatically just like User roles currently are.
-
And finally a main tab that allows you to force SSL use, or not.
What I’ve describe above it really not that much code. Would it make sense to risk the potential downside of tying the API to username and password in order to simply avoid the code that the API keys management would require?
Handling Escalating Security Requirements
Consider the "API Services" discussed earlier; we could implement a mapping of authentication requirements to API services such that different services have different authentication/authorization requirements. Consider this table:
Requirement | HTTP Methods | API Services That Allows | Example API Service |
---|---|---|---|
No API Key Required | GET | Access to public information with a low risk of needing a rate limiter. | An API service that returns site name and other metadata. The metadata could also including a links to an API service to request an API key via API. |
API Key | GET | Access to public information that might need to be rate limited. | Return the current list of blog posts. |
API Key + Nonce | POST, PUT | Add Content or Update Revertible Content | Update of Posts, add Taxonomy Terms. |
Nonce | GET | Add Content or Update Revertible Content | Update of Posts, add Taxonomy Terms. |
SSL+Basic Auth | GET | Returns secure information for client w/o API Key | Retrieve an API key programatically. |
SSL+API Key | POST, PUT | Updates secure information | Modify User Profile, Deletes Posts. |
SSL+Basic Auth | POST, PUT | Update highly sensitive information | Change user password |
API Keys + Nonces
Note that we combine nonces with API keys. One of the ways WordPress handles security is with nonces, and the API need be no different. Note that the nonce would be generated by WordPress core or a plugin for the logged in user to allow their browser’s to use the API via AJAX. These use-cases would authorize for the JSON API similar to how the current AJAX system in WordPress authorizes.
For mobile apps nonces could also be offered to last for longer, requiring a mobile device to retrieve a new nonce once every 15 minutes or so but then allowing them to just use the nonce + API key within those windows. Of course you wouldn’t want a 15 minute window for nonces used with AJAX apps
Using SSL
So if we follow the outlined approach we can provide a reasonably level of API access without requiring SSL but we can still enforce the benefit of SSL for those who are likely to have the where-with-all to upgrade to SSL.
Consider this, if they need their sensitive parts of their site updated via API then they are likely special enough that they can make sure that SSL happens. But if unexpected consequences occur and someone builds a SaaS that people want to use but that requires SSL then frankly it creates an opportunity for hosting companies to see a high level of demand for turnkey SSL setup.
And optionally we can add an 'WPAPI_ALLOW_NO_SSL'
constant for those site builders and site owners with a "Devil May Care" attitude.
Summary
In summary I’m proposing for the JSON API for WordPress to:
- Use API Keys for Authorization
- (And if you are still not convinced, read this).
- Incorporate API Roles and Capabilities
- Support Escalating Authentication Requirements for API Services
- Build Single Menu Item Admin UI for the admin to Manage the API.
Let me know your reactions in the comments below.
2 Replies to “Proposal – Securing the WordPress JSON API”