Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I authenticate with OAuth?

I have written a very simple application to update my twitter status on a given condition. I have used the twitter documentation to understand the requirements of creating the OAuth signature and also how to structure the Authorization header. I then send the request with cURL in PHP.

Using the OAuth Tools on the twitter dev site, I compared both my signature base string and authorization header, and both are exactly the same:

Signature Base String

POST&https%3A%2F%2Fapi.twitter.com%2F1%2Fstatuses%2Fupdate.json&oauth_consumer_key%3DYNxxxxxxxxxxxWnfI6HA%26oauth_nonce%3D31077a3c7b7bee4e4c7e2b5185041c12%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1340729904%26oauth_token%3D2991771-4csoiO2fxmWgSxxxxxxxxxxDjWj2AbyxATtiuadNE%26oauth_version%3D1.0%26status%3Dblah%2520test%2520blah.

Authorization header

Authorization: OAuth oauth_consumer_key="YN4FLBxxxxxxxxxxI6HA", oauth_nonce="31077a3c7b7bee4e4c7e2b5185041c12", oauth_signature="M2cXepcxxxxxxxxxxAImeAjE%2FHc%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1340729904", oauth_token="2991771-4cxxxxxxxxxxSmRvjzMoooMDjWj2AbyxATtiuadNE", oauth_version="1.0"

Obviously I've replaced some characters with x to hide my data, but comparing the two character for character yields exactly the same result. For reference, I hard-code the timestamp and nonce that the OAuth Tool generates so that my values can be the same for checking. My access level is set to Read and write. On that same page there is a final example - the command to run with cURL on the command line. When I run this command, it works perfectly and posts to my twitter feed with no issue.

With that in mind I believe everything I've created so far is fine, and don't think there's much point me posting the code that generates the details mentioned previously. However the code that I use to make the call, using cURL, I think is the culprit, but can't tell why:

<?php
// ...
$curl = curl_init();

curl_setopt($curl, CURLOPT_URL, $baseUrl);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_HTTPHEADER, array("Authorization: $header"));
curl_setopt($curl, CURLOPT_POSTFIELDS, array('status' => $status));
$result = json_decode(curl_exec($curl));
curl_close($curl);

var_dump($result);

Note that $baseUrl, $header and $status are the same variables used in generating the signature base string and authorization header, which matched just fine.

The output of the page when run is:

object(stdClass)#1 (2) { ["error"]=> string(34) "Could not authenticate with OAuth." ["request"]=> string(23) "/1/statuses/update.json" }

I hope there are enough details here for someone to point me in the right direction!

like image 458
LeonardChallis Avatar asked Oct 08 '22 10:10

LeonardChallis


2 Answers

After much more searching, testing with apache_request_headers() and sticking to the notion that my data was fine and it was cURL where the problem laid, I realised that cURL was setting the Content-type of the request as multipart/form-data; and adding boundary information, obviously with a longer Content-Length field too. This meant that the status wasn't getting sent correct, I presume because of a malformed multipart/form-data; request.

The solution was to send it as a string. For instance, this works:

curl_setopt($curl, CURLOPT_POSTFIELDS, 'status='. rawurlencode($status));

But I found that there's an even nicer way (especially with multiple values, when I want to use an array):

$postfields = array('status' => $status);
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($postfields));

which looks much nicer IMHO.

like image 143
LeonardChallis Avatar answered Oct 12 '22 11:10

LeonardChallis


I think it's your nonce. From the docs: "The oauth_nonce parameter is a unique token your application should generate for each unique request" (emphasis mine).

Caveat: I'm more familiar with OAuth 2 + Java or JavaScript rather than OAuth 1 + PHP.

If that's not it (or not the only thing), you could compare your actual HTTP request (e.g. using WireShark) to the sample request they document on that page. The note there on "Building the header string" may help too.

like image 35
Will Avatar answered Oct 12 '22 12:10

Will