Using Guzzle with Twitter via Oauth

Miguel Ibarra Romero

In a previous article, we found out about Guzzle and how it can aid us in the task of establishing communication with third party APIs over HTTP. We used it to get the output of a random number generator and for basic interaction with Github’s API. It also offers a series of ‘subscribers’, log-subscriber being one of them and we saw how easy it was to integrate monolog into it.

While interacting with Github’s API we discovered that it supports basic authentication (sending plain username/password). But what if the API we want to use just offers OAUTH authentication?

Guzzle’s Oauth

Implementing OAUTH from scratch can be a hard and time consuming task, and would be like reinventing the wheel… You could grab a third party library to deal with OAUTH requests, but why do that when Guzzle has its own OAUTH subscriber? Yay!

First of all, I have to make you aware that Guzzle has dropped support for PHP 5.3, so you’ll need PHP 5.4 to follow this exercise. The installation instructions from the previous article changed a little bit, yet composer makes our lives a lot easier.

Our goal is to interact with the Twitter API and get our own timeline for which ever purpose we need. We’ll begin with our composer.json file:

{
    "name": "johndoe/guzzle-twitter",
    "description": "PoC for Sitepoint article",
    "authors": [
        {
            "name": "John Doe",
            "email": "john.doe@gmail.tst"
        }
    ],
    "minimum-stability": "dev",
    "require": {
        "guzzlehttp/guzzle": "4.*",
        "guzzlehttp/log-subscriber": "1.*",
        "monolog/monolog": "*",
        "guzzlehttp/oauth-subscriber": "*",
    }
}

Please note that we are using Guzzle 4.x, the log subscriber, monolog as our logging back end, and finally the main goal of this article: oauth-subscriber.

Now we should be able to perform a composer update command to install everything needed.

As for the code, it is very simple:

<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;
use GuzzleHttp\Subscriber\Oauth\Oauth1;
use GuzzleHttp\Subscriber\Log\LogSubscriber;
use GuzzleHttp\Subscriber\Log\Formatter;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;


date_default_timezone_set('America/Phoenix'); //Set to a proper timezone

/*
 * Setting up the logging backend, 
 */
$log = new Logger('guzzle');
$log->pushHandler(new StreamHandler('guzzle.log'));  // Log will be found at a file named guzzle.log
$subscriber = new LogSubscriber($log, Formatter::SHORT); //To see full details, you can use Formatter::DEBUG

/*
 * Creating the Guzzle client, we are setting oauth as the default authentication method
 */
$client = new Client(['base_url' => 'https://api.twitter.com', 'defaults' => ['auth' => 'oauth']]);

/*
 * Setting up the oauth subscriber parameters.  Parameter values have to be generated at the Twitter site
 */
$oauth = new Oauth1([
    'consumer_key'    => '',
    'consumer_secret' => '',
    'token'           => '',
    'token_secret'    => ''
]);

/*
 * Attaching the oauth and the log subscriber to the client
 */
$client->getEmitter()->attach($oauth);

$client->getEmitter()->attach($subscriber);

/*
 * Executing a GET request on the timeline service, pass the result to the json parser
 */
$res = $client->get('1.1/statuses/home_timeline.json')->json();

print_r($res); //we have the parsed response in an array!

Now let’s see the interesting lines here

<?php

// ......

$client = new Client(['base_url' => 'https://api.twitter.com', 'defaults' => ['auth' => 'oauth']]);

// ......

The Guzzle HTTP’s Client object constructor accepts an array defining several configuration options. The defaults parameter is defined in the documentation as “Default request options to apply to each request” (see the code on Github). In this case, we are defining that for every request the client performs, it has to use the OAUTH authorization mechanism.

// ......

$oauth = new Oauth1([
    'consumer_key'    => '',
    'consumer_secret' => '',
    'token'           => '',
    'token_secret'    => ''
]);

// ......

Next we create an Oauth1 object, containing the parameters needed for an OAUTH authorization. The parameter values needed depend on whether we set up OAUTH in 1-legged or 3-legged mode. You can see this article for an explanation on the differences between those modes.

The essential parameters are consumer_key and consumer_secret. Twitter will generate those parameters when you register an app as follows:

First of all, go to https://apps.twitter.com/ and click on the [Create New App] button. Fill in the Name, Description and Website field, but leave the Callback URL blank for now. Agree to the TOS and finally click on the [Create your Twitter application] button.

Once you register you application, go to the [Api Keys] tab. You should see the Api Key and API Secret codes; those are your consumer_key and consumer_secret parameters.

OAUTH 1-legged mode is the easiest to code. To tell OUTH we want to use this mode, we have to supply the token and token_secret parameters. We will have to generate those on Twitter as follows:

Just below where your API key and API secret codes are, you should see a [Create my access tokens] button. Clicking it, you will see your Access token and Access token secret codes: those are the token and token_secret parameters. Now you have everything to fill in the Oauth1 object.

To finally be able to make our code work, we just need to attach the OAUTH subscriber to the Guzzle client with the $client->getEmitter()->attach($oauth); command. We are ready to execute our first request!

On Twitter you can use 3-legged Oauth by making some changes.

First we have to create an entry point for our application with Twitter to validate a user:

<?php
session_start();
require 'vendor/autoload.php';

use GuzzleHttp\Client;
use GuzzleHttp\Subscriber\Oauth\Oauth1;
use GuzzleHttp\Subscriber\Log\LogSubscriber;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use GuzzleHttp\Subscriber\Log\Formatter;

date_default_timezone_set('America/Phoenix');

$log = new Logger('guzzle');
$log->pushHandler(new StreamHandler('guzzle.log'));

$client = new Client(['base_url' => 'https://api.twitter.com', 'defaults' => ['auth' => 'oauth']]);

$subscriber = new LogSubscriber($log, Formatter::SHORT);
$client->getEmitter()->attach($subscriber);

$oauth = new Oauth1([
    'consumer_key'    => '[your_api_key]',
    'consumer_secret' => '[your_api_secret]'
]);

$client->getEmitter()->attach($oauth);

// Set the "auth" request option to "oauth" to sign using oauth
$res = $client->post('oauth/request_token', ['body' => ['oauth_callback' => 'http://[yourawesomehost]/callback.php']]);

$params = (string)$res->getBody();

parse_str($params);

$_SESSION['oauth_token'] = $oauth_token;
$_SESSION['oauth_token_secret'] = $oauth_token_secret;

header("Location: https://api.twitter.com/oauth/authenticate?oauth_token={$oauth_token}");

The most relevant parts of the code are

// ......

$oauth = new Oauth1([
    'consumer_key'    => '[your_api_key]',
    'consumer_secret' => '[your_api_secret]'
]);

// ......

Please notice that we are not using the token and token_secret parameters (yet). You still need to generate your API key and API secret as explained above.

// ......

$res = $client->post('oauth/request_token', ['body' => ['oauth_callback' => 'http://[yourawesomehost]/callback.php']]);

// ......

The statement above will send a POST request to oauth/request_token (this is the first leg of OAUTH 3-legged process). We are passing as a parameter a required callback url where Twitter will redirect the user once he has logged in and granted authorization to our application to use his account.

A successful response will include 2 parameters: oauth_token and oauth_token_secret. Be sure to store these two values; in the example above they are stored in $_SESSION.

// ......

header("Location: https://api.twitter.com/oauth/authenticate?oauth_token={$oauth_token}");

// ......

As a final step, we will redirect the user to a Twitter login screen. There, he will need to sign in and grant our application access to his account information (this is the second leg). We need to send as a parameter the oauth_token parameter we received in the first request.

Now let’s look at the contents of the callback.php file:

<?php
session_start();
require 'vendor/autoload.php';

use GuzzleHttp\Client;
use GuzzleHttp\Subscriber\Oauth\Oauth1;
use GuzzleHttp\Subscriber\Log\LogSubscriber;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use GuzzleHttp\Subscriber\Log\Formatter;

date_default_timezone_set('America/Phoenix');

$authToken = $_GET['oauth_token'];
$authVerifier = $_GET['oauth_verifier'];

$log = new Logger('guzzle');
$log->pushHandler(new StreamHandler('guzzle.log'));

$client = new Client(['base_url' => 'https://api.twitter.com', 'defaults' => ['auth' => 'oauth']]);

$subscriber = new LogSubscriber($log, Formatter::SHORT);
$client->getEmitter()->attach($subscriber);

$oauth = new Oauth1([
    'consumer_key'    => '[your_api_key]',
    'consumer_secret' => '[your_api_secret]',
    'token' => $_SESSION['oauth_token']
]);

$client->getEmitter()->attach($oauth);
  
if ($authToken == $_SESSION['oauth_token'])
{
  $res = $client->post('oauth/access_token', ['body' => ['oauth_verifier' => $authVerifier]]);
  
  $params = (string)$res->getBody();

  parse_str($params);
  
  $_SESSION['oauth_token'] = $oauth_token;
  $_SESSION['oauth_token_secret'] = $oauth_token_secret;
  $_SESSION['userId'] = $user_id;
  $_SESSION['screenName'] = $screen_name;
  
  header("Location: timeline.php");
}

If the user signed in and authorized our application, Twitter will redirect him to our callback file. It will provide two parameters: oauth_token and oauth_verifier. We need to catch these two.

<?php

// ......

$authToken = $_GET['oauth_token'];
$authVerifier = $_GET['oauth_verifier'];

// ......

Please beware that getting external parameters without filtering and validating them first, is a great security hazard for your application. For simplicity’s sake, we are not sanitizing any input/output, just make sure YOU DO! ;)

Let’s look at Guzzle’s Oauth subscriber now

<?php

// ......

$oauth = new Oauth1([
    'consumer_key'    => '[your_api_key]',
    'consumer_secret' => '[your_api_secret]',
    'token' => $_SESSION['oauth_token']
]);

// ......

Now the token parameter is present again from the first request.

// ......

if ($authToken == $_SESSION['oauth_token']) 

// ......

For security purposes, we have to validate if the oauth_token parameter provided to our callback is the same as the one we received in the first request.

Here comes the third leg:

// ......

$res = $client->post('oauth/access_token', ['body' => ['oauth_verifier' => $authVerifier]]);

// ......

We have to send a POST request to oauth/access_token and send the oauth_verifier parameter we received in the callback to get the final oauth_token and oauth_token_secret parameters needed for a full OAUTH request. If the request is successful, along with those two parameters, we will receive others like the user_id and screen_name. We can store all those in session to finally make an authenticated OAUTH request as our 1-legged example:

<?php
session_start();
require 'vendor/autoload.php';

use GuzzleHttp\Client;
use GuzzleHttp\Subscriber\Oauth\Oauth1;
use GuzzleHttp\Subscriber\Log\LogSubscriber;
use GuzzleHttp\Subscriber\Log\Formatter;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

date_default_timezone_set('America/Phoenix'); //Set to a proper timezone

/*
 * Setting up the logging backend, 
 */
$log = new Logger('guzzle');
$log->pushHandler(new StreamHandler('guzzle.log'));  // Log will be found at a file named guzzle.log
$subscriber = new LogSubscriber($log, Formatter::SHORT); //To see full details, you can use Formatter::DEBUG

/*
 * Creating the Guzzle client, we are setting oauth as the default authentication method
 */
$client = new Client(['base_url' => 'https://api.twitter.com', 'defaults' => ['auth' => 'oauth']]);

/*
 * Setting up the oauth subscriber parameters.  Parameter values 
 * have to be generated at the Twitter site
 */
$oauth = new Oauth1([
    'consumer_key'    => '[your_api_key]',
    'consumer_secret' => '[your_api_secret]',
    'token'           => $_SESSION['oauth_token'],
    'token_secret'    => $_SESSION['oauth_token_secret']
]);

/*
 * Attaching the oauth and the log subscriber to the client
 */
$client->getEmitter()->attach($oauth);

$client->getEmitter()->attach($subscriber);

/*
 * Executing a GET request on the timeline service, pass the result to the json parser
 */
$res = $client->get('1.1/statuses/home_timeline.json')->json();

echo '<pre>';

echo 'Timeline for user ' . $_SESSION['screenName'] . ':' . PHP_EOL . '<hr>';

foreach ($res as $tweet)
{
  echo 'From: ' . $tweet['user']['name'] . ' (@' . $tweet['user']['screen_name'] . ')' . PHP_EOL;
  echo '  ' . htmlentities($tweet['text']) . PHP_EOL .  '<hr>';
}

At the end, we have a complete Guzzle OAUTH subscriber object. This object has the token and token_secret parameters received from the third leg request and stored in session:

// ......

$oauth = new Oauth1([
    'consumer_key'    => '[your_api_key]',
    'consumer_secret' => '[your_api_secret]',
    'token'           => $_SESSION['oauth_token'],
    'token_secret'    => $_SESSION['oauth_token_secret']
]);

// ......

Conclusion

As you can see, Guzzle makes performing OAUTH authorization a snap, without having to worry about the gory details of request signing. If you have any questions or suggestions, please let me know in the comments below. All the source code for this tutorial is available on Github. Any constructive feedback will always be welcome!

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • Miguel Ibarra

    Thanks for your feedback. As a disclaimer, provided code should never be executed on production servers, or be part of a production ready application. Sanitation and validation techniques can depend on the framework you are using, php version, and even taste. I leave it open to be implemented as needed.

  • Franz Josef Kaiser

    Just found the Guzzle library on GitHub and was searching for a quick overview. Really well done article. Thanks.