WordPress Authentication (over) Concerns: A Quick Case Study

A big push back against the WordPress REST API has been a feeling that a lack of authentication system makes these endpoints not useful. This is strange to me, since the content endpoints use the exact same permissions system as the rest of WordPress.

I think a lot of this confusion comes from the excitement about building cool apps that connect from outside of WordPress via the REST API. In those cases, WordPress’ cookie-based authentication does not work. Therefore a different solution is needed. oAuth1, oAuth2, JWT, a custom system, etc.

I like JWT a lot by the way in those scenarios. This plugin makes it very easy.

But what about when we are using the REST API to improve WordPress from inside of a WordPress theme or plugin? Or what if — presuming the content endpoints make it into WordPress 4.7?

In those cases, cookie-based authentication, which is super easy to use, is all we need. This is especially exciting for core. I’d love to see the REST API used for content editing in the front-end and/ or the customizer. Also, what about new ways we could work with terms or custom fields in the post editor, or combine editors for multiple content types, or build dynamic list tables?

Today I made up a quick prototype of a front-end revisions browser for posts. It took me about an hour, and most of that time was spent figuring out how revision endpoints worked — I had never used them before — and working through a bug in the REST API that was quickly resolved.

I’d like to briefly show you how it works, and discuss using cookie authentication, as well as why that was the best choice.

If you want, you can check out the code here. Also, if you want to learn more about authentication using the REST API, I have a section of my REST API course that covers this topic.

Photo by: Philipp Reiner

Why Use Cookie Authentication?

The first question is why use cookie-based authentication for this type of problem. The short answer is “why not?” I mean, why does having a REST API in WordPress mean that we have to suddenly start using a second authentication system? We don’t do that with admin-ajax?

The REST API does force us to use a nonce, in a way that admin-ajax does not, which complicates this by 2-5 lines of code. If only admin-ajax had been designed the same way, we probably could have skipped a few thousand XSS and CSFR vulnerabilities in plugins and themes.

How Does Cookie Authentication Work?

Cookie authentication works exactly the same way as it does in any other WordPress request, which is to say pretty much automatically, if we send the right nonce. That nonce, which must be created using the wp_rest action, can be sent as a header or as part of the query in a GET request or the body of a post request.

In the plugin I built as a proof of concept for the revision browser, I localized the URL for the current post’s revisions endpoint, and included the _wpnonce query argument, like this:

	$api = add_query_arg( array(
		'_wpnonce' => wp_create_nonce( 'wp_rest' ),
		'context' => 'view'
	), rest_url( sprintf( 'wp/v2/posts/%d/revisions', $post->ID ) )  );
	wp_localize_script( 'revision-browser', 'REVBROWSER', [
		'api' => esc_url( $api ),
		'nonce' => wp_create_nonce( 'wp_rest' ),
		'content' => $selectors[ 'content' ],
		'title' => $selectors[ 'title' ],
		'links' => $links
	]);

That’s pretty simple. Another approach would have been to put nonce as a key in the REVBROWSER object I was localizing. Then I would have had to add a header to my request. To make that work I would change my AJAX request from this:

$.get( REVBROWSER.api ).success( function( r ){ ...

To something like this:

$.ajax( {
   url: REVBROWSER.api,
   method: 'GET',
   beforeSend: function ( xhr ) {
      xhr.setRequestHeader( 'X-WP-Nonce', REVBROWSER.nonce );
   },

} ).success( function( r ){ ...

Either way pretty simple and works perfectly with WordPress as-is. No one who isn’t authorized to view revisions can see these revisions.

What About admin-ajax?

I could do that with admin-ajax (image meme)This is the point where someone mentions that I could have used admin-ajax. That’s totally correct, but let’s talk about what I didn’t do when I built this, but would have had to with admin-ajax.

I didn’t write any new code to collect revisions and convert them to JSON. The content REST API does that for me. This kind of efficiency is why we use open source software.

I also didn’t worry about authentication, authorization or cross-site forgery. That’s handled by the core API for me. I could do all of that myself, and hopefully not mess it up. But I didn’t have to. The next person who does this might be not as picky about security issues as I am. When we off-load those concerns to core, we get help from all of the other people working on the project. This is why we use open source software.

And it’s important to say again that not everyone exposing private content or allowing updating of content via admin-ajax has added the right permissions and nonce checks. Our ecosystem has been harmed by the fallout of those mistakes. The WordPress REST API will not make developer-errors that cause security vulnerabilities go away, but it will help.

By the way I did this once before for Lasso using the custom API I built for that plugin. It took Nick and I a long time. What I built today, needs some UI work, but could, if there is a content API in WordPress core, be polished patch for WordPress core without much more work. I’d love to do that if possible.

Being Loved Isn’t Enough

My dog Josie begging in my kitchen.
This is my dog Josie.

One argument for the REST API’s inclusion in core is that so many people are excited about it. I think that is a decent argument. We need people to be excited and passionate about building cool stuff with WordPress.

But that’s not enough. Having a standard way of working with WordPress content via a REST API is about creating new tools for end-users while following a standard and working together on improving the tool we all love. That’s why we use open-source.

The Core Team Wants To Know What You Think

Earlier today, Helen Hou-Sandi, a lead developer of WordPress and the release lead for 4.7 asked a question on Twitter that led to a bit of a discussion and then this proof of concept.  There has been several posts on the WordPress core development blog asking for comments on the REST API merge proposal and invitations to discuss further in Slack.

That’s awesome. This is a big decisive for a big project and I love that those making the decisions are so open to a public discussion.

Get involved y’all.

 

One Reply to “WordPress Authentication (over) Concerns: A Quick Case Study”

  1. About “What About admin-ajax?”… I don’t think anyone has done a good job explaining what the advantages are of using the REST API in cases where you might have used ajax and I don’t think you’ve put a strong case for it here either, which is strange!

    Someone who writes insecure ajax logic is just as easily going to write poor REST logic. You mention not having to worry about authentication, authorization or cross-site forgery, but that’s not a good thing. Case in point, you can run into scenarios where you think a request is properly authenticated when it’s not. The fact that the REST classes handle so much is both a good thing and a bad thing, because unless you know what’s going on internally, you don’t actually know if you have the luxury of not worrying about the things you mentioned.

    AJAX is not hard to write either, in fact I think there’s more gotchas with the REST API. Using the REST API over ajax does give you perks: more structure, handy methods, better performance in most cases, discoverability, and the benefit of having a common standard for other application code to work with. But I’m still finding cases where ajax logic is quicker and simpler to implement. I just don’t agree particularly with the ‘you don’t have to think about x, y, z’ when x, y, z should be front of mind when writing any bit of code.

Leave a Reply

Your email address will not be published. Required fields are marked *