Creating Azure blob storage shared access signatures using JavaScript

There are quite a lot of examples on the Internet on how to create a shared access signature for Azure storage. However, the examples are often in C# or pseudo-code.

A Shared Access Signature is basically what is called a message authentication code – MAC – that is used to grant a user access to a restricted resource, often for a certain period of time. (In other words, a MAC typically has a validity period). In the case of Azure, a so-called Hashed MAC is used – HMAC. This is achieved using standard cryptography functionality, namely the SHA-256 hash algorithm. By using standard cryptography functionality, creating such MACs is also available in other programming languages and platforms.

In this blog entry I will show how to create MACs for Azure blob storage using JavaScript.

The first thing we need, is a library with cryptography functions. I chose Crypto-JS:

<script type="text/javascript" src="http://crypto-js.googlecode.com/files/2.0.0-crypto-sha256.js"></script>
<script type="text/javascript" src="http://crypto-js.googlecode.com/files/2.0.0-hmac.min.js"></script>

Next, we create a function to generate the signature we need:

Date.prototype.toIso8061 = function() {
   var d = this;
   function p(i) { return ("0"  + i).slice(-2); }
   return "yyyy-MM-ddThh:mm:ssZ"
      .replace(/yyyy/, d.getUTCFullYear())
      .replace(/MM/, p(d.getUTCMonth()))
      .replace(/dd/, p(d.getUTCDay()))
      .replace(/hh/, p(d.getUTCHours()))
      .replace(/mm/, p(d.getUTCMinutes()))
      .replace(/ss/, p(d.getUTCSeconds()));
};
function generateSignature(base64EncodedSharedKey, startTime, endTime, account, container, blobName) {
   var stringToSign = "rn{0}n{1}n/{2}/{3}/{4}n"
      .replace(/{0}/, startTime.toIso8061())
      .replace(/{1}/, endTime.toIso8061())
      .replace(/{2}/, account)
      .replace(/{3}/, container)
      .replace(/{4}/, blobName);
   var accessKeyBytes = Crypto.util.base64ToBytes(base64EncodedSharedKey);
   return Crypto.util.bytesToBase64(Crypto.HMAC(Crypto.SHA256, stringToSign, accessKeyBytes, { asBytes: true }));
}

Then, we can construct a URL that we will use to request a resource from the blob store:

var start = new Date(); // Start of the validity period of the MAC
var end = new Date(start.getTime() + (1000 * 60 * 30)); // End of the validity period, half an hour from now
var signature = generateSignature(startTime, endTime, "myaccount", "foryoureyesonly", "liveorletdie.avi");
var queryString = "?st={0}&se={1}&sr=b&sp=r&sig={2}"
   .replace(/{0}/, encodeURIComponent(startTime.toIso8061()))
   .replace(/{1}/, encodeURIComponent(endTime.toIso8061()))
   .replace(/{2}/, encodeURIComponent(signature));
var url = "http://myaccount.blob.core.windows.net/foryoureyesonly/liveorletdie.avi" + queryString;

Security warning

Although possible to do, it is not always advisable to generate the signature in a browser because generating the signature requires access to the shared key. The shared key is highly sensitive data, it is often not advisable to trust the browser client with this information if it should not be disclosed to the browser user. There are, however, some use cases where this is OK. I am planning to blog about one of them later on. Also, the code above can easily be used in a server-side scenario, such as a solution based on node.js.

Azure and TFS

Just checked in my Azure WebRole solution in to TFS. I immediately encountered two problems. Luckily, it turned out that both where solvable.

Problem #1: CSPack Fails on TFS Build

The forum thread concludes that this is is a bug that will be fixed in the Azure SDK and possibly Visual Studio. However, I was able to work around it by combining some of the other responses in the thread, namely by editing the OutputPath element and the ServiceOutputDDirectory element in the ccproj file.

Problem #2: System.ServiceModel.CommunicationObjectFaultedException During Role Instance Start-up

This error was caused by the web.config file not being editable on the file system. Checking out the file for edit from TFS fixed the problem.