I want to generate signature for my custom header to get access tokens from Magento. "oauth_signature_method" is "HMAC-SHA1". I prepared my base string but don't know how to generate the signature. Should I use "hash_hmac($algo,$data,$key)"? If it is the case, what should be the key? Any help would be appreciated.
$consumerSecret = "abcdef";
...
$nonce = generateNonce();
$time = time();
...
$base = "POST&http%3A%2F%2Fmydomain.com%2Fmagento%2Foauth%2Ftoken&".
"oauth_consumer_key%3Dc67eee22b81ac3237cc501aa58fdc236%26".
"oauth_nonce%3D".$nonce."%26".
"oauth_signature_method%3DHMAC-SHA1%26".
"oauth_timestamp%3D".$time."%26".
"oauth_version%3D1.0%26".
"oauth_token%3D".$oauthToken."%26".
"oauth_verifier%3D".$oauthVerifier;
You can find the documentation of OAuth 1.0 here, more precisely for your problem (sub)chapters
According to documentation the Signature Base String is formed as:
HTTP_METHOD&urlencode(BASE_URL_OF_RESOURCE)&urlencode(PARAMETERS_ORDERED_AND_NORMALIZED_AND_WITHOUT_OAUTHSIGNATURE)
The key used for hasing is:urlencode(CONSUMER_SECRET)&urlencode(OAUTH_TOKEN_SECRET)
- when you have a token secreturlencode(CONSUMER_SECRET)&
- when you don't have a token secret (on the very beginning when requesting a "Request Token")
The final signature should be base64_encode(hash_hmac('SHA1', $signatureBaseString, $key, 1));
I put an example script so you can use/test/see how it works:
<?php
// testMagentoRestApi.php file
error_reporting(E_ALL);
ini_set('display_errors', 'On');
session_start();
/**
* @author Bogdan Constantinescu <{first3LettersFromFirstname}_{first3LettersFromLastname}{at}yahoo{dot}com>
* @license The MIT License (http://opensource.org/licenses/MIT)
*/
class MagentoRestApi
{
/**
* @var const int Curl connect timeout.
*/
const TIMEOUT = 20;
/**
* @var string Application 's key.
*/
private $appKey;
/**
* @var string Application 's secret.
*/
private $appSecret;
/**
* @var resource The curl resource.
*/
protected $curl;
/**
* @var string Base Magento url.
*/
protected $baseMagentoUrl;
/**
* @var string Consumer 's callback url.
*/
protected $callbackUrl;
/**
* Constructor; initializes stuffs...
* @param string $strBaseMagentoUrl Magento 's base url.
* @param string $strAppKey API application key.
* @param string $strAppSecret API application secret.
* @param string $strCallbackUrl Consumer's callback url.
* @throws Exception If params are not ok / curl could not be initialized.
*/
public function __construct($strBaseMagentoUrl, $strAppKey, $strAppSecret, $strCallbackUrl)
{
// check params
if(!filter_var($strBaseMagentoUrl, FILTER_VALIDATE_URL)) {
throw new Exception('Invalid param base magento url.');
}
if (!is_string($strAppKey) || !mb_strlen($strAppKey)) {
throw new Exception('Invalid param app key.');
}
if (!is_string($strAppSecret) || !mb_strlen($strAppSecret)) {
throw new Exception('Invalid param app secret.');
}
if(!filter_var($strCallbackUrl, FILTER_VALIDATE_URL)) {
throw new Exception('Invalid param callback url.');
}
$this->baseMagentoUrl = trim($strBaseMagentoUrl, '/');
$this->callbackUrl = $strCallbackUrl;
$this->appKey = $strAppKey;
$this->appSecret = $strAppSecret;
if (!extension_loaded('curl')) {
throw new Exception('cURL extension is not enabled.');
}
$this->curl = curl_init();
if (false === $this->curl) {
throw new Exception('cURL could not be initialized.');
}
}
/**
* Makes api call.
* @param string $strUrl Api call url.
* @param string $strMethod Request method (POST|GET|DELETE|PUT...)
* @param array $arrHeaders Optional request headers.
* @param string $strPostData Request body.
* @param boolean $blnSuprimeResponseHeader Whether to suprime response 's headers or not.
* @return string Api call response.
* @throws Exception If smth went wrong.
*/
protected function makeApiCall(
$strUrl,
$strMethod = 'GET',
array $arrHeaders = array(),
$strPostData = '',
$blnSuprimeResponseHeader = false,
&$intStatus = null
) {
curl_setopt($this->curl, CURLOPT_URL, $strUrl);
curl_setopt($this->curl, CURLOPT_HTTPHEADER, $arrHeaders);
curl_setopt($this->curl, CURLOPT_TIMEOUT, self::TIMEOUT);
curl_setopt($this->curl, CURLOPT_CONNECTTIMEOUT, self::TIMEOUT);
curl_setopt($this->curl, CURLOPT_MAXREDIRS, 3);
curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($this->curl, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $strMethod);
curl_setopt($this->curl, CURLOPT_POSTFIELDS, $strPostData);
curl_setopt($this->curl, CURLOPT_HEADER, !$blnSuprimeResponseHeader);
curl_setopt($this->curl, CURLOPT_USERAGENT, 'Magento REST API Client');
curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, 0);
$mxdResponse = curl_exec($this->curl);
if (false === $mxdResponse) {
throw new Exception(curl_error($this->curl), curl_errno($this->curl));
}
$intStatus = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
// var_dump($mxdResponse);
return $mxdResponse;
}
/**
* Retrieve request token.
* @return array With keys 'oauth_token' & 'oauth_token_secret'
* @throws Exception If request did not succeed/smth went bad
*/
public function getRequestToken()
{
$returnValue = array();
// define params that will be used either in Authorization header, or as url query params, excluding 'oauth_signature'
$params = array(
'oauth_callback' => $this->callbackUrl,
'oauth_consumer_key' => $this->appKey,
'oauth_nonce' => uniqid(mt_rand(1, 1000)),
'oauth_signature_method' => 'HMAC-SHA1',
'oauth_timestamp' => time(),
'oauth_version' => '1.0',
);
// define HTTP method
$method = 'POST';
// this is the url to get Request Token according to Magento doc
$url = $this->baseMagentoUrl . '/oauth/initiate?oauth_callback=' . urlencode($params['oauth_callback']);
// start making the signature
ksort($params); // @see Zend_Oauth_Signature_SignatureAbstract::_toByteValueOrderedQueryString() for more accurate sorting, including array params
$sortedParamsByKeyEncodedForm = array();
foreach ($params as $key => $value) {
$sortedParamsByKeyEncodedForm[] = rawurlencode($key) . '=' . rawurlencode($value);
}
$strParams = implode('&', $sortedParamsByKeyEncodedForm);
$signatureData = strtoupper($method) // HTTP method (POST/GET/PUT/...)
. '&'
. rawurlencode($this->baseMagentoUrl . '/oauth/initiate') // base resource url - without port & query params & anchors, @see how Zend extracts it in Zend_Oauth_Signature_SignatureAbstract::normaliseBaseSignatureUrl()
. '&'
. rawurlencode($strParams);
$key = rawurlencode($this->appSecret) . '&'; // on later requests, when you also have a oauth_token_secret from this request append it to $key ( eq: $key = rawurlencode($this->appSecret) . '&' . rawurlencode($someOauthTokenSecret); )
$signature = base64_encode(hash_hmac('SHA1', $signatureData, $key, 1));
// end making signature
$responseStatusCode = 0;
$response = $this->makeApiCall(
$url,
$method,
array(
'Authorization: OAuth '
. 'oauth_callback="' . rawurlencode($params['oauth_callback']) . '",'
. 'oauth_consumer_key="' . $params['oauth_consumer_key'] . '",'
. 'oauth_nonce="' . $params['oauth_nonce'] . '",'
. 'oauth_signature_method="' . $params['oauth_signature_method'] . '",'
. 'oauth_signature="' . rawurlencode($signature) . '",'
. 'oauth_timestamp="' . $params['oauth_timestamp'] . '",'
. 'oauth_version="' . $params['oauth_version'] . '"'
),
'',
true,
$responseStatusCode
);
if (200 == $responseStatusCode) {
parse_str($response, $returnValue);
} else {
throw new Exception('Response HTTP code != 200, but ' . $responseStatusCode);
}
return $returnValue;
}
/**
* Retrieve access token.
* @param string $strOauthToken token from "Request Token".
* @param string $strOauthTokenSecret token secret from "Request Token".
* @param string $strOauthVerifier verifier returened after user authorization.
* @return array With keys 'oauth_token' & 'oauth_token_secret'
* @throws Exception If request did not succeed/smth went bad
*/
public function getAccessToken($strOauthToken, $strOauthTokenSecret, $strOauthVerifier)
{
$returnValue = array();
// define params that will be used either in Authorization header, or as url query params, excluding 'oauth_signature'
$params = array(
'oauth_consumer_key' => $this->appKey,
'oauth_nonce' => uniqid(mt_rand(1, 1000)),
'oauth_signature_method' => 'HMAC-SHA1',
'oauth_timestamp' => time(),
'oauth_version' => '1.0',
'oauth_token' => $strOauthToken,
'oauth_verifier' => $strOauthVerifier,
);
// define HTTP method
$method = 'POST';
// this is the url to get Request Token according to Magento doc
$url = $this->baseMagentoUrl . '/oauth/token';
// start making the signature
ksort($params); // @see Zend_Oauth_Signature_SignatureAbstract::_toByteValueOrderedQueryString() for more accurate sorting, including array params
$sortedParamsByKeyEncodedForm = array();
foreach ($params as $key => $value) {
$sortedParamsByKeyEncodedForm[] = rawurlencode($key) . '=' . rawurlencode($value);
}
$strParams = implode('&', $sortedParamsByKeyEncodedForm);
$signatureData = strtoupper($method) // HTTP method (POST/GET/PUT/...)
. '&'
. rawurlencode($url) // base resource url - without port & query params & anchors, @see how Zend extracts it in Zend_Oauth_Signature_SignatureAbstract::normaliseBaseSignatureUrl()
. '&'
. rawurlencode($strParams);
$key = rawurlencode($this->appSecret) . '&' . rawurlencode($strOauthTokenSecret);
$signature = base64_encode(hash_hmac('SHA1', $signatureData, $key, 1));
// end making signature
$responseStatusCode = 0;
$response = $this->makeApiCall(
$url,
$method,
array(
'Authorization: OAuth '
. 'oauth_consumer_key="' . $params['oauth_consumer_key'] . '",'
. 'oauth_nonce="' . $params['oauth_nonce'] . '",'
. 'oauth_signature_method="' . $params['oauth_signature_method'] . '",'
. 'oauth_signature="' . rawurlencode($signature) . '",'
. 'oauth_timestamp="' . $params['oauth_timestamp'] . '",'
. 'oauth_version="' . $params['oauth_version'] . '",'
. 'oauth_token="' . rawurlencode($params['oauth_token']) . '",'
. 'oauth_verifier="' . rawurlencode($params['oauth_verifier']) . '"'
),
'',
true,
$responseStatusCode
);
if (200 == $responseStatusCode) {
parse_str($response, $returnValue);
} else {
throw new Exception('Response HTTP code != 200, but ' . $responseStatusCode);
}
return $returnValue;
}
/**
* Destructor. Free resources.
*/
public function __destruct() {
if (is_resource($this->curl)) {
curl_close($this->curl);
}
}
}
// change the following variables accordingly to your needs
$callbackUrl = 'http://localhost/testMagentoRestApi.php';
$baseMagentoUrl = 'http://www.magento.dev/CE_1.9.1.0';
$consumerKey = 'e93a5396269851aaddaabd86999bafcb';
$consumerSecret = 'bfb0c10cf75f0b66f71186d806616807';
$client = new MagentoRestApi($baseMagentoUrl, $consumerKey, $consumerSecret, $callbackUrl);
if (!isset($_SESSION['request_token']) && !isset($_GET['oauth_token'])) { // retrieve "Request Token" and Authorize user
$_SESSION['request_token'] = $client->getRequestToken();
header('Location: ' . $baseMagentoUrl . '/oauth/authorize?oauth_token=' . urlencode($_SESSION['request_token']['oauth_token']));
exit;
} elseif (isset($_SESSION['request_token']) && isset($_GET['oauth_token']) && isset($_GET['oauth_verifier'])) { // user authorized and redirected here, now request access token (step E from http://oauth.net/core/diagram.png)
$_SESSION['access_token'] = $client->getAccessToken($_GET['oauth_token'], $_SESSION['request_token']['oauth_token_secret'], $_GET['oauth_verifier']);
var_dump($_SESSION['access_token']);
}
Also you can do some reverse engineering in magento. For example when requesting an "Request Token" the flow is:
Mage_Oauth_InitiateController::indexAction()
Mage_Oauth_Model_Server::initiateToken()
Mage_Oauth_Model_Server::_processRequest()
Mage_Oauth_Model_Server::_validateSignature()
Zend_Oauth_Http_Utility::sign()
Zend_Oauth_Signature_Hmac::sign()
Zend_Oauth_Signature_SignatureAbstract::_getBaseSignatureString()
Around the last 3 classes all the magic happens. You can check them out.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With