Skip to content

Instantly share code, notes, and snippets.

@kosso
Last active September 16, 2021 05:30
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save kosso/71c957e30a40116e5f98 to your computer and use it in GitHub Desktop.
Save kosso/71c957e30a40116e5f98 to your computer and use it in GitHub Desktop.
Test PHP client for Wordpress REST API v2.0 / OAuth1.0a 0.3.0
<?php
//opcache_reset(); // Disable local dev MAMP cache
/*
WP_API_OAuth_Test_client.php
Tested with Wordpress 4.7.1
WordPress REST API - OAuth 1.0a Server v.0.3.0 - https://en-gb.wordpress.org/plugins/rest-api-oauth1/
Description : Simple PHP test to obtain access_token and access_token_secret (Access tokens) from a Wordpress with WP-API and WP-OAuth plugins (and WP-CLI for OAuth consumer/app creation) installed.
Author : @kosso
Date : Nov 07, 2014
Updated : Nov 22, 2015
: Jan 16, 2016
: Mar 06, 2016
: Oct 11, 2016
: Jan 23, 2017
*/
/*
Instructions : After installing the WordPress REST API - OAuth 1.0a Server plugin, you can now create a new client application on wp-admin
; Go to Dashboard > Users > Applications.
: Set one up, then use the key/secret pair in the settings below.
: Use the url of *this* PHP test script for the callback url.
.. Now edit the config vars below with the server and client info.
*/
// Edit the config to your requirements.
$wp_site_url = 'http://yourWPdomain.com';
$wp_api_path = '/wp-json/wp/v2';
$oauth_config = array(
'key' => '#_YOUR_APP_CLIENT_KEY_#',
'secret' => '#_YOUR_APP_CLIENT_SECRET#',
'wp_api_domain' => $wp_site_url,
'wp_api_path' => $wp_api_path,
'uri_request' => $wp_site_url.'/oauth1/request',
'uri_authorize' => $wp_site_url.'/oauth1/authorize',
'uri_access' => $wp_site_url.'/oauth1/access',
'uri_user' => $wp_site_url.$wp_api_path.'/users/me?context=edit', // 'embed' context excludes roles and capabilities, so use 'edit' to determine if publishing and uploads are allowed.
'oauth_callback' => 'http://yourREMOTE.client.domain.com/WP_API_OAuth_Test_client.php' // The url where you will run this test. Point to THIS php file.
);
// Clear the cookies to log out.
if($_GET['logout']==1){
setcookie("access_token", $access_token, time() - 1, "/" );
setcookie("access_token_secret", $access_token_secret, time() - 1, "/" );
setcookie("user_object", json_encode($user_object), time() - 1, "/" );
setcookie("oauth_token_secret", "", time() - 1, "/" );
header('Location: '.$_SERVER['PHP_SELF']);
}
// OK.. Here we go...
$auth = new OAuthWP($oauth_config);
// Pick up url query params after the oauth_callback after request token generation. (Also added check to make sure we're coming back from the OAuth server host)
//if(isset( $_REQUEST['oauth_token'] ) && isset( $_REQUEST['oauth_verifier'] ) && parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST) == parse_url($oauth_config['uri_request'], PHP_URL_HOST) ){
if(isset( $_REQUEST['oauth_token'] ) && isset( $_REQUEST['oauth_verifier'] )) {
// Back from Authorisation. Now Generate Access Tokens for this user
// Generate access tokens param string
// Add the required 'oauth_verifier' parameter
$request_data = array(
'oauth_verifier' => $_REQUEST['oauth_verifier']
);
$temp_secret = $_COOKIE['oauth_token_secret']; // from /request token leg
$access_token_string = $auth->oauthRequest($oauth_config['uri_access'],'POST', $_REQUEST['oauth_token'], $temp_secret, $request_data); // no token secret yet...
parse_str($access_token_string, $access_tokens);
if(!isset($access_tokens['oauth_token'])){
echo '<h3>ERROR: Failed to get access tokens</h3>';
print_r($access_tokens);
echo '<hr>';
print_r($access_token_string);
exit;
}
$access_token = $access_tokens['oauth_token'];
$access_token_secret = $access_tokens['oauth_token_secret'];
// Verify user by getting currently looged in user daya from /wp-json/users/me
$user_object = json_decode($auth->oauthRequest($oauth_config['uri_user'],'GET', $access_token, $access_token_secret));
// Store information in a cookie for when the page is reloaded
setcookie("access_token", $access_token, time() + (3600 * 72), "/" ); // expire in 72 hours...
setcookie("access_token_secret", $access_token_secret, time() + (3600 * 72), "/" ); // expire in 72 hours...
setcookie("user_object", json_encode($user_object), time() + (3600 * 72), "/" ); // expire in 72 hours...
// Clear the temp cookie
setcookie("oauth_token_secret", "", time() - 1, "/" );
// Reload the page
header('Location: '.$_SERVER['PHP_SELF']);
exit;
}
if(isset($_COOKIE['access_token']) && isset($_COOKIE['access_token_secret']) && isset($_COOKIE['user_object'])){
// LOGGED-IN : Visitor already appears to have the required cookies set.
$u = json_decode($_COOKIE['user_object']);
$av = $u->avatar_urls;
foreach ($av as $key => $value) {
if($key == 48){ // medium thumbnail
$av = $value;
}
}
echo '<h3><img style="margin:6px;width:50px;height:50px;float:left;vertical-align:middle;" src="'.$av.'"> logged in as: '.$u->name.'</h3>';
echo '<br clear="all"><h4><a href="?logout=1">CLICK HERE TO LOG OUT</a></h4>';
echo '<hr>';
echo 'uncomment code below to try some tests.';
// TESTS /////////////////////////////////////////////
//echo $oauth_config['uri_user'];
// GET CURRENT USER DATA
// Docs : http://wp-api.org/#users_retrieve-current-user
echo '<h3>TEST: GET : '.$oauth_config['uri_user'].' to verify current user</h3>';
$current_user_object = json_decode($auth->oauthRequest($oauth_config['uri_user'],'GET', $_COOKIE['access_token'], $_COOKIE['access_token_secret']));
echo '<h4>RESPONSE:</h4>';
echo '<pre>';
print_r($current_user_object);
echo '</pre>';
// exit;
/*
// TEST : CREATE A NEW POST
// Docs : http://wp-api.org/#posts_create-a-post
echo '<hr><h3>test : Creating new post</h3>';
$post_data = array(
'status' => 'draft',
'title' => 'Heyyyyy Another test at '.date('H:i'),
'content' => 'The quick brown fox jumped over the lazy dogs.'
);
$post_object = $auth->oauthRequest($oauth_config['wp_api_domain'].$oauth_config['wp_api_path'].'/posts','POST', $_COOKIE['access_token'], $_COOKIE['access_token_secret'], $post_data);
echo '<h4>RESPONSE:</h4>';
echo '<pre>';
print_r($post_object);
echo '</pre>';
*/
// Uncomment the sections below to test some other things..
/*
// CREATE NEW ATTACHMENT (Media upload)
// Docs : http://wp-api.org/#media_create-an-attachment
echo '<hr><h3>Test : Create New Attachment (Media upload)</h3>';
$file_data = array(
'file' => '@./some_photo.jpg;type=image/jpeg'
//'file' => '@./some_video.m4v;type=video/m4v'
//'file' => '@./sometune.mp3;type=audio/mpeg'
);
$file_object = json_decode($auth->oauthRequest($oauth_config['wp_api_domain'].$oauth_config['wp_api_path'].'/media',
'POST',
$_COOKIE['access_token'],
$_COOKIE['access_token_secret'],
$file_data
)
);
echo '<h4>UPLOAD RESPONSE:</h4>';
echo '<pre>';
print_r($file_object);
echo '</pre>';
// Naturally, you'll need to do a file upload first, if you want the attachment to be 'attached' or embedded in a post.
/**/
/*
date
date_gmt
password
slug
status - One of: publish, future, draft, pending, private
title
content
author
excerpt
featured_media
comment_status - One of: open, closed
ping_status - One of: open, closed
format - One of: standard, aside, chat, gallery, link, image, quote, status, video, audio
sticky
categories
tags
*/
/*
// UPDATE A (previous) POST
echo '<hr><h3>Test : Updating Post</h3>';
$post_id = $post_object->id;
$update_data = array(
'title' => 'The title was edited immediately after creating it at '.time(),
'excerpt' => 'And this excerpt was added after.',
'categories' => [ 1, 2, 6 ]
);
$update_object = json_decode($auth->oauthRequest($oauth_config['wp_api_domain'].$oauth_config['wp_api_path'].'/posts/'.$post_id,
'POST',
$_COOKIE['access_token'],
$_COOKIE['access_token_secret'],
$update_data,
true // post as JSON
)
);
echo '<h4>UPDATE POST RESPONSE:</h4>';
echo '<pre>';
print_r($update_object);
echo '</pre>';
/**/
// that's it..
} else {
// Not logged in.
$request_token_string = $auth->oauthRequest($oauth_config['uri_request'],'POST', null, null);
parse_str($request_token_string, $request_parts);
// temporarily store the oauth_token_secret for the next step after the callback.
setcookie("oauth_token_secret", $request_parts['oauth_token_secret'], time() + 60, "/" );
// echo '<h4>request_token_string :'.$request_token_string.'</h4>';
// Start OAuth authorisation by obtaining a request token and generating a link to the OAuth server, with a callback here ...
echo '<h3><a href="'.$oauth_config['uri_authorize'].'?'.$request_token_string.'&oauth_callback='.urlencode($oauth_config['oauth_callback']).'">LOGIN USING YOUR '.$oauth_config['wp_api_domain'].' WORDPRESS ACCOUNT</a></h3>';
echo 'Uses WP-API and OAuth 1.0a Server for WordPress via https://github.com/WP-API';
}
// That's it!
// #########################################################################
// OAuth 1.0a library
// #########################################################################
// NB: All the echo and print_r must be commented out for setcookie to work when running logging in tests.
function logIt($text){
return;// comment to activate log
$debugLog = '/path/to/debug/log.txt';
$t = print_r($text, true);
$text = $t;
$handle = fopen($debugLog, "a+");
if (fwrite($handle, $text."\n") === FALSE) {
}
fclose($handle);
}
class OAuthWP
{
function OAuthWP($config)
{
$this->key = $config['key'];
$this->secret = $config['secret'];
$this->uri_request = $config['uri_request'];
$this->uri_authorize = $config['uri_authorize'];
$this->uri_access = $config['uri_access'];
$this->uri_user = $config['uri_user'];
}
function queryStringFromData($data, $queryParams = false, $prevKey = '')
{
if ($initial = (false === $queryParams)) {
$queryParams = array();
}
foreach ($data as $key => $value) {
if ($prevKey) {
$key = $prevKey.'['.$key.']'; // Handle multi-dimensional array
}
$queryParams[] = $this->_urlencode_rfc3986($key.'='.$value); // join with equals sign
}
if ($initial) {
return implode('%26', $queryParams); // join with ampersand
}
return $queryParams;
}
function oauthRequest($url, $method, $oauth_access_token, $oauth_access_token_secret, $post_params=null, $post_json=false){
$params = array(
"oauth_version" => "1.0",
"oauth_nonce" => md5(time().rand()),
"oauth_timestamp" => time(),
"oauth_consumer_key" => $this->key,
"oauth_signature_method" => "HMAC-SHA1",
"oauth_token" => $oauth_access_token
);
// Filter out empty params.
$params = array_filter($params);
// ## BUILD OAUTH SIGNATURE
// Add extra params if present and not JSON
if($post_params!=null && $post_json === false ){
foreach ($post_params as $k => $v){
if(is_array($v)){
$iii = 0;
logIt('***** ARRAY ');
foreach ($v as $kk => $vv){
$params[$k][$iii] = $vv;
$iii++;
}
} else {
$params[$k] = $v;
}
}
// Remove 'file' param from signature base string. Since the server will have nothing to compare it to. Also potentially exposes paths.
unset($params['file']);
ksort($params);
}
// Deal query with any query params in the request_uri
$request_query = parse_url($url, PHP_URL_QUERY);
$request_uri_parts = parse_url($url);
$request_base_uri = $request_uri_parts['scheme'].'://'.$request_uri_parts['host'].$request_uri_parts['path'];
$joiner = '?'; // used for final url concatenation down below
if(!empty($request_query)){
$joiner = '&';
parse_str($request_query, $query_params);
$params = array_merge($query_params, $params);
ksort($params);
}
// Encode params keys, values, join and then sort.
$keys = $this->_urlencode_rfc3986(array_keys($params));
$values = $this->_urlencode_rfc3986(array_values($params));
$params = array_combine($keys, $values);
ksort($params);
// Convert params to string
foreach ($params as $k => $v) {
$pairs[] = $this->_urlencode_rfc3986($k).'='.$this->_urlencode_rfc3986($v);
}
$concatenatedParams = implode('&', $pairs);
$concatenatedParams = str_replace('=', '%3D', $concatenatedParams);
$concatenatedParams = str_replace('&', '%26', $concatenatedParams);
// Form base string (first key)
// echo '<h4>concatenated params</h4><pre>'.$concatenatedParams.'</pre>';
// base string should never use the '?' even if it has one in a GET query
// See : https://developers.google.com/accounts/docs/OAuth_ref#SigningOAuth
$baseString= $method."&".urlencode($request_base_uri)."&".$concatenatedParams;
// Form secret (second key)
$secret = urlencode($this->secret)."&".$oauth_access_token_secret; // concatentate the oauth_token_secret (null when doing initial '1st leg' request token)
// Make signature and append to params
logIt('base : '.$baseString);
logIt('signature key : '.$secret);
$params['oauth_signature'] = rawurlencode(base64_encode(hash_hmac('sha1', $baseString, $secret, TRUE)));
// Re-sort params
ksort($params);
// Remove any added GET query parameters from the params to rebuild the string without duplication ..
if(isset($query_params)){
foreach ($query_params as $key => $value) {
if(isset($params[$key])){
unset($params[$key]);
}
}
ksort($params);
}
// Remove any POST params so they get sent as POST data and not in the query string.
if($post_params!=null && $post_json === false ){
foreach ($post_params as $key => $value) {
if(isset($params[$key])){
unset($params[$key]);
}
}
ksort($params);
}
// Build OAuth Authorization header from oauth_* parameters only.
$post_headers = $this->buildAuthorizationHeader($params);
// Convert params to string
foreach ($params as $k => $v) {
$urlPairs[] = $k."=".$v;
}
$concatenatedUrlParams = implode('&', $urlPairs);
// The final url can use the ? query params....
$final_url = $url; // original url. OAuth data will be set in the Authorization Header of the request, regardless of _GET or _POST (or _FILE)
// Request using cURL
$json_response = $this->_http($final_url, $method, $post_params, $post_headers, $post_json);
// Result JSON
return $json_response;
}
// Send Authorised Request Using Curl ///////////////////////////
function _http($url, $method, $post_data = null, $oauth_headers = null, $post_json=false)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
if($method=='POST')
{
curl_setopt($ch, CURLOPT_POST, 1);
logIt('POST');
logIt($post_data);
if(isset($post_data['file'])){
// Media upload
$header[] = 'Content-Type: multipart/form-data';
if(isset($oauth_headers)){
array_push($header, $oauth_headers);
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
} else {
if(isset($oauth_headers)){
if($post_json===true){
$header[] = 'Content-Type: application/json';
array_push($header, $oauth_headers);
} else {
$header[] = $oauth_headers;
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
}
if($post_json===true){
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
} else {
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); // application/x-www-form-urlencoded
}
}
} else {
// Not being used yet.
if(isset($oauth_headers))
{
$header[] = $oauth_headers;
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
}
}
$response = curl_exec($ch);
$this->http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE); // 201 = 'Created'
$this->last_api_call = $url;
$this->response = $response;
// echo "<br>status: ".$this->http_status."<br>";
// logit('this');
// logIt($this);
curl_close($ch);
return $response;
}
function _urlencode_rfc3986($input)
{
if (is_array($input)) {
return array_map(array('OAuthWP', '_urlencode_rfc3986'), $input);
}
else if (is_scalar($input)) {
return str_replace('+',' ',str_replace('%7E', '~', rawurlencode($input)));
}
else{
return '';
}
}
private function buildAuthorizationHeader($oauth){
$r = 'Authorization: OAuth ';
$values = array();
foreach($oauth as $key => $value){
$values[] = $key . '="' . rawurlencode($value) . '"';
}
$r .= implode(', ', $values);
return $r;
}
}
?>
@Jany-M
Copy link

Jany-M commented Feb 8, 2017

Currently this line
$request_token_string = $auth->oauthRequest($oauth_config['uri_request'],'POST', null, null);
produces
http://www.site.com/oauth1/authorize?No%20OAuth%20parameters%20supplied&oauth_callback=http%3A%2F%2Fwww.site.com%2Ftest.php

See the No%20OAuth%20parameters%20supplied.

In that function oauthRequest
$json_response = $this->_http($final_url, $method, $post_params, $post_headers, $post_json);
returns
No OAuth parameters supplied.

In function _http
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
returns
No OAuth parameters supplied
and
echo "<br>status: ".$this->http_status."<br>";
returns
status: 400

Any idea?

@kosso
Copy link
Author

kosso commented Mar 12, 2017

Hmm.. I just tried it on a fresh Wordpress 4.7.3 on one domain, running this script from another domain and it all worked fine.

Do you have the application client id/secret and the callback url all set up correctly?

Which version of Wordpress and also PHP are you running this on?

@rizqyhi
Copy link

rizqyhi commented May 26, 2017

Hi, i tried to implement this oAuth too. All flows are working fine.
The only problem is when I tried to fetch current user data, it always returned me unauthorized response like:
{"code":"rest_not_logged_in","message":"You are not currently logged in.","data":{"status":401}}

Any thought?

@bojanseirovski
Copy link

Works for me on 4.8, awesome job and thank you.

@bojanseirovski
Copy link

I spoke too soon... When talking to a WP instance on a domain, I am getting the "No OAuth parameters supplied" message also, I tried different domains, different instances, works only if I run it on a local instance.

@bojanseirovski
Copy link

It turns out, my hosting company has a policy for Authorization headers.

Adding :
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1

at the end of the .httaccess file solves the problem.

@shadowprompt
Copy link

@bojanseirovski Thank you very much. It works now.

@shadowprompt
Copy link

When I am going to exchange the token, it returned 'ERROR: Failed to get access tokens '

@CBADesign
Copy link

Hello Kosso,

First of all thank you for this magnificant client example. Unfortunatly i stumbled across a problem with the Media Upload.

The error I'm getting is:
( [code] => json_oauth1_signature_mismatch [message] => OAuth signature does not match [data] => stdClass Object ( [status] => 401 )
I really would like this to work.. so i hope you can help me!

Thanks in advance!

@eatherpro
Copy link

Notice: Undefined index: logout in C:\xampp\htdocs\example\scripts\curl_post_request.php on line 86
This is showing when I'm running the script on localhost! How to solve that issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment