Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Hosted-services / Azure

GET Azure Blob with Subtle Crypto Signed Pure JavaScript

0.00/5 (No votes)
7 Feb 2022CPOL5 min read 4.1K  
Example code showing how to use Subtle Crypto to sign your request to Azure API in pure JavaScript or jQuery
It took way too much work to sign an Azure API GET Blob request and get back my blob. Mistakes were made, and I wish there had been more resources for me to see where I made my mistakes early on. I compiled everything into an article hoping it might help others.

Introduction

I decided to write this article because I couldn’t find a single source for using vanilla JavaScript to call the Azure GET Blob API. I recently had a client require me to write some code for the SaleForce Lightning Components using pure JavaScript, which I’ll just refer to from now on as “vanilla” or “JS”. Well, it took longer than expected and after consulting a few other developers to get pieces I was unaware of, and combing through about twenty articles, I compiled a test script that works. This article is a summary of all that so others may find it useful and not have to do as much work as I did. No one I knew nor any articles showed a vanilla JavaScript version of this. Two things:

  • The full simplified method is at the bottom.
  • I’ll start the article off with some background so many of you might see why I think this article was important to write.

Here’s the Azure Get Blob documentation:

Background

Biggest Time Saver

If you’ve set everything up and you get an error from the Azure API server saying “Cannot find the blob/file.” Then don’t despair. You might just need to remove the file extension from your request url. Turns out, I would have had my API working immediately if I had realized this.

You Have Two API Options

You can get your files through the Blob API, or the File API. The biggest difference will be the url path changes from

https://<account>.blob…

to:

https://<account>.file…

And you’ll need to change the header value for x-ms-version.

You Need Subtle Crypto to Sign Your Request?

If your organization is requiring you to not send the Shared Key for your Azure API request, then you’ll need to sign a special string and add that into your authentication header. I was able to get a PostMan request through to Azure and get back my blob, a PDF, without doing the signing and just having the three header values shown in the next section. Below is what that signed signature will look like:

Header Name Value
Authentication Sharedkey: <account>:<signature using shared key>

If you’re not going to use a signature, such as in PostMan, but this is not as secure.

Header Name Value
Authentication Sharedkey <account>:< shared key >

If you use the shared key to sign, then that’s where a lot of the complexity comes in. Your organization might require this like my client did. The process in vanilla goes like this:

  1. Using Subtle Crypto
  2. Import a SHA-256 HMAC key used for signing
  3. Construct your string using exactly the same values you included in your header. The format matters.
  4. Add that constructed string into you Authentication header where above it says "signature using shared key"

More information on the Auth Header can be found at this link.

Required Header Values

x-ms-date

  • Is used to make sure your request isn’t out of time limits the API requires.
  • You’ll send it the time of your request in USD - I’ll show you later.

x-ms-version

  • This is the API version.
  • If you plan on using Blob API, then use “2009-09-19”.
  • If you plan on using File API, then use “2014-02-14” (I did not test this.)
  • More information can be found at this link.

Authentication

  • See this link for more information.
  • You can use SharedKey and SharedKeyLite - but I didn’t use “Lite” so it’s outside the scope of this article. But you can look here.

Everything’s Asynchronous

There are actually three async calls in my method, so be sure you are using async design patterns when coding your version. The version of the method I gave the client allowed them to pass me an array of requested files from Azure Storage.

  1. I’d imported a signing key in SubtleCrypto whose method was async.
  2. Then I’d sign the necessary string using Subtle Crypto in another async call.
  3. Finally, I’d call the API using XMLHTTPRequest which is also async.

Here’s a link to Subtle Crypto, there are libraries out there such as Crypto.js, but part of doing vanilla JS is not using additional libraries.

Here is a link to XMLHTTPRequest object in case you want to learn more.

If you want to see a simple example of it in use, go check out this link.

To understand AJAX, click on this link.

My example has the resulting blobs written out to the console. If you’re not sure how to handle this, a simple way is to pass a success and error method to your method that’s making the asynchronous calls. An example of that would be like below:

JavaScript
function Success(obj) { console.log(obj); } // do what you want with the file
function ApiCallMethod ( successCall, … ) {...}

//to actually call your method
ApiCallMethod (Success, … );

Your other option is to use “await”. I’m accustomed to async so I don’t mind writing it, it just flows right, but it’s not as simple as using “await”. Below is a great example of using await, which a lot of articles I referenced did use.

Using the Code

Remember, this is just a test function so it’s very simple. The method should be self explanatory at this point. It does use jQuery for the API call, which is fine in some cases because most of the devs I work with have jQuery enabled in their projects. If you want vanilla JS, then just use XMLHTTPRequest shown at the end of the method- commented out.

JavaScript
function Test_GetAzureBlog()
{
    var vdate = "2009-09-19";
    var ddate = (new Date().toUTCString());
    var stringToSign = "GET\n\n\n\n\n\n\n\n\n\n\n\nx-ms-date:" + ddate
        + "\nx-ms-version:" + vdate
        + "\n/[account]/[container]";
    const key = "[ Shared Key ]";// you can do this however you want

    //exclude the file extension
    const url = "https://[account].blob.core.usgovcloudapi.net/"
        + "[container]/[file and folder]";

    var sign2 = "";
    var enc = new TextEncoder("utf-8");
    window.crypto.subtle.importKey(
        "raw",
        enc.encode(key),
        {
            name: "HMAC",
            hash: { name: "SHA-256" }
        },
        false, // export = false
        ["sign", "verify"]
    ).then(keyh => {
        window.crypto.subtle.sign(
            "HMAC",
            keyh,
            enc.encode(stringToSign)
        ).then(signature => {
            var b = new Uint8Array(signature);
            var str2 = b.join(",");
            sign2 = str2;

            console.log(sign2);
    //BELOW uses jQuery, but the XMLHttpRequest works too, see it after the method
            $.ajax({
                type: 'GET',
                url: url,
                success: function (json) {
                    console.log(json);
                },
                error: function (json) {
                    console.log(json);
                },
                beforeSend: function (xhr) {
                    xhr.setRequestHeader("Authentication", "SharedKey "
                        + "sagiaprrdag:" + btoa(sign2));
                    xhr.setRequestHeader("x-ms-date", ddate);
                    xhr.setRequestHeader("x-ms-version", vdate);
                }
            });
        });
    });
}

/* --- variables are the same as the method above ---
var request = new XMLHttpRequest();

request.open('GET', url) ;
  //any variables scoped here will be available for request.onload and onerror
  request.setRequestHeader("x-ms-date", ddate);
  request.setRequestHeader("x-ms-version", vdate);
  request.setRequestHeader("Authentication", "SharedKey " + [account] + btoa(sign2));
  //handle success
  request.onload = async function () { console.log(this.response); };
  //handle error
  request.onerror = async function () { console.log(this.response); };
request.send();

*/

History

  • 7th February, 2022: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)