OAuth extension to CakePHP HttpSocket
An extension to CakePHP’s core HttpSocket class that supports OAuth requests in the same way HttpSocket supports Basic Auth. Provides a simple API with low level access and high flexibility. Usage instructions included with example for Twitter.
(First, apologies for not yet blogging about the filter or export plugins I released recently, they’re quite complex, as I’m sure you’ll agree if you’ve checked them out, but I will get round to it soon. Sorry.)
So, on to business. I’m working on a few projects at work at the moment that integrate with Twitter. According to the API, they’re going to stop supporting Basic Auth for the requests to the API that require authentication, e.g. creating a tweet. So, that leaves OAuth.
OAuth is a great concept. In summary, it allows users of a service (e.g. Twitter) to authorise other parties (your application) access to their accounts on that service, without sharing their password with the other parties. In reality, it means a little bit of handshaking between the third party and the service provider to get various string tokens and redirecting the user to the service in order for them to authorise that third party to access their account. So the user only signs in to the service, not the 3rd party.
I’ve already mentioned Twitter as one service that supports OAuth, but there are bazillions more and that number will keep growing, so OAuth is here to stay.
When I first started looking into it, I checked out various PHP and CakePHP specific implementations and examples. Most used the PHP OAuth library which is extensive but overkill for what I wanted. So, looking into OAuth a bit deeper, I realised the only complex/special thing about it is a string called the oauth_signature in the Authorization header on an http request. These other libraries and examples seemed way too bloated just to handle that!
CakePHP core’s excellent HttpSocket class was the perfect base on which to add this functionality. After a little playing around with it in my app, I settled on a vendor class that extends the HttpSocket class and overrides the HttpSocket::request() method, adding the Authorization header string (including signature) required by OAuth, to the $request param you pass in, before then passing the modified $request param back up to HttpSocket::request(). Simples! (Note, it currently only supports $request param supplied as an array, see below for examples).
You can grab the code from my github account. It’s MIT licensed, so enjoy. I also think that this functionality should be available in the core, in the same way HttpSocket supports Basic Auth, so I relinquish any right over it whatsoever, core devs, feel free to grab all or part of it if you want. It should be PHP4 compatible, but may require a bit of extending to cope with string uri’s etc.
Usage instructions (we’ll take twitter as an example):
- Grab the code from my github account and add it to app/vendors/http_socket_oauth.php
- Register your application with Twitter (See below if you are developing locally and twitter grumbles about your call back url containing ‘localhost’)
- Note the consumer key and secret (I add them to Configure in bootstrap)
- Add the following to a controller:
public function twitter_connect() {
// Get a request token from twitter
App::import('Vendor', 'HttpSocketOauth');
$Http = new HttpSocketOauth();
$request = array(
'uri' => array(
'host' => 'api.twitter.com',
'path' => '/oauth/request_token',
),
'method' => 'GET',
'auth' => array(
'method' => 'OAuth',
'oauth_callback' => '',
'oauth_consumer_key' => Configure::read('Twitter.consumer_key'),
'oauth_consumer_secret' => Configure::read('Twitter.consumer_secret'),
),
);
$response = $Http->request($request);
// Redirect user to twitter to authorize my application
parse_str($response, $response);
$this->redirect('http://api.twitter.com/oauth/authorize?oauth_token=' . $response['oauth_token']);
}
… replacing <enter your callback url here> with your callback url, i.e. the URL of the page in your application that twitter will redirect the user back to, after they have authorised your application to access their account. In this example, it’s the url of the action in the next step. (Note, when you register your app, if you are developing locally, and you tried to enter your callback url with localhost in it, twitter might grumble. A little gem I read somewhere said you can actually create a bit.ly link, add your local callback URL in there, and then add the bit.ly link as the call back url in the twitter application settings. I still add my localhost url in this place though).This action fetches a request token from twitter, which it then adds as a query string param to the authorize URL on twitter.com that the user is redirected to. This is the page that prompts them to authorise your app.
- Next add the action for the call back:
public function twitter_callback() {
App::import('Vendor', 'HttpSocketOauth');
$Http = new HttpSocketOauth();
// Issue request for access token
$request = array(
'uri' => array(
'host' => 'api.twitter.com',
'path' => '/oauth/access_token',
),
'method' => 'POST',
'auth' => array(
'method' => 'OAuth',
'oauth_consumer_key' => Configure::read('Twitter.consumer_key'),
'oauth_consumer_secret' => Configure::read('Twitter.consumer_secret'),
'oauth_token' => $this->params['url']['oauth_token'],
'oauth_verifier' => $this->params['url']['oauth_verifier'],
),
);
$response = $Http->request($request);
parse_str($response, $response);
// Save data in $response to database or session as it contains the access token and access token secret that you'll need later to interact with the twitter API
$this->Session->write('Twitter', $response);
}
After the user authorises your app, twitter redirects them back to this action, the callback you specified in the previous request. In the querystring are 2 params called ‘oauth_token’ and ‘oauth_verifier’. These, and the consumer key and secret are then sent back to twitter, this time requesting an access token.At the end of this action, $response is an associative array with keys for: ‘oauth_token’, ‘oauth_token_secret’, ‘user_id’, ‘screen_name’. You should save ‘oauth_token’ and ‘oauth_token_secret’ to the session or the database as you need them when you want to access the Twitter API. Then redirect the user to another action, or display a thanks message or tweet to their account or whatever.
Now if you link to the twitter_connect() action or hit it in your browser address bar, you should be directed off to twitter to authorise your application, and once done, be back within your app with someone’s twitter accounts access tokens.
Finally, I guess it’s useful to know how to do something with the twitter API with this new found power:
App::import('Vendor', 'HttpSocketOauth');
$Http = new HttpSocketOauth();
// Tweet "Hello world!" to the twitter account we connected earlier
$request = array(
'method' => 'POST',
'uri' => array(
'host' => 'api.twitter.com',
'path' => '1/statuses/update.json',
),
'auth' => array(
'method' => 'OAuth',
'oauth_token' => $oauthToken, // From the $response['oauth_token'] above
'oauth_token_secret' => $oauthTokenSecret, // From the $response['oauth_token_secret'] above
'oauth_consumer_key' => Configure::read('Twitter.consumer_key'),
'oauth_consumer_secret' => Configure::read('Twitter.consumer_secret'),
),
'body' => array(
'status' => 'Hello world!',
),
);
$response = $Http->request($request);
Hope you like it. Any issues, please leave on github issue tracker. Any comments, let me know below. Thanks.


12 Responses so far
April 17th, 2010
5:30 am
Hey Neil:
Thanks, this is great. Just two small things I ran into (using Cake 1.3RC4) — 1) the HttpSocketOauth request() method calls HttpSocket’s buildUri method, but in fact it’s _buildUri. 2) Twitter isn’t returning a oauth_verifier value in the querystring… not sure what it’s for, doesn’t seem to be necessary anyway!
Thanks again.
April 18th, 2010
7:54 am
Thanks for your comment Tyler, glad it’s of use to you. Thanks also for mentioning the what you need to do to get it to work with 1.3.
May 1st, 2010
3:12 am
[...] Crooks unveiled his Oauth Extension which looks awesome and it is a much smarter implementation then what I did for my Twitter client, [...]
May 1st, 2010
11:21 am
Neil:
I User this extension to get requestToken from google,but get error response.
here is the request array
$request = array(
‘uri’ => array(
‘host’ => ‘google.com’,
‘path’ => ‘/accounts/OAuthGetRequestToken’,
),
‘method’ => ‘GET’,
‘auth’ => array(
‘method’ => ‘OAuth’,
‘oauth_callback’ => ‘http://XXXXXXXX/’,
‘oauth_consumer_key’ => Configure::read(‘Google.consumer_key’),
‘oauth_consumer_secret’ => Configure::read(‘Google.consumer_secret’),
‘scope’=>’http://docs.google.com/feeds/’,
),
);
May 1st, 2010
11:23 am
and this is the response:
Moved Temporarily
The document has moved here.
June 1st, 2010
11:55 am
@WangMeng I’ve got it working with Google GData services such as YouTube. Things I did differently was setting the scheme key in the uri key to ‘https’ and I used the ‘www.google.com’ host. I also specify the scope in the query key in uri, i.e.:
$request = array(
'uri' => array(
'scheme' => 'https',
'host' => 'www.google.com',
'path' => '/accounts/OAuthGetRequestToken',
'query' => array(
'scope' => $oAuthScope,
)
),
'method' => 'GET',
'auth' => array(
'method' => 'OAuth',
'oauth_consumer_key' => Configure::read(‘Google.consumer_key’),
'oauth_consumer_secret' => Configure::read(‘Google.consumer_secret’),
'oauth_callback' => $callback,
),
);
June 1st, 2010
11:57 am
I’ve committed an update to github:
“Updating for 1.3, removing code to automatically add in OAuth realm, only including body in params for generating the signature if form/urlencoded, as per OAuth spec”
http://github.com/neilcrookes/http_socket_oauth/commit/9a5957c81f545163a9a9794e7935d6200386e0f5
June 1st, 2010
3:40 pm
[...] comments Neil Crookes on OAuth extension to CakePHP HttpSocketNeil Crookes on OAuth extension to CakePHP HttpSocketYonatan on CakePHP Searchable PluginYonatan on [...]
June 12th, 2010
12:40 am
I had noticed an issue with this when attempting to access twitter where it worked on a couple servers but not on others. The common thread being that it didn’t work on servers PHP < 5.3
The issue I ran into is that I was using a callback url with a '~' in the path (for accessing a userdir) and rawurlencode in PHP 5.2 and previous would urlencode the '~' symbol to '%7E'.
I just added a str_replace to lines 122 and 130 where you rawurlencode the $requestParam['value'] and the $normalisedRequestParams and that solved it.
June 21st, 2010
9:13 pm
I just pushed a fix to the github repo where the OAuth signature was utf8_encode()’ing params even if they were already in UTF-8.
This commit also fixes the issue Wes DeBoer reports in the previous comment (thanks Wes).
July 7th, 2010
12:30 pm
Hi Neil! Great extension!
I have one question: we currently have a blog on our website that when an article is published, it creates a status update in our twitter feed. However, I am having a hard time figuring out how to do that with the redirects/callbacks etc. Any suggestions?
Regards,
Barry
July 7th, 2010
8:44 pm
@Barry, the redirects and callback are only required to get an access token, you then save the access token to your database or grab it and hard code it in your application. You then add this access token to the request to twitter when you make the status update in your post moel aftersave callback, if thats how you are doing it – does that clarify things?
Leave a comment