This is the second article in a three-part series about using the WP API to achieve something I'm calling "Remote Control WordPress", a lifestyle where you'd manage network settings on a "control" install, and have other "client" installs pull their settings from the control. The advantage of this is that you could then manage the settings for many WordPress installs all in one place. The first article laid out how to register network settings as a custom endpoint in the WP API, but stopped short of demonstrating how to grab those settings when they are protected by a permissions callback, which they should be. This article picks up that thread, demonstrating how to pass OAuth credentials to the WP API.
OAuth is tricky. In learning the process, I was initially in disbelief as to the quantity and complexity of the steps involved. If you're like me, reading the spec is a pretty uninspiring place to start. The OAuth1 plugin docs are excellent, but the details are hard to appreciate without having seen the whole process. I'm hoping to provide you with a working example that you can refactor or break as desired, allowing you to see the different steps come to life. Given that, you can go back and appreciate the finer points in the docs and spec.
What You'll Need for This Article
In the first article, I explained that we'd need two WordPress installs to pull this off:
- The control install
- A client install
So far, we've only made use of the control install, but in this article we'll use both.
We'll also need a handful of plugins, which I'll specify as they're needed.
Like all the articles in this series, I'm running the twentysixteen theme, though this shouldn't really matter.
Preparing the Control
In the first article, we installed the WP REST API V2 plugin on the control install. While that plugin is still necessary, check out
/wp-json, which returns metadata about the API. If you
oauth, you'll get no results.
OAuth? More like NO AUTH, amirite?
Ahem. Therefore, now would be a fine time to install and network activate the OAuth1 plugin on the control, and try that url again. Voila:
Perfect. The control can handle OAuth requests now.
One of the other things the OAuth1 plugin does is allow you to register applications, which is exactly what our client installs are. In wp-admin, check out
/wp-admin/users.php?page=rest-oauth1-apps and register your client install. The details that you fill in are not at all important - seriously, they can just be gibberish - but the resulting
consumer_key are critical. You'll need them in a moment.
If it feels like we're piling plugins on top of plugins, I feel your pain (I've written about this feeling elsewhere, if you're interested). Unfortunately, that's just going to get worse, so hold your nose and get ready for more dependencies!
Preparing the Client
Now we'll switch to the client install, which is the first time we've done so thus far in the series. My client install happens to be my local MAMP. Just a reminder, indeed the whole purpose of this article: In production, you might have many, many client installs. In real life I have like 30, but for this demo I'm just using my local MAMP.
Grab my CSS-Tricks WP API Client plugin from GitHub and network activate it. It ships with a shortcode for demonstrating oauth requests:
[css_tricks_wp_api_client meta_key='site_name']. Add that shortcode to the content of a page. In that example, I'm asking for the value of the setting whose meta_key is
site_name. You can supply any network option name there.
You can browse that shortcode on the front-end if you like, but don't get your hopes up:
This is the type of thing where you download a plugin from GitHub, but then you have to hardcode in some of your own values. Take note of the main plugin file, where you're prompted to fill in the blanks. The first is a url for our
network_settings endpoint, which we established in the first article in this series. The other four are for OAuth.
It's worth pausing to restate why we need OAuth at all. You may recall from the first article in this series, that this is due to the
permission_callback, which only allows requests from credentialed users. That's why we need to provide an OAuth header with our request, and we have that in place so that all of our network settings are not sitting out there for anyone to query.
Therefore, it's time to build some OAuth creds. If you're like me, this is the part you found bewildering; this is why you're here. I can't say it any plainer: Get ready to jump through a bunch of hoops in order to OAuth.
Providing an OAuth header is a complicated process, made worse by the fact that there are a number of ways to go about it.
- You could use WP-CLI. I haven't tried this tutorial myself, but it looks promising.
- You could write a bunch of code and make a really cool interface that works right in the web browser. That gist is what allowed me to get a grasp on what OAuth is all about.
- You could use an HTTP client like Postman. I was introduced to using Postman as a way to build OAuth creds via this great tutorial.
We're going to do number three (I'm mostly just re-hashing part of that tutorial) because it seems to be the simplest method, so download and install Postman on your machine. Once Postman is installed, we're going to make an HTTP request to the control install, as follows:
There are three parameters there you'll need to fill in. The rest are generated by Postman, which is in fact much of the value in using Postman for this exercise. The three values you need to worry about are:
- The URL. Visit
/wp-jsonon the control install and
ctrl+Ffor "request". You'll find a url similar to
http:\/\/example.com\/oauth1\/request. Copy, paste, unslash, good to go.
- The consumer key. You generated this on the control install earlier in the tutorial, at
/wp-admin/users.php?page=rest-oauth1-apps. In that UI, it's called the "client key", as opposed to the "consumer key", but those two terms are synonymous here.
- The consumer secret. Same as step two, only it's called a "client secret" in wp-admin, as opposed to a "consumer secret".
Postman will reply with what are called "temporary credentials", which you'll need to copy from the console. Mine look like this:
The "temporary credentials" are actually just a query string which you'll tack on to a special URL. Similar to before, browse
wp-json, but this time find the
authorize URL. You'll prepend this (and a question mark) to the query string from Postman, which should leave you with the following URL:
You'll never guess what we're going to do with this URL. Enter it in some weird HTTP app? Not at the moment. Pass it through some bizarre encryption function? No sir. Chant it in front of a mirror thirteen times? Not necessary for our use case. Just fire up a web browser and visit it. I think you'll be delighted:
If you're not currently logged in to the control install, you'll first be prompted to do so.
Once you click authorize, you'll get a token.
If you were browse your user profile in wp-admin on the control install, you'd now see a list of the apps you've approved, along with a UI for revoking them:
Take the token back to Postman and make the following request:
There are five parameters that you'll need to supply.
- The URL. At
wp-json, look for the
accessURL, and add the token we just got as a URL variable for
oauth_verifier. In other words, your URL would look something like this:
How am I arriving at
oauth_verifierfor the URL variable? I really have no idea. I lucked out and saw it in the Token Exchange section of the tutorial I mentioned earlier. That just seems to be the thing to do. Maybe there are multiple ways of achieving this step. I don't see anything about adding this value as a URL variable per se in the docs section on token exchange. However, I did find that it also seems to work fine if you pass the
bodyof your request. Whatever?
- The consumer key. You got this in wp-admin when you registered your app. It's the same value you used here in the previous Postman call.
- The consumer secret. Again, you got this in wp-admin when you registered your app. Like the consumer key, it's the same value you used here in the previous Postman call.
- The token. This is a temporary credential found in the response from the previous Postman call.
- The token secret. This is also a temporary credential in the response from the previous Postman call.
Postman will respond with your
oauth_token_secret. Congrats! We now have everything we need in order to make OAuth'd HTTP requests!
Back to the Client Install
In the previous section, we gathered a ton of different tokens, secrets, keys, all kinds of weird stuff like that. It's time to put those values to work. Crack open the main plugin file for my CSS-Tricks WP API Client plugin and hardcode as indicated in the source code.
A lot of work, yes, but we made it! Load the front end again and see the resulting JSON:
It worked! We have just retrieved a setting, in this case
site_name, from the control install.
Sure, it Works in Practice, but Does it Work in Theory?
Normally my learning style is to have some context and theory before diving into an example. OAuth isn't like that. It's just too weird. That's why I've walked through an example without providing any theory. When it comes to OAuth, the theory-to-practice ratio is way out of whack. I think most of us here can read an article in the codex and end up with a general idea of what we're doing and why. It doesn't work that way with OAuth. Not for me, at least.
But I'll tell you what I'll do. Allow me to map a few sections of my plugin code to their corresponding treatment in the OAuth1 docs, so you can use your own critical thinking when implementing OAuth.
Crack open my OAuth class and give it a read. It's heavily commented. The class expects one argument, the
meta_key for the option we're querying for, and reveals one public function,
get_response(), for making OAuth'd HTTP requests. The constructor calls a whole bevy of functions in order to convert your secrets and tokens into an auth header, which I'll dig into now.
The Signature Key
The docs instruct us to build a signature key by taking the consumer secret and the token secret, URL-encoding each, then concatenating them with
& into a string. That's exactly what my function does. This value will be used later to create the OAuth signature itself.
The Headers (The First Time)
We can set most of the OAuth headers at this point, and in fact we'll need to in order to perform subsequent steps. Here's my code building the headers. The astute reader will note that this function gets called again later, once we have the signature. I'll revisit this in a moment. For now though, we're ready to build the base string.
The Base String
The base string is a concatenation of every part of the request, including the HTTP method, all of the OAuth headers other than the signature, and any variables we're passing along. Here's my code doing so. Like the signature key, we'll use the base string in a moment to create the signature.
We create the signature by combining and hashing the base string and the signature key. My code also runs it through
base64_encode(), which is required, but PHP's hashing function does not do so automatically.
The Headers (Again)
Now that we have the signature, we can add it to our OAuth headers. The headers are then concatenated in a very specific way. Finally, gloriously, they're ready to be used in our get_response() function. The shortcode calls this function when phoning the control install.
Some Lingering Concerns, Perhaps?
I gave you a working example, and I showed you how I arrived at this example, but I feel bad. I really didn't explain what most of this crap is. Tokens, signatures, keys - honestly I can barely keep track of it all. If you really feel the need to know how this works, I'm not going to be able to do a better job of explaining it than the spec does.
I've lost interest in my own tutorial at this point! It's ... um ... beyond the scope of this tutorial. Good luck to you.
In the first article, we added network settings to the WP API. In this article, we demonstrated how to retrieve them. In the third and final article, we'll explore my abstraction layer so we're not repeating this whole process in all of our feature plugins, and we'll write a small feature plugin as an example of how to use that abstraction.