AAWS signed URL function

Jump to: navigation, search

This is the code for a PHP function that produces a signed query to Amazon Product Advertising API. To use it, you must have an Web Services account and plug in your own public and private keys. This code is not guaranteed to work, to work well, or to comply with your Web Services contract. Use it at your own risk as a starting point.

See PA API Query Tester for a standalone PHP page that uses this function and displays the API’s response. You’ll need it for debugging and to manage your disillusionment.

The important thing to remember when signing a query is that your signing process must EXACTLY match Amazon's signing process. When Amazon receives your request:

  1. the signature is removed from the query and saved for later
  2. the remaining query parameters are sorted by key and a new, rawurlencoded query string is generated
  3. the method, server, uri, and query string joined into a signing string (separated with linefeed characters)
  4. the signing string is sha256 encoded
  5. that sha256 encoded string is compared to the signature you sent

So, in order to create a matching signature, your code must follow the same procedure. Your query parameters and values must be rawurlencoded (using "%20", not "+", leaving "~" unencoded), and the parameters must be sorted case sensitive. Every character in the signing string must match. You must use linefeed, not returns.

It is very easy to do with PHP. Below is a function that shows how.

The query that is passed to the function is an associative array. The array keys are the query parameters. Using an associative array is a useful way to build a query because it is easy to read, easy to sort (a necessary step), and avoids duplicate parameters. The values of the passed-in array should not be url_encoded—the function handles all that.

Some query parameters are added by the function so that the code that builds the query array can concentrate on the parts that really matter, thereby making your code easier to read.

/* Define the constants needed to access the PA API */
define( 'MY_ASSOCIATE_ID', 'myid-20' );
define( 'MY_PUBLIC_KEY',   'shortstring' );
define( 'MY_PRIVATE_KEY',  'muchlongerstring' );
 
/* Set the Amazon locale, which is the top-level domain of the server */
$amz_locale = '.com';
 
$query = array( 'Operation'     =>'ItemLookup', 
                'ResponseGroup' =>'Small,Images',
                'IdType'        =>'ASIN',
                'ItemId'        =>'0060558121' );
$signed_url = sign_query($query);
 
/* Use CURL to retrieve the data so that http errors can be examined */
$ch = curl_init($signed_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 7);
$xml_string = curl_exec($ch);
$curl_info = curl_getinfo($ch);
curl_close($ch);
 
if($curl_info['http_code']==200) {
    // dump_xml($xml_string);
    $xml_obj = simplexml_load_string($xml_string);
}
else {
    /* examine the $curl_info to discover why AWS returned an error 
       $xml_string may still contain valid XML, and may include an
       informative error message */
}
 
/* Traverse $xml_obj to display parts of it on your website */
print_r($xml_obj);
exit();
 
 
function sign_query($parameters) {
    //sanity check
    if(! $parameters) return '';
 
    /* create an array that contains url encoded values
       like "parameter=encoded%20value" 
       USE rawurlencode !!! */
    $encoded_values = array();
    foreach($parameters as $key=>$val) {
        $encoded_values[$key] = rawurlencode($key) . '=' . rawurlencode($val);
    }
 
    /* add the parameters that are needed for every query
       if they do not already exist */
    if(! $encoded_values['AssociateTag'])
        $encoded_values['AssociateTag']= 'AssociateTag='.rawurlencode('MY_ASSOCIATE_ID');
    if(! $encoded_values['AWSAccessKeyId'])
        $encoded_values['AWSAccessKeyId'] = 'AWSAccessKeyId='.rawurlencode('MY_PUBLIC_KEY');
    if(! $encoded_values['Service'])
        $encoded_values['Service'] = 'Service=AWSECommerceService';
    if(! $encoded_values['Timestamp'])
        $encoded_values['Timestamp'] = 'Timestamp='.rawurlencode(gmdate('Y-m-d\TH:i:s\Z'));
    if(! $encoded_values['Version'])
        $encoded_values['Version'] = 'Version=2011-08-01';
 
    /* sort the array by key before generating the signature */
    ksort($encoded_values);
 
 
    /* set the server, uri, and method in variables to ensure that the 
       same strings are used to create the URL and to generate the signature */
    global $amz_locale;
    $server = 'webservices.amazon'.$amz_locale;
    $uri = '/onca/xml'; //used in $sig and $url
    $method = 'GET'; //used in $sig
 
 
    /* implode the encoded values and generate signature
       depending on PHP version, tildes need to be decoded
       note the method, server, uri, and query string are separated by a newline */
    $query_string = str_replace("%7E", "~", implode('&',$encoded_values));   
    $sig = base64_encode(hash_hmac('sha256', "{$method}\n{$server}\n{$uri}\n{$query_string}", 'MY_PRIVATE_KEY', true));
 
    /* build the URL string with the pieces defined above
       and add the signature as the last parameter */
    $url = "http://{$server}{$uri}?{$query_string}&Signature=" . str_replace("%7E", "~", rawurlencode($sig));
    return $url;
}
 
 
/*	This function displays the XML. 
	It sends a header so your browser can format the XML for you. */
function dump_xml($XML_string) {
    header("Content-type: text/xml; charset=utf-8");
    die($XML_string);
}
 
Personal tools