Using OAuth-enabled APIs with CakePHP

Published on September 01, 2008 and tagged with cakephp  component  oauth

A growing number of APIs support OAuth, i.e. it is possible to give service A access to service B without giving B’s username/password to A. In this article I’m going to show you how to use OAuth-enabled APIs with CakePHP (and the OAuth consumer component I have written).

First, there are some preparations necessary, namely the download of the required files:

  • Get the OAuth component and place its content in “app/controllers/components”
  • Get the OAuth library and put it in “vendors/OAuth”
  • Set the security level in app/config/core.php to “low” (thanks to Bryan Young for mentioning in the comments)

The next step is to register your application at the service provider (e.g. Yahoo’s Fire Eagle) to get consumer key and consumer secret. Those values are then stored in a class called FireEagleConsumer if we go on using Fire Eagle as example service provider in this article:

// app/controllers/components/oauth_consumers/fire_eagle_consumer.php
class FireEagleConsumer extends AbstractConsumer {
    public function __construct() {
        parent::__construct('THE KEY', 'THE SECRET');
    }
}

This class is then used by the component to access consumer key and secret.

Using an OAuth-enabled API consists of four steps:

  • Get RequestToken
  • Authorize RequestToken
  • Exchange RequestToken for AccessToken
  • Access the API using the AccessToken

In the ideal case the first three steps are performed once. But it is also possible that for example the service provider expires the AccessToken after some time, and so those steps have to be repeated.

Anyway, let’s have a look at how those steps look in code. We start with getting the RequestToken:

// app/controller/oauth_consumer_example_controller.php
class OauthConsumerExampleController extends AppController {
    public $uses = array();
    public $components = array('OauthConsumer');
	
    public function index() {
        $requestToken = $this->OauthConsumer->getRequestToken('FireEagle', 'https://fireeagle.yahooapis.com/oauth/request_token');
        $this->Session->write('requestToken', $requestToken);
    }
}

The code is probably self-explanatory, we have to add the component to the $components array, and then we get the RequestToken from the specified url (while writing this I just realized it would make sense to move the url to the FireEagleConsumer class…).

The next step is simple: we have to redirect the user to the authorize page of the service provider:

// app/controller/oauth_consumer_example_controller.php
class OauthConsumerExampleController extends AppController {
    public $uses = array();
    public $components = array('OauthConsumer');
	
    public function index() {
        $requestToken = $this->OauthConsumer->getRequestToken('FireEagle', 'https://fireeagle.yahooapis.com/oauth/request_token');
        $this->Session->write('requestToken', $requestToken);
        $this->redirect('http://fireeagle.yahoo.net/oauth/authorize?oauth_token='.$requestToken->key);
    }
}

After the RequestToken is authorized, we get redirected to a callback url we specified while registering the application. We can now exchange the authorized RequestToken for an AccessToken:

// app/controller/oauth_consumer_example_controller.php
class OauthConsumerExampleController extends AppController {
    public $uses = array();
    public $components = array('OauthConsumer');

    ...

    public function callback() {
        $requestToken = $this->Session->read('requestToken');
        $accessToken = $this->OauthConsumer->getAccessToken('FireEagle', 'https://fireeagle.yahooapis.com/oauth/access_token', $requestToken);
    }
}

In a real use case you would also save the data of the AccessToken (key and secret) to the database, so you don’t have to perform all those steps every time you want to call the API.

The last step is to call the API, in this example to get the latest location of the user:

// app/controller/oauth_consumer_example_controller.php
class OauthConsumerExampleController extends AppController {
    public $uses = array();
    public $components = array('OauthConsumer');

    ...

    public function callback() {
        $requestToken = $this->Session->read('requestToken');
        $accessToken = $this->OauthConsumer->getAccessToken('FireEagle', 'https://fireeagle.yahooapis.com/oauth/access_token', $requestToken);
        $data = $this->OauthConsumer->get('FireEagle', $accessToken->key, $accessToken->secret, 'https://fireeagle.yahooapis.com/api/0.1/user');
        // do something with the data
    }
}

That’s it. Feedback is welcome!

Update 2008-09-15: Slightly adapted for new version of the component.
Update 2009-01-14: Adding hint about security level.

31 comments baked

  • Andreas September 01, 2008 at 20:33

    This is great, thanks Daniel.
    Just a few weeks ago I thought, that it would be really great if there was a OAuth Component in CakePHP.

    I will try this one out as soon as i can.

    Have you also experimented with enabling a cakePHP-app as a OAuth-provider? I tried but failed horribly ;-)

  • Tarique Sani September 02, 2008 at 06:56

    +1 to Andreas’s request CakePHP app as an OAuth provider would be great!

  • cakebaker September 02, 2008 at 17:48

    @Andreas, Tarique: Thanks for your comments!

    Yes, I’m experimenting with enabling a CakePHP app to be an OAuth provider and I hope I can write about it soon ;-)

  • OAuth for .NET and CakePHP « OAuth September 09, 2008 at 08:34

    [...] first is a component for CakePHP for accessing services with OAuth called OAuth component for CakePHP (points for [...]

  • Bryan Young January 13, 2009 at 23:29

    Daniel, thanks for the great OAuth tutorial. You may want to mention somewhere in your article that the security level in /config/core.php needs to be set to ‘low’ to make this work.

  • cakebaker January 14, 2009 at 18:50

    @Bryan: Thanks for the hint, I added your hint to the article!

  • Justin Spencer January 28, 2009 at 23:35

    Thanks for the great tutorial!
    It worked just fine for Fire Eagle, but when I tried getting a request token from myspace, who also has OAuth for their applications, it returned a null $requesToken after calling getRequestToken() and passing in the proper parameters. Should the code be any different between providers?

  • cakebaker February 01, 2009 at 17:16

    @Justin: Did you use a network analyzer like Wireshark to analyze what’s going on? And do you use the correct HTTP method to request the request token?

  • Rui Cruz February 04, 2009 at 22:56

    I’ve been trying to set this up using plugins but seems like the oauth consumer cannot find the consumer files.

    It throws an error on line 69 because it’s searching for the main app component folder.

    Is this a handicap or am I doing something wrong?

  • Rui Cruz February 04, 2009 at 23:16

    Actually I changed line 59 to this

    $CONSUMERS_PATH = dirname(__FILE__).DS.’oauth_consumers’.DS;

    and it now works in the plugin except I’m having a null accessToken…

    was it me :|

  • cakebaker February 05, 2009 at 18:42

    @Rui: The issue with not finding the consumer files is definitely a bug, I didn’t think about plugins at the time I developed it. I have to fix this.

    Regarding the second issue, it is difficult to say what could be the problem. Did you use a tool like Wireshark to see what’s going on?

  • Rui Cruz February 06, 2009 at 11:17

    @cakebaker: If you change your line 59 to what it posted it will work.

    The rest was me being lazy and not setting the security level on cake to low.

    It seem to work ok now.

    BTW why not post your component on github or something?

  • cakebaker February 06, 2009 at 16:32

    @Rui: Ok, the bug is now fixed. And I’m glad the other problem disappeared :)

    The component is already available on github: http://github.com/cakebaker/oauth-consumer-component/. However, I didn’t have linked to it yet ;-)

  • Rui Cruz February 06, 2009 at 18:17

    @cakebaker: Tip, how about another article using Gdata? :)

  • cakebaker February 10, 2009 at 17:33

    @Rui: Good idea, though I don’t have used Gdata yet ;-)

  • Ryan Billingsley April 07, 2009 at 23:58

    I know this article was written back in September but I just found it and am having problems. First, writing the requestToken Object to the Session is getting destroyed by Cake once the redirect has happened. Is anyone else having this issue? I am trying to do this with Twitter and I can get Twitter to allow my application but once it redirects to my callback url, there is no token to read, so I can’t get the access token. Any help would be really appreciated.

  • cakebaker April 08, 2009 at 16:23

    @Ryan: Thanks for your comment!

    Hm, what’s the setting of “Security.level” in app/config/core.php? Can you set it to “medium” or “low”, and try again?

    Hope that helps!

  • Ryan Billingsley April 08, 2009 at 16:34

    Security level is set to low. I have tried changing Session.start to false and manually creating my session but that didn’t seem to help. I have Session.save set to database, so I can see that it is writing the token to Session, but then it creates a new session once I return from Twitter.

  • cakebaker April 08, 2009 at 17:18

    @Ryan: You mention you set “Session.save” to “database”. Does it work if you keep the default value (”php”)?

  • Ryan Billingsley April 08, 2009 at 17:33

    No it didn’t work then either. I don’t know if that is tied into the cookies being saved as CAKEPHP and PHPSESSID, I haven’t tried that yet.

  • cakebaker April 10, 2009 at 16:38

    @Ryan: Hm, at the moment I don’t have any idea what could be the problem… I currently use it with Twitter in NoseRub, without any problem so far.

  • psuedo April 17, 2009 at 18:21

    I’m running Cake 1.2.2.8120 and I’m getting the error:

    Fatal error: Class ‘OAuthConsumer’ not found in (…)/controllers/components/oauth_consumers/abstract_consumer.php on line 40

  • psuedo April 17, 2009 at 18:25

    Sorry, I just didn’t put the library into the vendors directory when I thought I had… Feel free to delete this and the last comment!

  • cakebaker April 22, 2009 at 09:01

    @psuedo: No problem, I hope it works now as expected!

  • Jerry May 04, 2009 at 16:45

    Does anyone know how to import contacts from Gmail, yahoo hotmail using this?

    The example is very basic but a good example to start off with.

    Thanks

  • cakebaker May 05, 2009 at 16:22

    @Jerry: At least if the services you mention provide an OAuth-enabled API it should work in a similar way as shown in the example: you have to get an access token and with that access token you can then access the respective API. Or did you encounter a specific issue?

  • Keith May 29, 2009 at 22:52

    Hi David,

    Iam trying to connect to google docs api using this component.. however, I get an error saying
    “parameter_absent oauth_parameters_absent:scope ”

    The code Im using is as follows:

    $requestToken = $this->OauthConsumer->getRequestToken('Google', 'https://www.google.com/accounts/OAuthGetRequestToken', 'GET');
    $this->Session->write('requestToken', $requestToken);
    		
    $this->redirect('https://www.google.com/accounts/OAuthAuthorizeToken?oauth_token='.$requestToken->key);

    Because of this I cannot seem to get past the first step of this tutorial ;)

    Ever run across this?
    thanks

  • Keith June 01, 2009 at 16:22

    Fixed… our server time was off.

  • cakebaker June 03, 2009 at 16:20

    @Keith: Good to hear you could fix the issue in the meantime :)

  • J. Adam Moore June 14, 2009 at 18:25

    You don’t have an example of using a previously stored access token. This would be greatly appreciated as it is not clear to me at all how to do this, nor have I seen or heard of anyone doing this with a site like, let’s say Twitter. Is it possible, or are you just pointing out how to use a preexisting library of code? Because as actual solutions go, this one isn’t fully realized.

  • cakebaker June 16, 2009 at 17:55

    @J. Adam: Thanks for your comment!

    Well, usually an access token is represented by a model, and so you retrieve it with something like $this->AccessToken->find(’first’, some criteria); And with those data you can then call the respective API as shown in the article, simply replace $accessToken->key and $accessToken->secret with the data you got from your model.

    Hope that helps!

Bake a comment




(for code please use <code>...</code> [no escaping necessary])

© daniel hofstetter. Licensed under a Creative Commons License