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!
Related
I am working on a small PHP script which calls an API. This API uses OAuth 1.0 authorization. Hence I need to create the signature and request-header. I am pretty sure my signature-generation method and the generation of the request-header work well as I have done the same in C# (where the everything works) and the signature as well as the header look quite the same.
Generating signature:
function generateSignature($method,$url,$consumerKey,$consumerSecret,$tokenValue,$tokenSecret,$timestamp, $nonce, $signatureMethod, $version)
{
$base = $method . "&" . rawurlencode($url) . "&"
. rawurlencode("oauth_consumer_key=" . rawurlencode($consumerKey)
. "&oauth_nonce=" . rawurlencode($nonce)
. "&oauth_signature_method=" . rawurlencode($signatureMethod)
. "&oauth_timestamp=" . rawurlencode($timestamp)
. "&oauth_token=" . rawurlencode($tokenValue)
. "&oauth_version=" . rawurlencode($version));
$key = rawurlencode($consumerSecret) . '&' . rawurlencode($tokenSecret);
$signature = base64_encode(hash_hmac('sha1', $base, $key, true));
return $signature;
}
Generating authorization header:
$authHeader = "OAuth realm="."\"\","."
oauth_consumer_key=\"{$consumerKey}\",
oauth_nonce=\"{$nonce}\",
oauth_signature_method=\"{$signatureMethod}\",
oauth_timestamp=\"{$timestamp}\",
oauth_token=\"{$tokenValue}\",
oauth_version=\"{$version}\",
oauth_signature=\"{$signature}\"";
I got stuck at the point of passing the authorization header over to the API I am calling.
Setting the authorization:
$header = array('Content-Type: application/x-www-form-urlencoded');
curl_setopt($ch, CURLOPT_HTTPHEADER,$header);
curl_setopt($ch, CURLOPT_POSTFIELDS,urlencode($authHeader));
Finally, when I make the request I get the response, that a parameter is missing.
I've been stuck on this problem for like two days. I've written a Python script which makes a PUT request to AWS Pinpoint service.
Pinpoint like many other AWS services requires a signature authentification on requests, which I managed to handle in Python.
Right now I'm trying to translate my script into a PHP service for Symfony. When I run my first request to AWS pinpoint I get this:
The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
The Canonical String for this request should have been\n'PUT\n/v1/apps/.../endpoints/...\n\ncontent-type:application/json\nhost:pinpoint.eu-west-1.amazonaws.com\nx-amz-content-sha256:de98d86577f0e1...655e6de27154af1c05ab34\nx-amz-date:20191226T151542Z\nx-amz-security-token:IQoJb....\nx-amz-user-agent:aws-amplify/1.1.2 react-native aws-amplify/1.1.2 react-native callback\n\ncontent-type;host;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-user-agent\n0240a9479d0a66d74eaae42dc...95247aaa800fcbe5cf2
The String-to-Sign should have been
'AWS4-HMAC-SHA256\n20191226T151542Z\n20191226/eu-west-1/mobiletargeting/aws4_request\nb2c451534fe370503ecf4068b45c...63e91280cc3187ae3230034107
So I already checked if my Canonical String was wrong, it is the exact same AWS is asking. The String-to-Sign is different by the Canonical String hash.
Here's my headers function
public function create_headers($data,\DateTime $time,$canonical_uri,$method,$to_api=null)
{
$amz_date = $time->format('Ymd\THis\Z');
$date_stamp = $time->format('Ymd');
$payload_hash = hash('sha256',$data);#utf8_encode($data));
$canonical_querystring = "";
$canonical_headers = 'content-type:' . $this->content_type . '\n' . 'host:' . $this->host . '\n' . 'x-amz-content-sha256:' . $payload_hash . '\n' . 'x-amz-date:' . $amz_date . '\n' . 'x-amz-security-token:' . $this->security_token . '\n' . 'x-amz-user-agent:aws-amplify/1.1.2 react-native aws-amplify/1.1.2 react-native callback' . '\n';
$signed_headers = 'content-type;host;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-user-agent';
$canonical_request = $method . '\n' . $canonical_uri . '\n' . $canonical_querystring . '\n' . $canonical_headers . '\n' . $signed_headers . '\n' . $payload_hash;
echo '<br><br>';
print_r(str_replace('\n','<br>',$canonical_request));
#var_dump($canonical_request);
$algorithm = 'AWS4-HMAC-SHA256';
$credential_scope = "{$date_stamp}/{$this->region}/{$this->service}/aws4_request";
#$date_stamp . '/' . $this->region . '/' . $this->service . '/' . 'aws4_request';
#$credential_scope = $this->createScope($date_stamp,$this->region,$this->service);
echo '<br><br>';
#$string_to_sign = $algorithm . '\n' . $amz_date . '\n' . $credential_scope . '\n' . hash('sha256', utf8_encode($canonical_request));
$hash = hash('sha256', $canonical_request);
$string_to_sign = "AWS4-HMAC-SHA256\n{$amz_date}\n{$credential_scope}\n{$hash}";
print_r(str_replace('\n','<br>',$string_to_sign));
echo '<br><br>';
$signing_key = $this->get_signature_key($this->secret_key,$date_stamp,$this->region,$this->service);
$signature = hash_hmac('sha256',$string_to_sign,$signing_key);
$authorization_header = $algorithm . ' ' . 'Credential=' . $this->access_key . '/' . $credential_scope . ', ' . 'SignedHeaders=' . $signed_headers . ', ' . 'Signature=' . $signature;
$headers = array(
'host'=> $this->host,
'content-type'=> $this->content_type,
'x-amz-user-agent'=> 'aws-amplify/1.1.2 react-native aws-amplify/1.1.2 react-native callback',
'x-amz-content-sha256'=> $payload_hash,
'x-amz-security-token'=> $this->security_token,
'x-amz-date'=> $amz_date,
'authorization'=> $authorization_header
);
$this->s->headers = $headers;
return $headers;
}
I've been looking for my error for days but I think I need someone with a fresh eye...
Thank you!
After hours and hours of questionning myself, I've finally found why I was getting that error.
In PHP "\n" and '\n' doesn't have the same meaning.
"\n" is a real line break - which is what AWS is asking.
'\n' is a string of \ and n characters.
AWS API is still pretty shit though.
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 am attempting to access the fat secret API.
https://platform.fatsecret.com/api/
I am attempting to create an Oauth signature using the secret key given by fat secret. When I try access the URL to grab the data I get an error saying my Oauth signature is invalid.
I create the signiture using base64_encode and then hash hmac using sha1.
Here is my code with the secret key blanked out, am I creating the signature in an incorrect way? Or can anyone see where I am going wrong.
Thank you
<?php
$requestTokenUrl = "http://platform.fatsecret.com/rest/server.api";
$oauth_consumer_key = "925f5e1a8b674a70b52eabf15f3265e7";
$nonce = md5(mt_rand());
$outh_signature = '';
$oauthSignatureMethod = "HMAC-SHA1";
$oauthTimestamp = time();
$oauthVersion = "1.0";
$secret_key = "My Secret Key";
$sigKey= $consumer_key . "&";
$sigBase = "method=food.get" . rawurlencode($requestTokenUrl) . "&"
. rawurlencode("oauth_consumer_key=" . rawurlencode($oauth_consumer_key)
. "&oauth_nonce=" . rawurlencode($nonce)
. "&oauth_signature_method=" . rawurlencode($oauthSignatureMethod)
. "&oauth_timestamp=" . $oauthTimestamp
. "&oauth_version=" . $oauthVersion);
$oauthSig = base64_encode(hash_hmac('sha1', $secret_key, true));
$requestUrl = $requestTokenUrl . "?"
. "food_id=33691"
. "&method=food.get"
. "&oauth_consumer_key=" . rawurlencode($oauth_consumer_key)
. "&oauth_nonce=" . rawurlencode($nonce)
. "&oauth_signature=" . rawurlencode($oauthSig)
. "&oauth_signature_method=" . rawurlencode($oauthSignatureMethod)
. "&oauth_timestamp=" . rawurlencode($oauthTimestamp)
. "&oauth_version=" . rawurlencode($oauthVersion);
var_dump($requestUrl);
I have to implement a merchant website where a customers pays a seller for some material goods and a service fee to the website via paypal as described on the picture.
I know that I need to use adaptive payments (parallel or chained) and that the REST API does not support these payments yet (correct me if I'm wrong).
The problem with the classic API (NVP/SOAP) is that during the payment when the webpage redirects the customer to paypal, paypal does not show any item name or description. The other problem is that when the customer logs in no shipping information is shown and no shipping address is sent to the seller after the purchase is made.
Any help is appreciated. Thank you.
Note: The photo shows the desired result which is not achieved in my website.
Note 2: I want to use the shipping address that the user used when he/she set up the paypal account and not send it again when I make API calls from PHP
Thank you!
P.S. Some websites that do this kind of payment, on their redirect url to paypal have https://www.paypal.com/us/cgi-bin/merchantpaymentweb?cmd=_flow&SESSION=.....
You can do Parallel Payment without the Adaptive Payments API.
The Express Checkout API give you the option to add multiple receivers.
If you use the NVP string in your API call, you can do the following to have 2 receivers:
$nvpreq="METHOD=" . $METHOD //required
. "&VERSION=" . $VERSION //required
. "&PWD=" . $PWD //required
. "&USER=" . $USER //required
. "&SIGNATURE=" . $SIGNATURE //required
. "&RETURNURL=" . $RETURNURL //required
. "&CANCELURL=" . $CANCELURL //required
. "&PAYMENTREQUEST_0_DESC=" . $OVERALLDESCRIPTION //optional but best practices
. "&PAYMENTREQUEST_0_PAYMENTACTION=" . $PAYMENTACTION //optional but best practices
. "&PAYMENTREQUEST_0_AMT=" . $TOTALAMT //required = PAYMENTREQUEST_0_ITEMAMT + PAYMENTREQUEST_0_SHIPPINGAMT + PAYMENTREQUEST_0_TAXAMT + PAYMENTREQUEST_0_INURANCEAMT
. "&PAYMENTREQUEST_0_ITEMAMT=" . $TOTALITEMAMT //optional but best practice = (L_PAYMENTREQUEST_0_AMT0 x L_PAYMENTREQUEST_0_QTY0) + (L_PAYMENTREQUEST_0_AMT1 x L_PAYMENTREQUEST_0_QTY1)
. "&PAYMENTREQUEST_0_SHIPPINGAMT=" . $SHIPPINGAMOUNT //optional but best practices
. "&PAYMENTREQUEST_0_CURRENCYCODE=" . $CURRENCYCODE //optional but best practices
. "&PAYMENTREQUEST_0_TAXAMT=" . $TAX //optional but best practices
//1st seller information, specific to parallel payment
. "&PAYMENTREQUEST_0_SELLERPAYPALACCOUNTID=" . $SELLER1
. "&PAYMENTREQUEST_0_PAYMENTREQUESTID=" . $UNIQUESELLERID1
. "&PAYMENTREQUEST_0_NOTIFYURL=" . $IPNURL //optional but best practices
//Line items - (optional)
. "&L_PAYMENTREQUEST_0_NAME0=" . $DESCRIPTION1 //optional but best practices
. "&L_PAYMENTREQUEST_0_DESC0=" . $DESCRIPTION1 //optional but best practices
. "&L_PAYMENTREQUEST_0_AMT0=" . $PAYMENTREQUEST_AMT1 //optional but best practices
. "&L_PAYMENTREQUEST_0_QTY0=" . $PAYMENTQUANTITY1 //optional but best practices
. "&L_PAYMENTREQUEST_0_NAME1=" . $DESCRIPTION2 //optional but best practices
. "&L_PAYMENTREQUEST_0_DESC1=" . $DESCRIPTION2 //optional but best practices
. "&L_PAYMENTREQUEST_0_AMT1=" . $PAYMENTREQUEST_AMT2 //optional but best practices
. "&L_PAYMENTREQUEST_0_QTY1=" . $PAYMENTQUANTITY2 //optional but best practices
. "&L_PAYMENTREQUEST_0_NAME2=" . $DISCOUNTTXT //optional but best practices
. "&L_PAYMENTREQUEST_0_DESC2=" . $DISCOUNTTXT //optional but best practices
. "&L_PAYMENTREQUEST_0_AMT2=" . $DISCOUNTAMT //optional but best practices
. "&L_PAYMENTREQUEST_0_QTY2=" . $DISCOUNTQTY //optional but best practices
//Address information
. "&PAYMENTREQUEST_0_SHIPTONAME=" . $SHIPPINGNAME
. "&PAYMENTREQUEST_0_SHIPTOSTREET=" . $SHIPPINGSTREET
. "&PAYMENTREQUEST_0_SHIPTOSTREET2=" . $SHIPPINGSTREET2
. "&PAYMENTREQUEST_0_SHIPTOCITY=" . $SHIPPINGCITY
. "&PAYMENTREQUEST_0_SHIPTOSTATE=" . $SHIPPINGSTATE
. "&PAYMENTREQUEST_0_SHIPTOZIP=" . $SHIPPINGZIPCODE
. "&PAYMENTREQUEST_0_SHIPTOCOUNTRYCODE=" . $SHIPPINGCOUNTRYCODE
. "&PAYMENTREQUEST_0_SHIPTOPHONENUM=" . $SHIPTOPHONENUM
//2nd seller information
. "&PAYMENTREQUEST_1_DESC=" . $DESCRIPTIONSELLER2
. "&PAYMENTREQUEST_1_PAYMENTACTION=" . $PAYMENTACTIONSELLER2
. "&PAYMENTREQUEST_1_AMT=" . $AMOUNTSELLER2
. "&PAYMENTREQUEST_1_ITEMAMT=" . $AMOUNTSELLER2
. "&PAYMENTREQUEST_1_CURRENCYCODE=" . $CURRENCYCODESELLER2
. "&PAYMENTREQUEST_1_SELLERPAYPALACCOUNTID=" . $SELLER2
. "&PAYMENTREQUEST_1_PAYMENTREQUESTID=" . $UNIQUESELLERID2
. "&L_PAYMENTREQUEST_1_NAME0=" . $DESCRIPTIONSELLER2
. "&L_PAYMENTREQUEST_1_DESC0=" . $DESCRIPTIONSELLER2
. "&L_PAYMENTREQUEST_1_AMT0=" . $AMOUNTSELLER2
. "&L_PAYMENTREQUEST_1_QTY0=" . $QUANTITYSELLER2
. "&L_PAYMENTREQUEST_1_ITEMCATEGORY0=Digital"
//Payment page settings
. "&ALLOWNOTE=" . $ALLOWNOTE
. "&ADDROVERRIDE=" . $ADDROVERRIDE
. "&NOSHIPPING=" . $NOSHIPPING
. "&LOCALECODE=" . $LOCALECODE
. "&LANDINGPAGE=" . $LANDINGPAGE;
As you can see, we have two receivers, but you can add more. For each receiver you can add line items which will appear on the payment page.
In this example I add the line items only for the first receiver. If you want to add the line items for the 2nd receiver, simply add the following to the NVP string:
. "&L_PAYMENTREQUEST_1_NAME0=" . $DESCRIPTION1 //optional but best practices
. "&L_PAYMENTREQUEST_1_DESC0=" . $DESCRIPTION1 //optional but best practices
. "&L_PAYMENTREQUEST_1_AMT0=" . $PAYMENTREQUEST_AMT1 //optional but best practices
. "&L_PAYMENTREQUEST_1_QTY0=" . $PAYMENTQUANTITY1 //optional but best practices
Once the user return back to your website (return url), call the GetExpressCheckout API and store the result in a variable:
$nvpreq="USER=" . $_SESSION['USER']
. "&PWD=" . $_SESSION['PWD']
. "&SIGNATURE=" . $_SESSION['SIGNATURE']
. "&METHOD=GetExpressCheckoutDetails"
. "&VERSION=" . $_SESSION['VERSION']
. "&TOKEN=" . $_SESSION['PP_TOKEN']
;
//Send the NVP string to the PayPal server, store the result in a variable.
$resultECD = ....
Eventually build the NVP string with the GetExpressCheckout result and call the DoExpressCheckoutPayment:
$nvpDoECReq="USER=" . $_SESSION['USER']
. "&PWD=" . $_SESSION['PWD']
. "&SIGNATURE=" . $_SESSION['SIGNATURE']
. "&METHOD=DoExpressCheckoutPayment"
. "&PAYMENTREQUEST_0_PAYMENTACTION=" . $_SESSION['PAYMENT_ACTION']; // payment type
//Build the DoExpressCheckout request
foreach ($resultECD as $i => $value) {
//Ignore some elements of the GetExpressCheckoutDetails answer
if ((!($i == "CHECKOUTSTATUS")) && (!($i == "TIMESTAMP")) && (!($i == "CORRELATIONID")) && (!($i == "ACK")) && (!($i == "BUILD"))) {
$nvpDoECReq = $nvpDoECReq . "&" . urldecode($i) . "=" . urldecode($value);
}
}