Tuesday 12 April 2011

Accessing Google's Content API for Shopping using OAuth in PHP

***Update 31st July 2011 - edited the post to replace my Google API account number with "XXXXXXX" in a couple of URIs - you'll need to replace with your own to get this working. Thanks to Robert for suggesting this in the comments section.****

Trying to get OAuth working against Google's APIs using the standard PHP libraries like oauth-php and the Zend library supplied by Google was turning out to be a nightmare. Nothing would work and I kept running into problems. In the end I built scripts based on oauth-php using the following useful pages to guide me:

After installing the oauth-php library and running the MySQL store install script in the library/store/mysql directory of the library files in order to set up the right MySQL database structure, I was heading in the right direction. Step-by-step, through a lot of trial and error, and by watching the apache and MySQL logs, I got to three scripts which are shown below.

1. Google OAuth Consumer Generation Script
name: test-oauth-google-consumer.php
Call this script in your browser and it will take you to Google's OAuth interface where you grant your application permission to access Google services which use OAuth (in this case the Content API for Shopping). It will then redirect you back to your own site. Meanwhile, in the background the php-oauth library is taking care of getting all the right token details etc into the database.

<?php

// Script name = test-oauth-google-consumer.php
//
// Script editor Sam Critchley - sam@l3bv.com
//
// This is a script to manage OAuth against the Google OAuth service.
// It uses php-oauth library and a set of tips gained from a web page at
// http://www.elance.com/p/api/examples/oauth/php
// In doing so, it pretty much follows the Google OAuth flow which is at
// http://code.google.com/apis/accounts/docs/OAuth_ref.html

// First include the relevant files from the oauth-php library which is in
// <YOUR_PATH>/oauth-php/library
// Also make sure you include OAuthRequestLogger to log requests
require_once '/oauth-php/library/OAuthDiscovery.php';
require_once '/oauth-php/library/OAuthRequester.php';
require_once '/oauth-php/library/OAuthRequestVerifier.php';
require_once '/oauth-php/library/OAuthServer.php';
require_once '/oauth-php/library/OAuthRequestLogger.php';

// Now add some code to initialise the OAuth store, with oauth-php connecting
// to the MySQL database using the set of parameters supplied
// N.B. Make sure you've run the install script in the oauth-php library/store/mysql
// directory to install the right tables in the database
$store_options = array('server' => 'localhost', 'username' => '<YOUR_MYSQL_USERNAME>',
                 'password' => '<YOUR_MYSQL_PASSWORD>',  'database' => '<YOUR_MYSQL_DATABASE');

$store = OAuthStore::instance("MySQL", $store_options);

// Now register the Google OAuth API with the local OAuth store. It first calls
// $store->getServer() to test if the server is already registered, and if it isn't
// then it registers it with the OAuth store.
// It's necessary to fill in the values below such as consumer_key
// For Google you get these from your domain management page at https://www.google.com/accounts/ManageDomains
// First the consumer key e.g. $consumer_key = 'www.example.com';
$consumer_key = '<YOUR_CONSUMER_KEY>';
// Then the consumer secret e.g. $consumer_secret = 'GRp9MrOW4+HHWKoRI34Le609'
$consumer_secret = '<YOUR_CONSUMER_SECRET>';
// Set the user_id for use in the database. This can, of course, be dynamic if you've got many many users
$user_id = 1;
// The $params values get added to the request
// Add the API scope which you get from the Content API for Shopping page at:
// http://code.google.com/apis/shopping/content/getting-started/usingapi-products.html
// NOTE - the scope will vary if you are accessing other Google APIs than the
// Content API for Shopping, so you'll need to change it
//
$scope = "https://www.googleapis.com/auth/structuredcontent"; // fill with the scope

// If you set $xoauth_displayname it sets the description on the Google verification
// page and gives you an error about not being able to verify the application as it's
// "on your computer and not on a registered web service"
// $xoauth_displayname = ('<YOUR_SERVICE_DISPLAY_NAME');
//
// The $params array if you're going to include the xoauth_displayname in it - if you don't
// it just uses the default from your domain management page
//$params = array('scope' => $scope, 'oauth_callback' => $callback_url, 'xoauth_displayname' => $xoauth_displayname);
//
// Don't forget to include the oauth_callback as Google requires it at this stage rather than later
$callback_url = "http://<YOUR_WEB_SERVER>/test-oauth-google-consumer-callback.php?user_id=1";
$params = array('scope' => $scope, 'oauth_callback' => $callback_url);

$store = OAuthStore::instance("MySQL", $store_options);

$server = null;
try {
        $server = $store->getServer($consumer_key, $user_id);
} catch (OAuthException2 $e) {
    $server = array(
        'consumer_key' => $consumer_key,
        'consumer_secret' => $consumer_secret,
        // Note that server_uri needs to be the URI of the API you'll be accessing or else it will throw
        // an error which you'll have to use MySQL to debug. In this case the URI is obtained from the
        // Google Content API for Shopping documentation at
        // http://code.google.com/apis/shopping/content/getting-started/usingapi-products.html
        // It will be different for different Google APIs
        'server_uri' => 'https://content.googleapis.com/content/v1/XXXXXXX/items/products/generic',
        'signature_methods' => array('HMAC-SHA1'),
        'request_token_uri' => 'https://www.google.com/accounts/OAuthGetRequestToken',
        'authorize_uri' => 'https://www.google.com/accounts/OAuthAuthorizeToken',
        'access_token_uri' => 'https://www.google.com/accounts/OAuthGetAccessToken'
    );

    $store->updateServer($server, $user_id);
}

// Now we've got everything lined up locally including all the variables necessary to
// go out and hit the Google OAuth URI, all you have to do is call OAuthRequester and
// it goes out and does it for you. Don't forget to include $params in the request
// - it wasn't included by default in the Elance documentation
// Also don't forget that if you're missing one of the parameters defined in the Google
// OAuth documentation then you will get a 400 exception error saying something like:
//
// Unexpected result from the server "https://www.google.com/accounts/OAuthGetRequestToken" (400) while requesting a request token
//
// Use the new error handling method in PHP with tryand catch
try {
$token = OAuthRequester::requestRequestToken($consumer_key, $user_id, $params);
}
catch(OAuthException2 $e)
{
        echo "Exception:<br>" . $e->getMessage();
}

// If this works successfully the oauth-php library will get back info from Google
// containing the token, token_secret, and authorize_uri values. You can test this by
// printing out the array $token, but note that it doesn't contain the token_secret
// although oauth-php should have grabbed this. You can check by looking in the
// oauth_consumer_registry and oauth_consumer_token tables in MySQL as this happens
// and watch the entries in the tables changing during the process

// TEST print out the $token array, comment this out if you don't want that at the moment
/*
echo '<br><br>OAuthRequester response:<br>';
echo '<PRE>';
print_r($token);
echo '</PRE>';
echo '<br><br>';
*/

// Now that the token and token_secret have been obtained from Google, redirect the
// user to the authorisation URI to run the OAuthAuthorizeToken process
//
// Then construct the right authorisation uri from the values at the beginning
$authorization_uri = $token['authorize_uri'] . '?oauth_token=' . $token['token'];
// Then redirect the user to it
header("Location: " . $authorization_uri);
// Then exit the script
exit();

?>

2. Google OAuth Callback Script
name: test-oauth-google-consumer-callback.php
After you've successfully authenticated over at Google, you'll be redirected back to the callback script running on your server. As well as displaying a success message the script will extract the OAuth access token Google sends back in the URL (as a GET parameter) and will store it in the database.

<?php

// Script name = test-oauth-google-consumer-callback.php
//
// Script editor Sam Critchley  - sam@l3bv.com

// This is a callback script for managing OAuth against the Google OAuth service.
// You will be redirected to this page by Google if authenticating your signup
// request is successful
//
// The script uses php-oauth library and a set of tips gained from a web page at
// http://www.elance.com/p/api/examples/oauth/php
// In doing so, it pretty much follows the Google OAuth flow which is at
// http://code.google.com/apis/accounts/docs/OAuth_ref.html

// First include the relevant files from the oauth-php library which is in
// <YOUR_PATH>/oauth-php/library
// Also make sure you include OAuthRequestLogger to log requests
require_once '/oauth-php/library/OAuthDiscovery.php';
require_once '/oauth-php/library/OAuthRequester.php';
require_once '/oauth-php/library/OAuthRequestVerifier.php';
require_once '/oauth-php/library/OAuthServer.php';
require_once '/oauth-php/library/OAuthRequestLogger.php';

// Now add some code to initialise the OAuth store, with oauth-php connecting
// to the MySQL database using the set of parameters supplied
// N.B. Make sure you've run the install script in the oauth-php library/store/mysql
// directory to install the right tables in the database
$store_options = array('server' => 'localhost', 'username' => '<YOUR_MYSQL_USERNAME>',
                 'password' => '<YOUR_MYSQL_PASSWORD>',  'database' => '<YOUR_MYSQL_DATABASE');

$store = OAuthStore::instance("MySQL", $store_options);

// Now call OAuthRequester::requestAccessToken() in oauth-php to exchange your Request Token for an Access Token
// It's necessary to fill in the values below such as consumer_key
// First the consumer key e.g. $consumer_key = 'www.example.com';
$consumer_key = '<YOUR_CONSUMER_KEY>';
// Grab the user_id from the incoming GET value in the URL
$user_id = $_GET['user_id'];

// Now process the options received as the Google stuff returns
$options = array();
$options['oauth_verifier'] = $_GET['oauth_verifier'];

try
{
    OAuthRequester::requestAccessToken($consumer_key, $_GET['oauth_token'], $user_id, 'POST', $options);

}
catch (OAuthException $e)
{
        echo "Exception:<br>" . $e->getMessage();       
}

// If OAuthRequester::requestAccessToken() does not throw an exception, then the request was successful.
// Google grants your application an Access Token and an Access Token Secret, which oauth-php inserts into
// the OAuth store. These values will be included with and used to sign all requests you make against any
// authenticated methods.

echo 'Google account request successful';

?>

3. Content API for Shopping query script
name: test-oauth-google-shopping-api.php
This is a simple script which goes and queries the Google Content API for Shopping for all the items you have listed. The content is returned in an ATOM feed so you may need to "view source" in your browser (or modify the script) to be able to view the items.

<?php

// Script name = test-oauth-google-shopping-api.php
//
// Script editor Sam Critchley - sam@l3bv.com
//
// This is a script to manage OAuth against the Google OAuth service.
// It uses php-oauth library and a set of tips gained from a web page at
// http://www.elance.com/p/api/examples/oauth/php
// In doing so, it pretty much follows the Google OAuth flow which is at
// http://code.google.com/apis/accounts/docs/OAuth_ref.html

// First include the relevant files from the oauth-php library which is in
// <YOUR_PATH>/oauth-php/library
// Also make sure you include OAuthRequestLogger to log requests
require_once '/oauth-php/library/OAuthDiscovery.php';
require_once '/oauth-php/library/OAuthRequester.php';
require_once '/oauth-php/library/OAuthRequestVerifier.php';
require_once '/oauth-php/library/OAuthServer.php';
require_once '/oauth-php/library/OAuthRequestLogger.php';

// Now add some code to initialise the OAuth store, with oauth-php connecting
// to the MySQL database using the set of parameters supplied
// N.B. Make sure you've run the install script in the oauth-php library/store/mysql
// directory to install the right tables in the database
$store_options = array('server' => 'localhost', 'username' => '<YOUR_MYSQL_USERNAME>',
                 'password' => '<YOUR_MYSQL_PASSWORD>',  'database' => '<YOUR_MYSQL_DATABASE');

// Set the user_id (used in the OAuth MySQL store) as an example for this script
$user_id = 1;

$store = OAuthStore::instance("MySQL", $store_options);

// Need to set a couple of options including (optionally) GData-Version=1.0
// see http://code.google.com/apis/gdata/docs/developers-guide.html for details
$options = array('max-results' => '10', 'v' => '1.0');

// Run the API call using the php-oauth library
$manage_request = new OAuthRequester("https://content.googleapis.com/content/v1/XXXXXXX/items/products/generic", 'GET', $options);

try {
    $result = $manage_request->doRequest($user_id);
} catch (OAuthException $e){
    // Handle error if it occurs (will appear in your apache logs).
    echo "Exception:<br>" . $e->getMessage();
}

/*
The output from <span style="font-family:monospace;">doRequest()</span> is the full array of header and body values
returned by the cUrl HTTP library. We access the JSON response using the body array key.
*/

// Print out the results of the API poll
// These are in an ATOM feed so in a normal browser you'll need to
// look at the page source if the content on screen is blank
$text = $result['body'];
echo $text;


?>

I hope these scripts are useful. Leave a comment if you have any questions.


9 comments:

  1. Also note that although the Zend GData library doesn't support the Content API for Shopping yet, you could use it just for oauth if you wanted to.

    ReplyDelete
  2. Thanks for this very helpfull post Sam.
    I struggled a bit with oauth-php lack of documentation but get it to work properly.

    ReplyDelete
  3. Thanks Sam
    This is real useful if having to jump in at the deep end !
    Really useful
    Robert

    ReplyDelete
  4. oh one last thing. You might want to xxxx out merchant account number in the request and make a note for us that don't read the API that we have to insert our own. After running the script and not changing it to my own account nbr I found that subsequent attempts using my correct merchant number failed. I had to go into mysql and delete all data from the Oauth tables.

    Thanks again mate.
    Robert

    ReplyDelete
  5. Thanks for your kind comments Robert! You may notice I've updated the post accordingly. Sam

    ReplyDelete
  6. This comment has been removed by the author.

    ReplyDelete
  7. Hi

    Any one can help me ?

    I got an issue in OAuthRequester.php.while i print the $text variable...

    HTTP/1.1 400 Bad Request
    Content-Type: text/plain; charset=UTF-8
    Date: Thu, 10 May 2012 13:16:55 GMT
    Expires: Thu, 10 May 2012 13:16:55 GMT
    Cache-Control: private, max-age=0
    X-Content-Type-Options: nosniff
    X-XSS-Protection: 1; mode=block
    Content-Length: 52
    Server: GSE

    Timestamp is too far from current time: 1336655815

    Thanks is advance

    ReplyDelete
  8. Thanks Sam. Great documentation for oauth-php consumer!
    Have you try implementing oauth-php server?

    ReplyDelete
  9. In your test-oauth-google-consumer.php, why did you do

    $store = OAuthStore::instance("MySQL", $store_options);

    twice?

    ReplyDelete