Proposal - Securing the WordPress JSON API

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:

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.

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

Basic Auth

Easy to implement

Insecure

If API login is compromised then user may loose their account or be made to go through the hassle of regaining access.

Since APIs access can be automated it’s much more likely that a hacker could capture a username/password on a non-SSL API call (calls might be made continuously) than for a user login (which comparatively happen very infrequently.)

Can only support one Authorization profile per user account.

To support multiple authorization profiles a user would need create multiple user accounts,

To allow another person API access they either need to share their username/password or create another user account for them.

If API access requires a user account some sites could go from 5-10 users to having 50,000+ users (think of smaller sites like Mashable.)

If multiple user accounts are required then we’ll need a way to relate user accounts and allow one user account to manage other user accounts.

API Keys

API authorization is decoupled from user accounts.

One or many API keys can be tied to a single user account.

If API Key is compromised user can login and deactivate it.

Plugins could easily deactivate API keys if they follow an abuse pattern.

API keys could be added with expiration dates.

Sites with a large number of API users do not gain an explosion of regular users.

Each API Key can potentially support a different Authorization Profile (example use-case: I provide on API key to a social network – the key has limited capability – and use another API key – one that can do anything my user account can do – for an official WordPress mobile app that I use to access my site.)

Requires what appears to be more architecture

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.