Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generating OAuth 1 Signature in PHP

Tags:

php

oauth

I'm trying to connect to the LivePerson Engagement History API and I'm running into an issue that I believe is related to the signature being generated.

First off, the API already provides the necessary consumer key, consumer secret, access token, and token secret. So I don't have to go through the process of retrieving those. In order to access their API I just have to provide the auth header. I've mocked everything up using Postman and it all works correctly. The issue is when I try to generate my own timestamp/nonce/signature in my class.

Here's the method from my class that sends the cURL request:

private function execute($options = array())
{
    if (!isset($options['url'])) {
        return;
    }

    $ch = curl_init($options['url']);

    $method = (isset($options['method'])) ? $options['method'] : 'GET';

    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);

    if (isset($options['auth']) && $options['auth']) {
        $timestamp = round(microtime(true) * 1000);
        $nonce = $this->getNonce(11);
        $version = "1.0";
        $signatureMethod = "HMAC-SHA1";
        $signature = $this->generateSignature($options, $timestamp, $nonce, $signatureMethod, $version);

        $authHeader = "Authorization: OAuth oauth_consumer_key=\"{$this->consumerKey}\",oauth_token=\"{$this->accessToken}\",oauth_signature_method=\"{$signatureMethod}\",oauth_timestamp=\"{$timestamp}\",oauth_nonce=\"{$nonce}\",oauth_version=\"{$version}\",oauth_signature=\"{$signature}\"";

        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            $authHeader,
            "Content-Type: application/json"
        ));
    }

    if (isset($options['body']) && !empty($options['body'])) {
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($options['body']));
    }

    $result = curl_exec($ch);
    curl_close($ch);

    return $result;
}

The getNonce method I copied pretty much directly from https://github.com/BaglerIT/OAuthSimple/blob/master/src/OAuthSimple.php.

Here's the method I've written to generate the signature (which has been cobbled together from various SO posts and other sources):

protected function generateSignature($request, $timestamp, $nonce, $signatureMethod, $version)
{
    $base = $request['method'] . "&" . rawurlencode($request['url']) . "&"
        . rawurlencode("oauth_consumer_key=" . rawurlencode($this->consumerKey)
        . "&oauth_nonce=" . rawurlencode($nonce)
        . "&oauth_signature_method=" . rawurlencode($signatureMethod)
        . "&oauth_timestamp=" . $timestamp
        . "&oauth_version=" . $version);

    $key = rawurlencode($this->consumerSecret) . '&' . rawurlencode($this->tokenSecret);
    $signature = base64_encode(hash_hmac('sha1', $base, $key, true));

    return $signature;
}

I can actually copy and paste the authorization header from Postman into my $authHeader variable, and replace everything except the timestamp/nonce/signature, and it works.

The response I'm getting from their server right now is [code] => 0005 but I can't find anything in their docs about response codes.

Edit: I had missed looking at the response header - the exact error is invalid signature.

like image 414
Charlie Stanard Avatar asked Oct 12 '18 18:10

Charlie Stanard


2 Answers

There are 2 things I changed to get this to work.

  1. I was missing the oauth_token when creating the base string for the signature
  2. According to the OAuth Core 1.0 documentation, "Parameters are sorted by name, using lexicographical byte value ordering."

So I ended up re-ordering the parameters to be alphabetical. Here's what the code for generating the base string ended up looking like:

$base = $request['method'] . "&" . rawurlencode($request['url']) . "&"
        . rawurlencode("oauth_consumer_key=" . rawurlencode($this->consumerKey)
        . "&oauth_nonce=" . rawurlencode($nonce)
        . "&oauth_signature_method=" . rawurlencode($signatureMethod)
        . "&oauth_timestamp=" . rawurlencode($timestamp)
        . "&oauth_token=" . rawurlencode($this->accessToken)
        . "&oauth_version=" . rawurlencode($version));

I also re-ordered the params in the auth header to match the order of the base string:

$authHeader = "Authorization: OAuth oauth_consumer_key=\"{$this->consumerKey}\",oauth_nonce=\"{$nonce}\",oauth_signature_method=\"{$signatureMethod}\",oauth_timestamp=\"{$timestamp}\",oauth_token=\"{$this->accessToken}\",oauth_version=\"{$version}\",oauth_signature=\"{$signature}\"";
like image 162
Charlie Stanard Avatar answered Sep 28 '22 06:09

Charlie Stanard


$base = $request['method']
        . '&' . rawurlencode($request['url'])
        . '&' . rawurlencode('oauth_consumer_key=' . $this->consumerKey)
        . rawurlencode('&oauth_nonce=' . $nonce)
        . rawurlencode('&oauth_signature_method=' . $signatureMethod)
        . rawurlencode('&oauth_timestamp=' . $timestamp)
        . rawurlencode('&oauth_version=' . $version)
        . rawurlencode('&' . http_build_query($data));

$key = rawurlencode($this->consumerSecret) . '&';

$signature = rawurlencode(base64_encode(hash_hmac('SHA1', $base, $key, true)));

If you do a POST, make sure to include your posted data, otherwise the signature will not validate.

CURLOPT_HTTPHEADER => array(
    "authorization: OAuth oauth_consumer_key=\"{$consumerKey}\",oauth_signature_method=\"{$signatureMethod}\",oauth_timestamp=\"{$timestamp}\",oauth_nonce=\"{$nonce}\",oauth_version=\"{$version}\",oauth_signature=\"{$oauthSignature}\"",
    "content-type: application/x-www-form-urlencoded",
  ),

And the header should be as above

like image 32
NYC Avatar answered Sep 28 '22 07:09

NYC