RSS

Neil Crookes

Learnings and Teachings on Web Application Development & CakePHP

Apr

12

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.

Share and Enjoy:

  • Digg
  • del.icio.us
  • StumbleUpon
  • Technorati
  • Slashdot

(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):

  1. Grab the code from my github account and add it to app/vendors/http_socket_oauth.php
  2. Register your application with Twitter (See below if you are developing locally and twitter grumbles about your call back url containing ‘localhost’)
  3. Note the consumer key and secret (I add them to Configure in bootstrap)
  4. 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.

  5. 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.

Share and Enjoy:

  • Digg
  • del.icio.us
  • StumbleUpon
  • Technorati
  • Slashdot
(6 votes, average: 5.00 out of 5)
Loading ... Loading ...

51 Responses so far

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.

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.

[...] Crooks unveiled his Oauth Extension which looks awesome and it is a much smarter implementation then what I did for my Twitter client, [...]

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/',
),
);

and this is the response:

Moved Temporarily
The document has moved here.

@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,
),
);

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

[...] comments Neil Crookes on OAuth extension to CakePHP HttpSocketNeil Crookes on OAuth extension to CakePHP HttpSocketYonatan on CakePHP Searchable PluginYonatan on [...]

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.

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).

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

@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?

Hello,

thanks for the great and easy to implement tool for oAuth.

The trouble I am facing right now is, Fatal error: Call to undefined method HttpSocketOauth::_buildUri() in C:\wamp\www\testcode\ppv10\site\app\vendors\http_socket_oauth.php on line 123

which you guys might have resolved hopefully, can you please let me know, what needed to correct for such an error.

Expecting your reply soon!

Thanks,

JOI

Depending on the version of CakePHP, v1.2 or v1.3, the method in the parent class, HttpSocket, is either _buildUri() or buildUri(), can’t remember which, but swap it for the other one, if the one in your app doesn’t work.

Hi Neil,

thanks for your help and reply !

I have changed the code at line 123 as below of vendor file: “http_socket_oauth.php”

$requestUrl = $this->buildUri($request['uri'], $uriFormat);

and it worked smooth for my CakePHP version 1.2

Hope you guys get it rock .

Thanks,

JOI

When getting request token in response i get Array ( [Failed_to_validate_oauth_signature_and_token] => ), So when trying to authorize i get an error that the token is already used. Any advise on how to make it work properly?

Nevermind my last post. I failed with the configure in bootstrap :D

Trying to get this to work in Java using Netbeans.
Such a pain…

hi Neil this is great help and nice work. i have two questions about this outh th first is how add the the consumer key and secret in the bootstrap? and the second is what is the controller when i put the code

thanks

@willians

// app/config/bootstrap.php
Configure::write('Twitter.consumer_key', '');
Configure::write('Twitter.consumer_secret', '
'),

You can put the actions in whatever controller you want, you can even just put them in a dummy_controller, get the oauth token and oauth token secret, hard code these into your bootstrap.php file too, then delete the dummy controller.

Then you can make calls to the twitter API from anywhere in your app using the last code block in the post above (replacing the values assigned to oauth_token and oauth_token_secret with those you hardcoded into bootstrap and loaded into the Configure class).

Hello Neil, Thanks for your simple code for connecting Twitter Api. I successfully connected with the Twitter and also able to post TWITT into that. But, How I can access the the TWITTS that are present in the consumer account and also please give me a solution to find out the list of all FOLLOWERS of that particular customer.

Check out my twitter plugin on github

https://github.com/neilcrookes/CakePHP-Twitter-API-Plugin

Try the TwitterStatus::find(‘homeTimeline’) and TwitterUser::find(‘followers’) methods.

Hi Neil!
Thank you for this extension!
Im trying to implement this for a User registration usin Twitter for Login, but while using you code, twitter_connect() always asks Twitter for permission, even though i already gave it before!
How could i check if a user as already permission from Twitter? So they dont have to allow everytime they do a Login.

Thank you!!

Thank u very much Neil… I got the solution. HAVE A GUD DAY.

Great contribution to the Cake community Neil. I already have it working perfectly in CakePHP 1.3. A few things to mention:

1) I’ll be adding a caching flag such that certain calls will check the default caching engine for previous calls within a certain time frame. Of course I will set parameters in place so that only the “cache safe” calls are actually cached. The point is to safe calls and increase speed where the same calls are made repeatedly.

2) I’ll be making a component specifically for twitter that will use the HttpSocketOauth for Twitter Connect such that only 2 or 3 function calls need to be made. basically a wrapper for a slimmer controller.

3) Like Pedro (November 19th, 2010) I am curious to know what the logic flow and pseudo code is for avoiding having to request authorization every time a user goes through the twitter connect process. I will research this some more and perhaps post the answer here if no one beats me to it :-)

Again, thanks so much Neil, cheers!

@neilCrookes , @Pedro and @TheNeilCrookesCommunity

if you use http://api.twitter.com/oauth/authenticate rather than http://api.twitter.com/oauth/authorize when you get the request tokens, twitter will skip the authorization process if you have authorized your app before. Simply put, it will forward the user back to the call back url without even showing the twitter “Allow this app access” page.

@Neil, it makes sense to make this switch in your class. Thanks for the awesome code. It has rekindled my love for CakePHP once again! (Got hired as a Zend programmer so I’ve neglected my cake community)

Thanks @Angel did you see the TwitterAuthComponent in the Twitter plugin I linked to above?

https://github.com/neilcrookes/CakePHP-Twitter-API-Plugin/blob/master/controllers/components/twitter_auth.php

Geez! this is exactly what I was building! how come i never came across this when I googled CakePHP Twitter Oauth? This is perfect, I am looking to right an article about how to create Twitter Apps in CakePHP in under 30 minutes. Haha, your plugin is perfect for this! Sorry for skipping the comment and not seeing it sooner.

Great work Neil!

@Angel, no worries dude, I really should get off my ass and write a blog post about it.

I *just* started getting a reponse with “Error: Incorrect signature”

Nothing had changed on my end for a while now.

Any clues? or debugging suggestions?

Cheers,

I’ve had this before too. For me it was down to the host company messing with the time on the server. SSH in and run

$> date

Hmm, that we be uber weird..

I have ntp running on my server – and the date (nor any code) has changed.. :|

I tried creating another app on twitter (belonging to the same account), created separate credentials, and and git cloned my other app/updating the credentials – and I have the same issue.

Any other thoughts?

Thanks!

An update:
I noticed that my POST calls to twitter work, its only the GET calls that return incorrect signatures (in case that helps for debugging suggestions)
Thanks

Awesome extension dude!

Worked straight out of the box for me. Very cool stuff, thanks for the labour ;)

If someone also gets the error “Fatal error: Class ‘HttpSocketOauth’ not found in”

change
App::import(‘Vendor’, ‘HttpSocketOauth’);
to
App::import(‘Vendor’, ‘http_oauth_socket’);

at least that worked for me (1.3.7)

Great work Neil!

This is a wonderful extension to allow socket interaction with the API.

Some additional error handling while returning the response data below for those of you struggling with errors.

if($response = $Http->request($request)){
$decodeData = json_decode($response);
if(array_key_exists(‘error’, $decodeData)){
if($this->twitter_connect()){
$this->twitter_callback();
}
}
}

Thank you so much for posting this! It’s saving me a lot of time!

Thanks for the extension! It’s very easy to use.

However, I am having one problem using this with Youtube; I am able to get the request token. But when trying to get access token in the request token callback function, it failed with an “invalid signature” (via HTTP GET).

My code do something like this:


$Http = new HttpSocketOauth();
$request = array(
‘uri’ => array(
‘scheme’ => ‘https’,
‘host’ => ‘www.google.com’,
‘path’ => ‘/accounts/OAuthGetAccessToken’,
‘query’ => array(
‘scope’ => ‘http://gdata.youtube.com
)
),
‘method’ => ‘GET’,
‘auth’ => array(
‘method’ => ‘OAuth’,
‘oauth_consumer_key’ => Configure::read(‘oauth_key’),
‘oauth_consumer_secret’ => Configure::read(‘oauth_secret’),
‘oauth_token’ => $this->params['url']['oauth_token']
)
);
$response = $Http->request($request);

I looked at the YT doc but the code seems right to me. Any suggestion?

Thanks in advance.

As Jeff Spicoli would say; “Awesome, totally awesome!” I was able to follow Facebook’s API but got totally turned around on Twitter. Thanks for the great work.

Thank you so much, it was very useful to me..

Amine from Morocco !

amazing!!!!
works perfectly… many thanks mate! :)

{“request”:”\/1\/statuses\/update.json”,”error”:”Incorrect signature”}

This is the error i’m getting , even when I’m using method=>POST

My request looks like:
I have the oauth_token and oauth_token_secret from the previous request from the user
Array ( [method] => POST [uri] => Array ( [host] => api.twitter.com [path] => 1/statuses/update.json ) [auth] => Array ( [method] => OAuth [oauth_token] => xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx [oauth_token_secret] => xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx [oauth_consumer_key] => xxxxxxxxxxxxxxxxxxxxxxxxx [oauth_consumer_secret] => xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ) [body] => Array ( [status] => Hello World ) )
response looks like {“request”:”\/1\/statuses\/update.json”,”error”:”Incorrect signature”}

Hi everyone,

Brand new to CakePHP. Just walked through the blog and auth tutorials without any issues. I want to get basic Twitter OAuth working on a brand new CakePHP app and am having some serious issues.

I downloaded Neil’s plugin from here:

https://github.com/neilcrookes/CakePHP-Twitter-API-Plugin

And applied it here:

/app/plugins/twitter

Now what? How do add the “connect to Twitter” link on every page? What is the Twitter callback URL going to point to? How do I check whether or not a user has been connected, access user’s information?

Appreciate your help!

Thanks

[...] OAuth extension to CakePHP HttpSocket [...]

Thank you, I was able to get the Hello World example working.

I am new to OAuth and web services in general, I hope you don’t mind some beginner questions.

When I go to twitter_connect I am of course redirected to api.twitter.com where I have to click to approve my app and then I get my oauth_token and oauth_verifier at my callback.

First question, it seems these two values are only good for posting my one Hello World tweet, is that correct? So, I need to go to twitter_connect and manually click approve for each tweet, right? My question is, how does this become automated or how can I always approve the app, basically get rid of that human click so my app is free to tweet away?

Thanks.

thank you it was very helpful :)

It seems google deprecated OAuth v1 on April 20. Do you know of a Oauth v2 extension?

What about Authentication?…….Only authorize of ‘oauth_token’ is available?…How to authenticate
like authorize : $this->redirect(‘http://api.twitter.com/oauth/authorize?oauth_token=‘ . $response['oauth_token']);

beacuse am not get my friends after some time thenj i need to wait for the certain time. please help me

How to avoid rate limit exceeded?…..please help me…

Hi,

I am trying to integrate with Xing. I am using your extension but i get an “Invalid OAuth signature” when querying for the access token…any ideas???

Thanks

Leave a comment