OAuth is great – there's no need to save users' passwords, it's – in theory – a consistent way to interact with other services, and it's hopefully something that your users are familiar and comfortable using. But if you're not just interacting with your users' accounts – for example, your application uses a single account on a service to broadcast messages, or analyze data – getting or renewing the access token can be painful.
For example, take a Twitter application that acts as a auctioneer – tracking bids in the form of @mentions, posting the current high bid, and replying or sending direct messages to the participants. In that case only one OAuth token is needed for the application’s Twitter account. So how do you get the token?
Setup a rarely used admin function to authorize the application's Twitter account? Use a one off script to request then echo the credentials to the browser and copy/paste to a config file?
In that case the application is acting like a desktop application – at least from the perspective of the web server. Some services offer a PIN-based OAuth dance for that use case, and a while back I ran across a more elegant way to do this: A Self Updating Command Line Script
The code looked great, but I use Zend_Twitter. So let's do the same thing – use a command line script and PIN based OAuth request – with my favorite framework.
The initial request token setup looks much like a normal request:
$consumer = new Zend_Oauth_Consumer(array(
'siteUrl' => 'https://twitter.com/oauth',
'consumerKey' => $key,
'consumerSecret' => $secret
));
The only thing missing is a callbackUrl. If you’ve setup your application type as ‘client’ in Twitter, you don’t need to specify any callbackUrl. If the application type is ‘browser’, then you’ll need to override the default callback – but not in the config array. Doing it there will get you a Zend_Oauth_Exception about an invalid URI. You’ll need to set the special callback – ‘oob’ – when getting the request token:
$requestToken = $consumer->getRequestToken(array('callbackUrl' => 'oob'));
Then simply echo the OAuth link to so the account can grant access, and instead of redirecting, Twitter will show the user a PIN:
echo "OAuth Link: {$consumer->getRedirectUrl()}" . PHP_EOL;
fwrite(STDOUT, "Enter the PIN: ");
Once the PIN is entered, the process is similar to how you would get a normal access token. Since getAccessToken() expects an array of GET parameters, that can be emulated by passing an array with the pin and the original request token (just the token, as it would have been passed back to the callback). The request token is also passed as the second parameter (as a Zend_Oauth_Token_Request):
$pin = trim(fgets(STDIN));
try{
$accessToken = $consumer->getAccessToken(array('oauth_verifier' => $pin, 'oauth_token' => $requestToken->getToken()), $requestToken);
} catch (Zend_Oauth_Exception $e){
echo $e->getMessage() . PHP_EOL;
exit;
}
The access token can be persisted a few different ways. Most of the Zend_Oauth examples serialize the token and store it, but to keep things friendly to humans, let’s just echo the token’s data:
echo "OAuth Token: {$accessToken->getToken()}" . PHP_EOL;
echo "OAuth Secret: {$accessToken->getTokenSecret()}" . PHP_EOL;
Now that the core functionality is working, things can be made a little better. Instead of hard coding the application’s OAuth keys, let’s grab them from the command line using Zend_Getop:
//setup command line opts
$opts = new Zend_Console_Getopt(array(
'key|k=w' => 'OAuth Key - required',
'secret|s=w' => 'OAuth Secret - required'));
//validate the passed options
try {
$opts->parse();
} catch (Zend_Console_Getopt_Exception $e) {
echo $e->getUsageMessage();
exit;
}
//ensure both key and secret have been passed
if(!isset($opts->k) OR !isset($opts->s)){
echo $opts->getUsageMessage();
exit;
}
$key = $opts->k;
$secret = $opts->s;
We now have a nice command line script that can get an access token for any Twitter application. But it still requires a bit of copy/paste to store the token keys someplace. Let’s add the ability to pull the applications OAuth keys from a config file, then write the access token data to that config file. First a few options are added to the getop:
$configTypes = array('ini', 'xml', 'json', 'yaml');
$opts = new Zend_Console_Getopt(array(
'key|k=w' => 'OAuth Key - required if not using a config file',
'secret|s=w' => 'OAuth Secret - required if not using a config file',
'config|c=s' => 'Config File - required if not using a key/secret pair',
'config-type|t=w' => 'Config Type ['.implode(', ', $configTypes).'] - defaults to extension of file',
'config-section|n=w' => 'Config Section - the section to use'));
Then, if a config file is passed, the key and secret are loaded from it (at this point the script expects the data to be in oauth->consumer->key, but that could be made more flexible):
//ensure a config file or both key and secret have been passed
if(!isset($opts->c) AND (!isset($opts->k) OR !isset($opts->s))){
echo $opts->getUsageMessage();
echo "A key/secret pair or a config file are required." . PHP_EOL;
exit;
}
//load the config file if passed
if(isset($opts->c)){
//allow type to be manually set
if(isset($opts->t)){
$type = $opts->t;
//or default to file's extension
} else {
$info = pathinfo($opts->c);
$type = $info['extension'];
}
//ensure config type is valid
$type = strtolower($type);
if(!in_array($type, $configTypes)){
echo $opts->getUsageMessage();
echo "$type is not a valid config file type." . PHP_EOL;
exit;
}
//determine the file to include/config class to use
$type = ucfirst($type);
require_once "Zend/Config/$type.php";
$class = "Zend_Config_$type";
//read the config file and get the consumer data
$config = new $class($opts->c, $opts->n);
if(!isset($config->oauth->consumer->key) OR !isset($config->oauth->consumer->secret)){
echo $opts->getUsageMessage();
echo "could not find oauth.consumer configuration" . PHP_EOL;
exit;
}
$key = $config->oauth->consumer->key;
$secret = $config->oauth->consumer->secret;
//use a consumer key/secret passed
} else {
$key = $opts->k;
$secret = $opts->s;
}
The script can read the application’s keys from the config file, so now let’s update that same config file instead of just echoing the token data.
if(isset($opts->c)){
//reload the config file for writing
$config = new $class(
$opts->c,
null,
array('skipExtends' => true, 'allowModifications' => true));
//jump through a few hoops if the config file uses sections
if(isset($opts->n)){
if(!isset($config->{$opts->n}->oauth)){
$config->{$opts->n}->oauth = array();
}
$config->{$opts->n}->oauth->access = array();
$config->{$opts->n}->oauth->access->token = $accessToken->getToken();
$config->{$opts->n}->oauth->access->secret = $accessToken->getTokenSecret();
} else {
$config->oauth->access = array();
$config->oauth->access->token = $accessToken->getToken();
$config->oauth->access->secret = $accessToken->getTokenSecret();
}
//write the new config file
require_once "Zend/Config/Writer/$type.php";
$class = "Zend_Config_Writer_$type";
$writer = new $class(array('config' => $config, 'filename' => $opts->c));
$writer->write();
}
But wait – how do you use the token? Most of the Zend_Oauth examples persist the serialized access token object, not the more human friendly token/secret pair. Here’s how to setup the access token manually (it’s really pretty simple):
$token = new Zend_Oauth_Token_Access();
$token->setToken($config->oauth->access->token)
->setTokenSecret($config->oauth->access->secret);
$twitter = new Zend_Service_Twitter(array(
'accessToken' => $token,
'consumerKey' => $config->oauth->consumer->key,
'consumerSecret' => $config->oauth->consumer->secret));
And there it is, a simple command line script to OAuth an application’s account – and optionally, update the config file. Seems like someone should take this and make it even nicer with Zend_Tool.
You can view the code (along with the various changes) at gist.github.com.




June 8, 2011
Zend Framework