REST API has been released in february to set blob CORS property, but this hasn't been implemented for NodeJS yet.
Since I need this feature, I tried to implement it in a module for my azure website running NodeJS.
Based on REST API documentation to change CORS properties and to generate authentification key, on this implementation of authentification key generation using NodeJS, I tried to follow the accepted answer from this post, but it didn't work for me.
Here is what I've got in setcrosproperties.js :
var crypto = require('crypto');
var request = require('request');
exports.setCors = function (MY_ACCOUNT_URL, MY_ACCOUNT_NAME, MY_ACCOUNT_HOST, accountKey) {
var MY_CORS_XML =
'<?xml version="1.0" encoding="utf-8"?>'+
'<StorageServiceProperties>'+
'<Cors>'+
'<CorsRule>'+
'<AllowedOrigins>*</AllowedOrigins>'+
'<AllowedMethods>GET,PUT</AllowedMethods>'+
'<MaxAgeInSeconds>500</MaxAgeInSeconds>'+
'<ExposedHeaders>x-ms-meta-data*,x-ms-meta-customheader</ExposedHeaders>'+
'<AllowedHeaders>x-ms-meta-target*,x-ms-meta-customheader</AllowedHeaders>'+
'</CorsRule>'+
'</Cors>'+
'<DefaultServiceVersion>2013-08-15</DefaultServiceVersion>'+
'</StorageServiceProperties>';
var url = MY_ACCOUNT_URL + '/?restype=service&comp=properties';
var canonicalizedResource = '/' + MY_ACCOUNT_NAME + '/?comp=properties';
var corsMD5 = crypto.createHash('md5' ).update(MY_CORS_XML).digest('base64');
var date = (new Date()).toUTCString();
var headers = {
'x-ms-version': '2013-08-15',
'x-ms-date': date,
'Host': MY_ACCOUNT_HOST
};
var canonicalizedHeaders = buildCanonicalizedHeaders( headers );
// THIS
var key = buildSharedKeyLite( 'PUT', corsMD5, 'text/plain; charset=UTF-8', canonicalizedHeaders, canonicalizedResource, accountKey);
// AND THIS, BOTH YIELD THE SAME SERVER RESPONSE
// var key = buildSharedKeyLite( 'PUT', "", "", canonicalizedHeaders, canonicalizedResource, accountKey);
headers['Authorization'] = 'SharedKeyLite ' + MY_ACCOUNT_NAME + ':' + key;
var options = {
url: url,
body: MY_CORS_XML,
headers: headers
};
console.log("url : " + url);
console.log("canonicalizedResource : " + canonicalizedResource);
console.log("canonicalizedHeaders : " + canonicalizedHeaders);
console.log("corsMD5 : " + corsMD5);
console.log("key : " + key);
console.log("options : " + JSON.stringify(options));
function onPropertiesSet(error, response, body) {
if (!error && response.statusCode == 202) {
console.log("CORS: OK");
}
else {
console.log("CORS: " + response.statusCode);
console.log("body : " + body);
}
}
request.put(options, onPropertiesSet); // require('request')
};
function buildCanonicalizedHeaders( headers ) {
var xmsHeaders = [];
var canHeaders = "";
for ( var name in headers ) {
if ( name.indexOf('x-ms-') == 0 ) {
xmsHeaders.push( name );
}
}
xmsHeaders.sort();
for ( var i = 0; i < xmsHeaders.length; i++ ) {
name = xmsHeaders[i];
canHeaders = canHeaders + name.toLowerCase().trim() + ':' + headers[name] + '\n';
}
return canHeaders;
}
function buildSharedKeyLite( verb, contentMD5, contentType, canonicalizedHeaders, canonicalizedResource, accountKey) {
var stringToSign = verb + "\n" +
contentMD5 + "\n" +
contentType + "\n" +
"" + "\n" + // date is to be empty because we use x-ms-date
canonicalizedHeaders +
canonicalizedResource;
// return crypto.createHmac('sha256', accountKey).update(encodeURIComponent(stringToSign)).digest('base64');
return crypto.createHmac('sha256', new Buffer(accountKey, 'base64')).update(stringToSign).digest('base64');
}
And here is how I call this function from my server.js file :
var setcrosproperties = require('./setcrosproperties.js');
// setCors(MY_ACCOUNT_URL, MY_ACCOUNT_NAME, MY_ACCOUNT_HOST, accountKey)
setcrosproperties.setCors(
'https://'+process.env['AZURE_STORAGE_ACCOUNT']+'.blob.core.windows.net',
process.env['AZURE_STORAGE_ACCOUNT'],
process.env['AZURE_STORAGE_ACCOUNT']+'.blob.core.windows.net',
process.env['AZURE_STORAGE_ACCESS_KEY']);
I did not understand what was the difference intended with variables MY_ACCOUNT_UTL (I assumed URL) and MY_ACCOUNT_HOST, so I use the same value for both parameters of the function.
(I removed the "cors" parameter, which seemed to be unused.)
Here is what I get in the console :
url : https://NAME_OF_MY_STORAGE_ACCOUNT.blob.core.windows.net/?restype=service&comp=properties
canonicalizedResource : /NAME_OF_MY_STORAGE_ACCOUNT/?comp=properties
canonicalizedHeaders : x-ms-date:Sun, 09 Mar 2014 12:33:41 GMT
x-ms-version:2013-08-15
corsMD5 : +ij...w==
key : sNB...JrY=
options : {"url":"https://NAME_OF_MY_STORAGE_ACCOUNT.blob.core.windows.net/?restype=service&comp=properties","body":"GET,PUT500x-ms-meta-data,x-ms-meta-customheaderx-ms-meta-target*,x-ms-meta-customheader2013-08-15","headers":{"x-ms-version":"2013-08-15","x-ms-date":"Sun, 09 Mar 2014 12:33:41 GMT","Host":"NAME_OF_MY_STORAGE_ACCOUNT.blob.core.windows.net","Authorization":"SharedKeyLite NAME_OF_MY_STORAGE_ACCOUNT:sNB...rY="}}
CORS: 403
body : AuthenticationFailedServer failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
RequestId:1e6abfe3-e0e8-4b9c-922d-7cb34485eec9
Time:2014-03-09T12:33:41.7262308ZThe MAC signature found in the HTTP request 'sNB...JrY=' is not the same as any computed signature. Server used following string to sign: 'PUT
x-ms-date:Sun, 09 Mar 2014 12:33:41 GMT
x-ms-version:2013-08-15
/NAME_OF_MY_STORAGE_ACCOUNT/?comp=properties'.
Any idea about what I am doing wrong here? Thanks for your help
To configure CORS, use the Azure storage library for Node.js.
You can do npm install azure-storage to get it. Source code is at https://github.com/Azure/azure-storage-node.
The one that come with npm package azure (a.k.a. azure-sdk-for-node) use older azure-storage-legacy package, which does not support CORS.
You can set the CORS with the following code:
var service = azure.createBlobService();
var serviceProperties = {
Cors: {
CorsRule: [{
AllowedOrigins: ['*'],
AllowedMethods: ['GET'],
AllowedHeaders: [],
ExposedHeaders: [],
MaxAgeInSeconds: 60
}]
}
};
service.setServiceProperties(serviceProperties, callback);
Please add Content-Type and Content-MD5 in your headers array and that should do the trick. Here's the modified code:
var crypto = require('crypto');
var request = require('request');
exports.setCors = function (MY_ACCOUNT_URL, MY_ACCOUNT_NAME, MY_ACCOUNT_HOST, accountKey) {
var MY_CORS_XML =
'<?xml version="1.0" encoding="utf-8"?>'+
'<StorageServiceProperties>'+
'<Cors>'+
'<CorsRule>'+
'<AllowedOrigins>*</AllowedOrigins>'+
'<AllowedMethods>GET,PUT</AllowedMethods>'+
'<MaxAgeInSeconds>500</MaxAgeInSeconds>'+
'<ExposedHeaders>x-ms-meta-data*,x-ms-meta-customheader</ExposedHeaders>'+
'<AllowedHeaders>x-ms-meta-target*,x-ms-meta-customheader</AllowedHeaders>'+
'</CorsRule>'+
'</Cors>'+
'<DefaultServiceVersion>2013-08-15</DefaultServiceVersion>'+
'</StorageServiceProperties>';
var url = MY_ACCOUNT_URL + '/?restype=service&comp=properties';
var canonicalizedResource = '/' + MY_ACCOUNT_NAME + '/?comp=properties';
var corsMD5 = crypto.createHash('md5' ).update(MY_CORS_XML).digest('base64');
var date = (new Date()).toUTCString();
var headers = {
'x-ms-version': '2013-08-15',
'x-ms-date': date,
'Host': MY_ACCOUNT_HOST,
'Content-Type': 'text/plain; charset=UTF-8',//Added this line
'Content-MD5': corsMD5,//Added this line
};
var canonicalizedHeaders = buildCanonicalizedHeaders( headers );
// THIS
var key = buildSharedKeyLite( 'PUT', corsMD5, 'text/plain; charset=UTF-8', canonicalizedHeaders, canonicalizedResource, accountKey);
// AND THIS, BOTH YIELD THE SAME SERVER RESPONSE
// var key = buildSharedKeyLite( 'PUT', "", "", canonicalizedHeaders, canonicalizedResource, accountKey);
headers['Authorization'] = 'SharedKeyLite ' + MY_ACCOUNT_NAME + ':' + key;
var options = {
url: url,
body: MY_CORS_XML,
headers: headers
};
console.log("url : " + url);
console.log("canonicalizedResource : " + canonicalizedResource);
console.log("canonicalizedHeaders : " + canonicalizedHeaders);
console.log("corsMD5 : " + corsMD5);
console.log("key : " + key);
console.log("options : " + JSON.stringify(options));
function onPropertiesSet(error, response, body) {
if (!error && response.statusCode == 202) {
console.log("CORS: OK");
}
else {
console.log("CORS: " + response.statusCode);
console.log("body : " + body);
}
}
request.put(options, onPropertiesSet); // require('request')
};
function buildCanonicalizedHeaders( headers ) {
var xmsHeaders = [];
var canHeaders = "";
for ( var name in headers ) {
if ( name.indexOf('x-ms-') == 0 ) {
xmsHeaders.push( name );
}
}
xmsHeaders.sort();
for ( var i = 0; i < xmsHeaders.length; i++ ) {
name = xmsHeaders[i];
canHeaders = canHeaders + name.toLowerCase().trim() + ':' + headers[name] + '\n';
}
return canHeaders;
}
function buildSharedKeyLite( verb, contentMD5, contentType, canonicalizedHeaders, canonicalizedResource, accountKey) {
var stringToSign = verb + "\n" +
contentMD5 + "\n" +
contentType + "\n" +
"" + "\n" + // date is to be empty because we use x-ms-date
canonicalizedHeaders +
canonicalizedResource;
// return crypto.createHmac('sha256', accountKey).update(encodeURIComponent(stringToSign)).digest('base64');
return crypto.createHmac('sha256', new Buffer(accountKey, 'base64')).update(stringToSign).digest('base64');
}
Related
I've googled around a lot with no luck in finding the solution to my problem. I've read through the entire authentication process for AWS Signature 4 and followed their tutorial as well as view other sources. I'm trying to have client side authentication for a desktop application that makes request to API Gateway.
When I use Postman it works properly but I tried generating my own signature in Nodejs but to no avail, I keep getting 403 messages back from the call.
The function below returns the authenticated requestUrl which is then run by axios.get(requestUrl). When I use the Postman generated request it works perfectly fine but, once I use my generated request I have problems.
Am I missing something while authenticating? Here is what my code currently looks like:
function Authorize() {
const host = "EXAMPLE.execute-api.us-east-1.amazonaws.com"
const reg = 'us-east-1'
const meth = 'GET'
const serv = 'execute-api'
const endpoint = '/development/putImage'
// Keys
let access = "EXAMPLE"
let key = "KEY"
// Get Date
let t = new Date();
let amzDate = t.toJSON().replace(/[-:]/g, "").replace(/\.[0-9]*/, "");
let dateStamp = t.toJSON().replace(/-/g, "").replace(/T.*/, "");
// ************* TASK 1: CREATE CANONICAL REQUEST *************
// Create Canonical Request
let canonical_uri=endpoint
let canonical_headers="host: "+host+"\n"
let signedHeaders = 'host'
let algorithm = 'AWS4-HMAC-SHA256'
let credentialScope = dateStamp + "/" + reg + "/" + serv + "/" + "aws4_request"
// Set query string
let canonicalQueryString = ""
canonicalQueryString += "X-Amz-Date=" + amzDate
canonicalQueryString += "&X-Amz-Algorithm=" + algorithm;
canonicalQueryString += "&X-Amz-Credential=" + encodeURIComponent(access + "/" + credentialScope)
canonicalQueryString += "&X-Amz-SignedHeaders=" + signedHeaders
// Empty payload for get request
var payloadHash = crypto.createHash('sha256').update('').digest('hex');
// Set canonical request
var canonicalRequest = meth + "\n" + canonical_uri + "\n" + canonicalQueryString + "\n" + canonical_headers + "\n" + signedHeaders + "\n" + payloadHash
console.log(canonicalRequest)
// ************* TASK 2: CREATE THE STRING TO SIGN*************
let stringToSign = algorithm + '\n' + amzDate + '\n' + credentialScope + '\n' + crypto.createHash('sha256').update(canonicalRequest).digest('hex');
// ************* TASK 3: CALCULATE THE SIGNATURE *************
var signingKey = getSignatureKey(key, dateStamp, reg, serv)
var signature = crypto.createHmac('sha256', signingKey).update(stringToSign).digest('hex');
// ************* TASK 4: ADD SIGNING INFORMATION TO THE REQUEST *************
canonicalQueryString += '&X-Amz-Signature=' + signature
let requestUrl = "https://"+host+ endpoint + "?" + canonicalQueryString
console.log(requestUrl)
return requestUrl
}
The below code worked for me well. For more info, please visit https://docs.aws.amazon.com/opensearch-service/latest/developerguide/request-signing.html#request-signing-node
const { HttpRequest} = require("#aws-sdk/protocol-http");
const { defaultProvider } = require("#aws-sdk/credential-provider-node");
const { SignatureV4 } = require("#aws-sdk/signature-v4");
const { NodeHttpHandler } = require("#aws-sdk/node-http-handler");
const { Sha256 } = require("#aws-crypto/sha256-browser");
...
var request = new HttpRequest({
body: JSON.stringify({"users":["G0000000B","G0000000A"]}),
headers: {
'Content-Type': 'application/json',
'apiKey':'XXXXXXXXXXXX',
'apiSecret': 'XXXXXXXXXXXXXXXXXX',
'host': 'service2.xxx.xxx.xx'
},
hostname: 'service2.xxx.xxx.xx',
method: 'POST',
path: 'API/user/list'
});
var signer = new SignatureV4({
credentials: defaultProvider(),
region: 'ap-southeast-1',
service: 'execute-api',
sha256: Sha256
});
const signedRequest = await signer.sign(request);
// Send the request
var client = new NodeHttpHandler();
var { response } = await client.handle(signedRequest)
console.log(response.statusCode + ' ' + response.body.statusMessage);
var responseBody = '';
await new Promise(() => {
response.body.on('data', (chunk) => {
responseBody += chunk;
});
response.body.on('end', () => {
console.log('Response body: ' + responseBody);
});
}).catch((error) => {
console.log('Error: ' + error);
});
I am trying to request a token to the Here Map API with NodeJS in order to obtain OAuth 2.0 Token Credentials.
I am blocked a the request level and constantly having the same error but according to the documentation I don't do anything wrong.
Here is the necessary code in NodeJS to make the request :
const keyID = "MyKeyID";
const keySecret = "MySecretKey";
const httpMethod = "POST";
const httpUrl = "https://account.api.here.com/oauth2/token";
let oauthNonce = Math.random().toString(36).substring(6);
let oauthTimestamp = Date.now();
let oauthSignatureMethod = "HMAC-SHA256";
let oauthVersion = "1.0";
let baseString = httpMethod + "&" + httpUrl;
let oauth1Param = [];
oauth1Param.oauth_consumer_key = keyID;
oauth1Param.oauth_signature_method = oauthSignatureMethod;
oauth1Param.oauth_signature = "";
oauth1Param.oauth_timestamp = oauthTimestamp;
oauth1Param.oauth_nonce = oauthNonce;
oauth1Param.oauth_version = oauthVersion;
let paramString = "grant_type=client_credentials"
+ "&oauth_consumer_key=" + oauth1Param.oauth_consumer_key
+ "&oauth_nonce=" + oauth1Param.oauth_nonce
+ '&oauth_signature_method=' + oauth1Param.oauth_signature_method
+ "&oauth_timestamp=" + oauth1Param.oauth_timestamp
+ "&oauth_version=" + oauth1Param.oauth_version;
baseString += "&" + paramString;
let signingKey = keySecret + "&";
let signature = crypto.createHmac('SHA256', signingKey).update(baseString).digest('base64');
oauth1Param.oauth_signature = signature;
console.log(JSON.stringify(paramString));
console.log(oauth1Param);
let headerOauth = "OAuth ";
for (let key in oauth1Param) {
headerOauth += key + "=" + oauth1Param[key] + ",";
}
headerOauth = headerOauth.slice(0, -1);
console.log(headerOauth);
var request = require('request');
request.post({
url : httpUrl,
headers: {
'content-type' : 'application/x-www-form-urlencoded',
'Authorization' : headerOauth
},
body : JSON.stringify({
"grant_type" : "client_credentials"
})
}, function(error, response, body){
if(error){
console.log("------ ERROR -------");
throw error;
}
//console.log(response);
console.log(body);
});
And this is the error that I keep getting :
{"errorId":"ERROR-51fa3a57-1dfa-4da7-b2e9-5f94574a4f75",
"httpStatus":401,
"errorCode":401205,
"message":"Unsupported signature method in the header. Require HMAC-SHA256",
"error":"invalid_request",
"error_description":"errorCode: '401205'. Unsupported signature method in the header. Require HMAC-SHA256"}
According to the documentation, https://developer.here.com/documentation/authentication/dev_guide/topics/error-messages.html, I have an error with the signature method and that it's required to be HMAC-SHA256 but actually this is what I have put at the beginning of my code : let oauthSignatureMethod = "HMAC-SHA256";
Does anyone know how to fix this issue?
The library you're using, request, supports OAuth signing. Therefore the code can be significantly shorter:
const request = require('request');
const OAUTH_URL = 'https://account.api.here.com/oauth2/token';
const KEY_ID = '<KEY_ID>';
const KEY_SECRET = '<KEY_SECRET>';
request.post({
url: OAUTH_URL,
oauth: {
consumer_key: KEY_ID,
consumer_secret: KEY_SECRET,
signature_method: 'HMAC-SHA256'
},
headers: {
'Content-Type' : 'application/x-www-form-urlencoded',
},
form: {
'grant_type': 'client_credentials'
}
}, function (e, r, body) {
console.log(body);
});
As for your code snippet, JSON.stringify(obj) is not appropriate as it will encode the curly bracket of the object as well.
Additionally, the values in the OAuth header need to be surrounded by double quotes. It's easy to get little things wrong with OAuth :)
I am trying to hit SubmitFeed API of Amazon MWS API but whenever I try to make a HTTP call to the API it gives me an error from server saying "Signature Mismatch".
Note: First I tried to hit the API without Content MD5 value and the file .
var AWSAccessKeyId = "<AWSAccessKeyId>",
Action = "SubmitFeed",
MarketplaceId = "<MarketplaceId>",
SellerId = "<SellerId>",
SignatureMethod = "HmacSHA256",
SignatureVersion = "2",
Timestamp = new Date().toISOString(),
Version = "2009-01-01",
FeedType = "_POST_INVENTORY_AVAILABILITY_DATA_",
ContentMD5Value = "",
Signature = "",
SecretKey = "<SecretKey>";
var queryString = "AWSAccessKeyId=" + AWSAccessKeyId + "&Action=" + Action + "&ContentMD5Value=" + ContentMD5Value + "&FeedType=" + FeedType + "&MarketplaceIdList.Id.1=" + MarketplaceId + "&Merchant=" + SellerId + "&PurgeAndReplace=false" + "&SignatureMethod=" + SignatureMethod + "&SignatureVersion=2" + "&Timestamp=" + Timestamp + "&Version=" + Version;
var StringToSign = "POST" + "\n" + "mws.amazonservices.com" + "\n" + "/" + "\n" + queryString;
Signature = crypto.createHmac('sha256', SecretKey).update(StringToSign).digest('base64');
request({
url: 'https://mws.amazonservices.com/Feeds/2009-01-01/',
qs: {
AWSAccessKeyId: AWSAccessKeyId,
Action: Action,
FeedType: FeedType,
'MarketplaceIdList.Id.1': MarketplaceId,
Merchant: SellerId,
PurgeAndReplace: false,
SignatureMethod: SignatureMethod,
SignatureVersion: 2,
Timestamp: Timestamp1,
Version: Version,
Signature: Signature
},
method: 'POST',
headers: {
'Content-Type': 'text/txt; charset=iso-8859-1',
'x-amazon-user-agent': 'DIApp/1.0 (Language=Javascript)'
}
}, function(error, response, body) {
if (error) {
console.log(error);
} else {
console.log('-----------Status Code-------------');
console.log(response.statusCode);
console.log('-----------Status Code-------------');
console.log('-----------BODY-------------');
console.log(body);
console.log('-----------BODY-------------');
}
});
I want to send a notification to kaa server. The below cURL command is working fine but I want to send POST request from my node.js server. Kindly help me in converting to post request.
curl -v -S -u devuser:devuser123 -F'notification=
{"applicationId":"32769","schemaId":"32778","topicId":"32770","type":"USER"};
type=application/json' -F file=#notification.json "http://localhost:8080/kaaAdmin/rest/api/sendNotification" | python -mjson.tool
I tried like this:
var notificationValue= {"applicationId":"32769","schemaId":"32778","topicId":"32770","type":"USER"};
var file = 'notification.json';
var opts = {
url: 'http://localhost:8080/kaaAdmin/rest/api/sendNotification',
method: 'POST',
auth: { user: 'devuser', password: 'devuser123' },
json: true,
formData: {
notification: JSON.stringify(notificationValue),
file : fs.readFileSync(file)
}
};
request(opts, function(err, resp, body) {
if(err)
res.send(err);
else{
res.send(body);
}
});
I am getting: Error 400 Required request part 'notification' is not present.
Here is a solution.
First import next modules.
var fs = require('fs');
var request = require('request');
var crypto = require('crypto');
We need two utility functions to generate boundary for multipart content type and the other to build raw POST request body.
var CRLF = "\r\n";
var md5 = crypto.createHash('md5');
function multipartRequestBodyBuilder(fields, boundary) {
var requestBody = '';
for(var name in fields) {
var field = fields[name];
var data = field.data;
var fileName = field.fileName ? '; filename="' + field.fileName + '"' : '';
var type = field.type ? 'Content-Type:' + field.type + CRLF : '';
requestBody += "--" + boundary + CRLF +
"Content-Disposition: form-data; name=\"" + name + "\"" + fileName + CRLF +
type + CRLF +
data + CRLF;
}
requestBody += '--' + boundary + '--' + CRLF
return requestBody;
}
function getBoundary() {
md5.update(new Date() + getRandomArbitrary(1, 65536));
return md5.digest('hex');
}
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
Then we form our data and generate the boundary.
var notificationValue = {
"applicationId":"2",
"schemaId":"12",
"topicId":"1",
"type":"USER"
};
var postData = {
notification : {
data : JSON.stringify(notificationValue),
type : "application/json"
},
file : {
data : fs.readFileSync("message.json"),
fileName : 'notification.json',
type : 'application/octet-stream'
}
}
var boundary = getBoundary();
After that compose a request and send to Kaa Server.
var opts = {
url: 'http://localhost:8080/kaaAdmin/rest/api/sendNotification',
method: 'POST',
auth: { user: 'devuser', password: 'devuser123' },
headers: {
'content-type': 'multipart/form-data; boundary=' + boundary
},
body : multipartRequestBodyBuilder(postData, boundary)
};
request(opts, function(err, resp, body) {
if(err) {
console.log("Error: " + err);
} else {
console.log("Satus code: " + resp.statusCode + "\n");
console.log("Result: " + body);
}
});
After all, you will see the confirmation response with status code 200.
Status code: 200
Result: {
"id" : "57e42623c3fabb0799bb3279",
"applicationId" : "2",
"schemaId" : "12",
"topicId" : "1",
"nfVersion" : 2,
"lastTimeModify" : 1474569763797,
"type" : "USER",
"body" : "CkhlbGxvAA==",
"expiredAt" : 1475174563793,
"secNum" : 17
}
I attach the file with whole code that I tested on Notification Demo from Kaa sandbox: send notification.
I have been following the blob service and authentication documentation in order to set the CORS properties on my azure blob storage account from my mobile service.
I can't figure out what I am doing wrong.
The server response is:
The MAC signature found in the HTTP request 'JI...Tk=' is not the same as any computed signature. Server used following string to sign: 'PUT
x-ms-date:Wed, 19 Feb 2014 07:24:06 GMT x-ms-version:2013-08-15
/apporotest/?comp=properties'
When I log the string to sign (not passing contentMD5 and content type) on my end, it turns out to be the same string. So I guess my function to build the shared key is wrong.
This should build : Base64(HMAC-SHA256(UTF8(StringToSign))):
function buildSharedKeyLite( verb, contentMD5, contentType, canonicalizedHeaders, canonicalizedResource ) {
var stringToSign = verb + "\n" +
contentMD5 + "\n" +
contentType + "\n" +
"" + "\n" + // date is to be empty because we use x-ms-date
canonicalizedHeaders +
canonicalizedResource;
return crypto.createHmac('sha256', self.accountKey).update(encodeURIComponent(stringToSign)).digest('base64');
}
What confuses me though is that the formerly mentioned documentation for the Shared Key Lite requires MD5 of the content as well as the content type to be set. However, the server response with the string to sign does not seem to expect these.
If the creation of the shared key lite is correct, then I assume I am not handling the creation of the MD-5 content correctly or the canonicalized headers:
function setCors( cors ) {
var url = MY_ACCOUNT_UTL + '/?restype=service&comp=properties';
var canonicalizedResource = '/' + MY_ACCOUNT_NAME + '/?comp=properties';
var corsMD5 = crypto.createHash('md5' ).update(MY_CORS_XML).digest('base64');
var date = (new Date()).toUTCString();
var headers = {
'x-ms-version': '2013-08-15',
'x-ms-date': date,
'Host': MY_ACCOUNT_HOST
};
var canonicalizedHeaders = buildCanonicalizedHeaders( headers );
// THIS
var key = buildSharedKeyLite( 'PUT', corsMD5, 'text/plain; charset=UTF-8', canonicalizedHeaders, canonicalizedResource );
// AND THIS, BOTH YIELD THE SAME SERVER RESPONSE
var key = buildSharedKeyLite( 'PUT', "", "", canonicalizedHeaders, canonicalizedResource );
headers['Authorization'] = 'SharedKeyLite ' + MY_ACCOUNT_NAME + ':' + key;
var options = {
url: url,
headers: headers
};
function onPropertiesSet(error, response, body) {
if (!error && response.statusCode == 202) {
console.log("CORS: OK");
}
else {
console.log("CORS: "+ response.statusCode);
console.log(body);
}
}
request.put(options, onPropertiesSet); // require('request')
}
function buildCanonicalizedHeaders( headers ) {
var xmsHeaders = [];
var canHeaders = "";
for ( var name in headers ) {
if ( name.indexOf('x-ms-') == 0 ) )
xmsHeaders.push( name );
}
}
xmsHeaders.sort();
for ( var i = 0; i < xmsHeaders.length; i++ ) {
name = xmsHeaders[i];
canHeaders = canHeaders + name.toLowerCase().trim() + ':' + headers[name] + '\n';
}
return canHeaders;
}
I am really thankful for any pointers.
I'm not 100% sure but I believe encodeURIComponent is creating problem for you. For example, look at the code below:
var a = "PUT\n\n\nFeb 2014 09:08:18 GMT\nx-ms-version:2013-08-15\n/cynapta/?comp=properties";
var b = encodeURIComponent(a);
console.log(a);
console.log("\n");
console.log(b);
and this is how a and b are displayed on my console:
Can you try by removing encodeURIComponent and just pass stringToSign directly for signature calculation?
Update
Looking at the source code here for signing the request and assuming you're passing storage account key as string, can you try the following in your buildSharedKeyLite function:
return crypto.createHmac('sha256', new Buffer(self.accountKey, 'base64')).update(stringToSign).digest('base64');