Question

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

Was it helpful?

Solution 2

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');
}

OTHER TIPS

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);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top