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.
There are 2 things I changed to get this to work.
I was missing the oauth_token when creating the base string for the signature
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}\"";
$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
This fixed version worked for me :
function generateOauthSignature($method, $url, $consumerKey, $nonce, $signatureMethod, $timestamp, $version, $consumerSecret, $tokenSecret, $tokenValue, $extraParams = array())
{
$base = strtoupper($method) . "&" . rawurlencode($url) . "&"
. rawurlencode("oauth_consumer_key=" . $consumerKey
. "&oauth_nonce=" . $nonce
. "&oauth_signature_method=" . $signatureMethod
. "&oauth_timestamp=" . $timestamp
. "&oauth_token=" . $tokenValue
. "&oauth_version=" . $version);
if (!empty($extraParams)) {
$base .= rawurlencode("&" . http_build_query($extraParams));
}
$key = rawurlencode($consumerSecret) . '&' . rawurlencode($tokenSecret);
$signature = base64_encode(hash_hmac('sha1', $base, $key, true));
return rawurlencode($signature);
}
The following twitter thread helped me : https://twittercommunity.com/t/how-to-generate-oauth-signature-when-post-json-body-in-php/87581
I was also struggling with the proper setup of the OAuth 1 signature and had a lot of failed attempts. After TGA's hint to have a look how it's done with Twitter, I found out that there is an existing class which may be used out-of-the-box:
TwitterAPIExchange.php from the repository https://github.com/J7mbo/twitter-api-php.
Even if is called "Twitter...", it may also be used for other OAuth1 APIs. Calls will look like this:
$settings = array(
'oauth_access_token' => TOKEN,
'oauth_access_token_secret' => TOKEN_SECRET,
'consumer_key' => CONSUMER_KEY,
'consumer_secret' => CONSUMER_SECRET
);
$url = "https://api-url.com/api/v4/users/0451432/";
$requestMethod = 'POST';
$postfields = array(
'groupIds' => '23,24,25',
);
$twitter = new TwitterAPIExchange($settings);
return $twitter->buildOauth($url, $requestMethod)
->setPostfields($postfields)
->performRequest();
It works perfect for me.
This version matches the OAuth PECL library's function so you no longer need it.
public static function oauth_get_sbs(
$requestMethod,
$requestURL,
$request_parameters
): string
{
return $requestMethod . "&" . rawurlencode($requestURL) . "&"
. rawurlencode("oauth_consumer_key=" . rawurlencode($request_parameters['oauth_consumer_key'])
. "&oauth_nonce=" . rawurlencode($request_parameters['oauth_nonce'])
. "&oauth_signature_method=" . rawurlencode($request_parameters['oauth_signature_method'])
. "&oauth_timestamp=" . $request_parameters['oauth_timestamp']
. "&oauth_token=" . $request_parameters['oauth_token']
. "&oauth_version=" . $request_parameters['oauth_version']);
}
I am trying this sample code from this link. Currently, I can authorize but after i authorize the app, its showing me
Your session has timed out
What error is this actually? I tried searching in the internet but i couldnt found any solution for this. I just copied the full code and change the API key to my app API key.
This is my php code:
<?php
// Initiate the authorization process by sending a signed request to Twitter
// for a Request Token and then redirect the user to Twitter to authorize the
// application.
require "../include/oauthvalues.php";
session_start();
$oauthTimestamp = time();
$nonce = md5(mt_rand());
echo time();
// prepare the signature string
$sigBase = "GET&" . rawurlencode($requestTokenUrl) . "&"
. rawurlencode("oauth_consumer_key=" . rawurlencode($consumerKey)
. "&oauth_nonce=" . rawurlencode($nonce)
. "&oauth_signature_method=" . rawurlencode($oauthSignatureMethod)
. "&oauth_timestamp=" . $oauthTimestamp
. "&oauth_version=" . $oauthVersion);
// prepare the signature key
$sigKey = $consumerSecret . "&";
$oauthSig = base64_encode(hash_hmac("sha1", $sigBase, $sigKey, true));
// send the signed request and receive Twitter's response
$requestUrl = $requestTokenUrl . "?"
. "oauth_consumer_key=" . rawurlencode($consumerKey)
. "&oauth_nonce=" . rawurlencode($nonce)
. "&oauth_signature_method=" . rawurlencode($oauthSignatureMethod)
. "&oauth_timestamp=" . rawurlencode($oauthTimestamp)
. "&oauth_version=" . rawurlencode($oauthVersion)
. "&oauth_signature=" . rawurlencode($oauthSig);
$response = file_get_contents($requestUrl);
// extract and preserve the request tokens
parse_str($response, $values);
$_SESSION["requestToken"] = $values["oauth_token"];
$_SESSION["requestTokenSecret"] = $values["oauth_token_secret"];
// send the user to Twitter to grant authorization
$redirectUrl = $authorizeUrl . "?oauth_token=" . $_SESSION["requestToken"];
header("Location: " . $redirectUrl);
I'm trying to create a script that uses the twitter streaming api to keep a tab on tweets with a certain hash-tag.
Whenever I try creating a request though, I always get a 401 Unauthorized return. Can anyone help me figure out what I'm doing incorrectly?
I tried to follow twitter's api to the dot, but apparently I'm doing something wrong. The code I have is below.
$base_url_string = urlencode($base_url);
$parameter_string = urlencode('oauth_consumer_key') . '=' . urlencode($consumer_key) . '&'
. urlencode('oauth_nonce') . '=' . urlencode($nonce) . '&'
. urlencode('oauth_signature_method') . '=' . urlencode($signature_method) . '&'
. urlencode('oauth_timestamp') . '=' . urlencode($timestamp) . '&'
. urlencode('oauth_token') . '=' . urlencode($token) . '&'
. urlencode('oauth_version') . '=' . urlencode($version) . '&'
. urlencode('track') . '=' . urlencode('#kitten');
$signature_base_string = $method . "&" . $base_url_string . "&" . urlencode($parameter_string);
$signature = base64_encode(hash_hmac('sha1', $signature_base_string, $secret, true));
$fp = fsockopen("ssl://stream.twitter.com", 443, $errno, $errstr, 30);
if (!$fp) {
print "$errstr ($errno)\n";
} else {
$request = $method . " /1.1/statuses/filter.json?" . urlencode("track") . "=" . urlencode("#kitten") . " HTTP/1.1\r\n";
$request .= "Host: stream.twitter.com\r\n";
$request .= 'Authorization: OAuth'
. ' oauth_consumer_key="' . $consumer_key . '",'
. ' oauth_nonce="' . $nonce . '",'
. ' oauth_signature="' . $signature . '",'
. ' oauth_signature_method="' . $signature_method . '",'
. ' oauth_timestamp="' . $timestamp . '",'
. ' oauth_token="' . $token . '",'
. ' oauth_version="' . $version . '"'
. "\r\n\r\n";
print $request . "</br>";
fwrite($fp, $request);
while (!feof($fp)) {
$json = fgets($fp);
echo $json;
$data = json_decode($json, true);
if ($data) {
print $data;
}
}
print 'exiting...';
fclose($fp);
}
Ok so I figured this all out. It was a combination of poor string manipulation and not Percent Encoding everything. Both the keys and values of the OAuth string must be Percent encoded. I had done that in the signature, but not in the actual request. Once I fixed that, the request got through and I was connected to the Twitter Streaming API.
So I'm putting together a quick and dirty app to add new posts automatically to Tumblr based on new items pulled from an RSS feed. The app is in Codeigniter, and so far I've managed to obtain the request credentials and sent the user to Tumblr for authorisation. The problem is, once they're redirected back to the app and I make a request for the access credentials, I'm getting the following error:
Message: file_get_contents(http://www.tumblr.com/oauth/access_token?oauth_consumer_key=THECONSUMERKEY&oauth_nonce=9362afdd34f9ce1601fb9cf505ffa3cf&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1327440390&oauth_token=09mFsxCvsODDmSxPCyQNu4QKFMMXaAEEyPtBibPUyUTE1n2BsJ&oauth_verifier=hDfGgesf9EKIO5yFhiHxtnsbP42XEP1FISY2qyyWerzXf6fPTG&oauth_version=1.0&oauth_signature=yeFw8ACvVvKtD%2BQ%2FdzbLivDSm1Y%3D) [function.file-get-contents]: failed to open stream: HTTP request failed! HTTP/1.1 401 Unauthorized
Here's the code I'm using:
$oauthVerifier = $_GET["oauth_verifier"];
$sigBase = "GET&" . rawurlencode($this->accessTokenUrl) . "&"
. rawurlencode("oauth_consumer_key=" . rawurlencode($this->consumerKey)
. "&oauth_nonce=" . rawurlencode($this->nonce)
. "&oauth_signature_method=" . rawurlencode($this->oauthSignatureMethod)
. "&oauth_timestamp=" . $this->oauthTimestamp
. "&oauth_token=" . rawurlencode($this->CI->session->userdata('requestToken'))
. "&oauth_verifier=" . rawurlencode($oauthVerifier)
. "&oauth_version=" . $this->oauthVersion);
$sigKey = $this->consumerSecret . "&";
$oauthSig = base64_encode(hash_hmac("sha1", $sigBase, $sigKey, true));
$requestUrl = $this->accessTokenUrl . "?"
. "oauth_consumer_key=" . rawurlencode($this->consumerKey)
. "&oauth_nonce=" . rawurlencode($this->nonce)
. "&oauth_signature_method=" . rawurlencode($this->oauthSignatureMethod)
. "&oauth_timestamp=" . rawurlencode($this->oauthTimestamp)
. "&oauth_token=" . rawurlencode($this->CI->session->userdata('requestToken'))
. "&oauth_verifier=" . rawurlencode($oauthVerifier)
. "&oauth_version=" . rawurlencode($this->oauthVersion)
. "&oauth_signature=" . rawurlencode($oauthSig);
$response = file_get_contents($requestUrl);
Any bright ideas?
This is incomplete and needs some bug chasing by you.
Do understand the relevant part of the conversation:
TumApp to user: please go to Tumblr with one of my request tokens: GHF3F4F
user to Tumblr: I authorize TumApp, and here is it's request token: GHF3F4F
Tumblr to itself: let me authorize GHF3F4F, which TumApp can exchange for an access token
to user: redirect to Tumapp's callback
Tumapp to itself: Great, user authorized me (by requesting my callback). Let me exchange my request token for an access token
to Tumblr: Hey, give me an access token for GHF3F4F
Tumblr to Tumapp: Epic 401 fail!
Here are the reasons why Tumblr would give a 401
From http://oauth.net/core/1.0a/#http_codes
HTTP 401 Unauthorized
Invalid Consumer Key
Invalid / expired Token
Invalid signature
Invalid / used nonce
I would deduce starting with the token; the consumer key successfully got you a request token and the signature and nonce are generated by the library.
UPDATE: The stray ampersand was actually not in the original code (my bad), but something I did afterwards when trying to fix it. In fact, the cause of the original problem was the absence of the Request Token Secret in the signature key.
Thanks for the pointers, aitchnyu - in the end, the problem was a stray ampersand. In the signature base, I wrapped the ampersand before "oauth_consumer_key=" in a rawurlencode, when it should have come before it.
Here's the old sig base:
"GET&" . rawurlencode($this->accessTokenUrl)
. rawurlencode("&oauth_consumer_key=" . rawurlencode($this->consumerKey)
. "&oauth_nonce=" . rawurlencode($this->nonce)
. "&oauth_signature_method=" . rawurlencode($this->oauthSignatureMethod)
. "&oauth_timestamp=" . rawurlencode($this->time)
. "&oauth_token=" . rawurlencode($token)
. "&oauth_verifier=" . rawurlencode($oauthVerifier)
. "&oauth_version=" . $this->oauthVersion);
The new one:
"GET&" . rawurlencode($this->accessTokenUrl) . "&"
. rawurlencode("oauth_consumer_key=" . rawurlencode($this->consumerKey)
. "&oauth_nonce=" . rawurlencode($this->nonce)
. "&oauth_signature_method=" . rawurlencode($this->oauthSignatureMethod)
. "&oauth_timestamp=" . rawurlencode($this->time)
. "&oauth_token=" . rawurlencode($token)
. "&oauth_verifier=" . rawurlencode($oauthVerifier)
. "&oauth_version=" . $this->oauthVersion);
Vital difference!
I am facing some problem in generating oauth_signature for flickr api. Can you please look into this and advise me what I am doing wrong?
// p.s. I am sharing my Flickr key and secret as I will change them when I will start production development.
Code
/* PHP code */
$NONCE=base64_decode(rand());
$TIMESTAMP= gmdate('U');
$SECRET="39b4f5fd592ede81";
$KEY="1bab082052d7cf8b3aa9e2bc92882ac0";
$CONSUMER_SECRET= $SECRET. "&";
$url_1 = "http://www.flickr.com/services/oauth/request_token?";
$url_1 = urlencode($url_1);
$url_2 = "oauth_callback=http%3A%2F%2Flocalhost%2FFlickr%2Flogin.php&oauth_consumer_key=". $KEY;
$url_2 .="&oauth_nonce=". $NONCE. "&oauth_signature_method=HMAC-SHA1&oauth_timestamp=". $TIMESTAMP. "&oauth_version=1.0";
// generate signature
$BASE_STRING ="";
$BASE_STRING .= "GET&". urlencode($url_1). urlencode($url_2);
$API_SIG= base64_encode(hash_hmac("sha1",$BASE_STRING,$CONSUMER_SECRET, true) );
// url generate
$url="http://www.flickr.com/services/oauth/request_token?oauth_callback=http://localhost/Flickr/login.php&oauth_consumer_key=". $KEY;
$url.="&oauth_nonce=". $NONCE. "&oauth_timestamp=". $TIMESTAMP. "&oauth_signature_method=HMAC-SHA1&oauth_version=1.0&oauth_signature=". $API_SIG;
// calling
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_REFERER, "http://www.example.org/yay.htm");
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla Firefox/3.0");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data= curl_exec($ch);
echo $data;
curl_close($ch);
This worked for me, hopefully it helps someone else...
<?php
$consumerKey = 'your_Flickr_key';
$consumerSecret = 'your_Flickr_secret';
$requestTokenUrl = "https://www.flickr.com/services/oauth/request_token";
$oauthTimestamp = time();
$nonce = md5(mt_rand());
$oauthSignatureMethod = "HMAC-SHA1";
$oauthVersion = "1.0";
$sigBase = "GET&" . rawurlencode($requestTokenUrl) . "&"
. rawurlencode("oauth_consumer_key=" . rawurlencode($consumerKey)
. "&oauth_nonce=" . rawurlencode($nonce)
. "&oauth_signature_method=" . rawurlencode($oauthSignatureMethod)
. "&oauth_timestamp=" . $oauthTimestamp
. "&oauth_version=" . $oauthVersion);
$sigKey = $consumerSecret . "&";
$oauthSig = base64_encode(hash_hmac("sha1", $sigBase, $sigKey, true));
$requestUrl = $requestTokenUrl . "?"
. "oauth_consumer_key=" . rawurlencode($consumerKey)
. "&oauth_nonce=" . rawurlencode($nonce)
. "&oauth_signature_method=" . rawurlencode($oauthSignatureMethod)
. "&oauth_timestamp=" . rawurlencode($oauthTimestamp)
. "&oauth_version=" . rawurlencode($oauthVersion)
. "&oauth_signature=" . rawurlencode($oauthSig);
$response = file_get_contents($requestUrl);
var_export($response);
I am answering my own question. Thanks to Sam Judson: http://www.wackylabs.net
I removed base64_decode() from generating random numbers and it just did worked.