Was playing with OAUTH this weekend and i spent some time trying to find a nice and elegant setup. OAUTH was designed to simplify and unify websites interactions by having a common way to call their API while providing authentication (thanks to user credential on another site). It is used more and more by big social media players like Twitter or Foursquare so that they can provide their service to anybody.
I wanted something simple: to integrate Twitter in a website and let them deal with authentication/registration. The new users will then access my site with their Twitter login only; there will be no login form nor registration process on my site. Later you may need to store date relative to the user on your site, this will be then very easy to add.
First some basics. To use OAUTH you need first to register your application to the service provider. Then using the given key and secret tokens you can ask for a request token; the user should then be redirected to the service provider site where he can login with his own credential, after that the user is redirected to your application with the request token parameter. You will then be authenticated, you may retrieve the screen name and other data to identify the user and store it in the session. This is a very short description, you may start look a the official documentation also.
A few tips:
- you can test locally, the service provider never access your site, he just redirects (like in 301 redirect) the user to your site. So in you first baby steps, you can make your site points to 127.0.0.1 in your hosts file and let the magic happen.
- i used the PECL library OAUTH because i trust C extension better. Still i read the tutorials/doc for OAUTH_PHP and ZendOauth to have a better overview on the usage of Oauth. They are all very similar so read, compare and make your choice. Finally I highly recommend this tutorial from Rasmus Lerdorf.
- handle the errors and exception correctly to help you debug and understand. In my case i let symfony catch everything and show me the stack.
Ok, then lets start with a basic sandbox Symfony 1.4, wget, untar, chmod pif pouf ready. Now lets create a module connect to handle the connection. Inside we will create 2 actions:
- the first one ‘Request’ will be used to ask Twitter for a request token and then redirect the user with this token to Twitter for him to authenticate.
- the second one ‘Callback’ is the destination where Twitter will redirect the user after he authenticates. It will then register those data in the session to make the user authenticated on our symfony side.
I like classes so i will use a class ‘myOauthAuthenticator’ to handle most of the logic. The code for the action ‘Request’ look like this then:
class connectActions extends sfActions
{
public function executeIndex(sfWebRequest $request)
{
$mc = new myOauthAuthenticator(TWITTER_KEY_TOKEN, TWITTER_SECRET_TOKEN);
$mc->processRequestToken();
$this->getUser()->setAttribute('token_secret', $mc-> token_request_secret, 'oauth');
$this->redirect( $mc->getAuthorizeUrl() );
}
We create an instance of our authenticator class using the tokens given by Twitter when we registered our app. The method ‘processRequestToken’ then makes a call to Twitter to retrieve the request token. We store it back in the session to be used in the ‘Callback’ action. We then redirect the user to Twitter so that he can log in.
On Twitter, the user must log in and then should accept the authorization request to let our app used Twitter on the user behalf. This is what we want so we click allow and then Twitter redirects us back to the application with a token as parameter.
Now lets take a look at the ‘Callback’ action:
public function executeOauthCallback(sfWebRequest $request)
{
$req_token = $request->getParameter('oauth_token', false);
$user_secret_token = $this->getUser()->getAttribute('token_secret', false, 'oauth');
if( ! $req_token || ! $user_secret_token )
{
$this->redirect('@homepage');
}
$mc = new myOauthAuthenticator(TWITTER_KEY_TOKEN, TWITTER_SECRET_TOKEN);
$mc->processAccessToken($req_token, $user_secret_token);
$this->getUser()->setAttribute('token', $mc->token_request, 'oauth');
$this->getUser()->setAttribute('token_secret', $mc->token_request_secret, 'oauth');
$this->data = $mc->getUserInfo();
$this->getUser()->signIn($this->data);
}
We check first that we really receive the token as parameter and that we got the previous token in the session. Then we request the access token and we store the tokens from the response in the session. Using those new tokens, we retrieve data from the user and set the user as authenticated. Et voila.
Now lets look more in details the class ‘myOauthAuthenticator’:
class myOauthAuthenticater
{
public
$token_request,
$token_request_secret;
private
$key,
$secret,
$oauth;
public function __construct($key, $secret)
{
$this->secret = $secret;
$this->key = $key;
$this->oauth = new OAuth($this->key, $this->secret, OAUTH_SIG_METHOD_HMACSHA1, OAUTH_AUTH_TYPE_URI);
}
We use the HMAC-SHA1 signature method as it is the only one supported by Twitter now. The URI type means we will communicate by sending GET parameter; if you need to send POST parameter like by updating your status for example, you may change to the option OAUTH_AUTH_TYPE_FORM.
public function getAuthorizeUrl()
{
return 'http://twitter.com/oauth/authorize?oauth_token='.$this->request_token;
}
Nothing special here.
public function processRequestToken()
{
$request_token_info = $this->oauth->getRequestToken('http://twitter.com/oauth/request_token');
$this->token_request = $request_token_info['oauth_token'];
$this->token_request_secret = $request_token_info['oauth_token_secret'];
}
First step in the connection, those parameters will be stored in the session and passed to the next function in the second step.
public function processAccessToken($req_token, $user_token_secret)
{
$this->oauth->setToken($req_token, $user_token_secret);
$access_token_info = $this->oauth->getAccessToken('http://twitter.com/oauth/access_token');
$this->token_request = $access_token_info['oauth_token'];
$this->token_request_secret = $access_token_info['oauth_token_secret'];
}
Second step, we reinitialize the tokens by those received before and we get new tokens. We need to overwrite the previous ones in the session by those ones. Those ones are the one used to any further interactions with the service provider.
public function getUserInfo()
{
$this->oauth->setToken($this->token_request, $this->token_request_secret);
$this->oauth->fetch('http://twitter.com/account/verify_credentials.json');
return json_decode($this->oauth->getLastResponse());
}
This method is basically a normal data fetch using the authenticated token. The result is returned as JSON. A check is missing to make sure that the response code is correct.
The last class is ‘myUser’ to allow the authentication of the user in symfony:
class myUser extends sfBasicSecurityUser
{
public function signIn($data)
{
$this->setAttribute('nickname', (string)$data->screen_name, 'user');
$this->setAttribute('description', (string)$data->description, 'user');
$this->setAttribute('status', (string)$data->status->text, 'user');
$this->setAttribute('location', (string)$data->location, 'user');
$this->setAuthenticated(true);
}
Our site should be now powered by Twitter. You can easily retrieve data from Twitter via oauth and also tweet directly from your site using the last tokens. Some clean up are necessary, the parameters fit better inside a config file. Also the myOauthAuthenticater class is the only part really specific to Twitter and can be easily configured to be used with another Oauth service provider like Yahoo or Foursquare.
I hope this quick tutorial will give you a better overview of the possibilities of Oauth and help you integrate it in your products. Cheers!