Browser Direct Upload Data to Azure Blob Storage Part 2: SAS PHP (Tricky!)

Overview

I discuss the PHP source code (Very Tricky!) to generate correct Shared Access Signature and the mechanism behind for the purpose of Browser Direct Upload Data to Azure Blob Storage.

Well, this part is quite tricky part to generate the correct SAS, you probably could find tons of material online describing how to generate the SAS for Azure Blob Storage yet still failed to make it (I feel very frustrated back then). The common error you encounter when you got the wrong signature would be CORS error telling you “No ‘Access-Control-Allow-Origin’ header is present on the requested resource” and if you click the query url you probably get “Server failed to authenticate the request” error.

All of these are about the incorrect signature. Even the Microsoft official documentation is kinda misleading. Here in this post, I hope to summarize all the traps I have fallen into and analyze the mechanisms behind and give clarifications as well.

Shared Access Signature Concept

Following a similar paradigm as for Amazon S3 direct uploading and as indicated by the figure below, SAS is used to authorize the browser client to directly access the Azure Blob Storage.

sas-storage-provider-service

All the general concepts and examples could be found in the following two links: Examples of Shared Access Signatures Shared Access Signatures, Part 1: Understanding the SAS Model. Basically you could treat Azure SAS as policy and signature in Amazon S3 case.

PHP Details to Generate SAS

I give the code directly first:

function converToISOTime($timeToConvert = null) {
  $tz = @date_default_timezone_get();
  @date_default_timezone_set('UTC');

  if (is_null($timeToConvert )) {
      $timeToConvert = time();
  }

  $returnValue = str_replace('+00:00', '.0000000Z', @date('c', $timeToConvert));
  @date_default_timezone_set($tz);
  return $returnValue;
}

function generateSignature($accountKey, $resourceFullPath, $start, $expires) {
  $strToSign = array();
  $strToSign[] = 'w';
  $strToSign[] = $start;
  $strToSign[] = $expires;
  $strToSign[] = $resourceFullPath;
  $strToSign[] = '';

  $stringToSign = implode("\n", $strToSign);
  $sig = base64_encode(hash_hmac('sha256', $stringToSign, $accountKey, true));
  return $sig;
}

$blob = 'YourOwnBlobName';
$container = 'YourOwnContainerName';
$accountName = 'YourOwnAccountname';
$accountKey = YourOwnAccessKey;
$baseUrl = "http://$accountName.blob.core.windows.net/$container";

$start =   convertToISODate(time() - 60);
$expires = convertToISODate(time() + 3000);

$sig = generateSignature($accountKey, "/".$accountName."/".$container."/".$blob, $start, $expires);

$options = array();
$options[] = 'sp=w';
$options[] = 'st=' . urlencode($start);
$options[] = 'se=' . urlencode($expires);
$options[] = 'sr=b';
$options[] = 'sig=' . urlencode($sig);
$url = "$baseUrl$/$blob?".implode('&', $options);
?>

Let me explain the meaning of several parameters and all the tricks I found to finally come up with the above working code:

  1. You need to know the format of the $url variable by reading the official document, all the meaning of sp=w, st, se, sr, sig= could be found in the official documentation, these are not the tricky part but you need to know in order to understand how we form the final $url
  2. The most tricky part is how to generate the signature. Examples of Shared Access Signatures gives several examples. You first need to know the following fields by reading the examples <pre>signedstart=2009-02-09T08:49Z signedexpiry=2009-02-10T08:49Z signedresource=c signedpermissions=w signature= Rcp6gQRfV7WDlURdVTqCa+qEArnfJxDgE+KH3TCChIs= signedidentifier=YWJjZGVmZw== signedversion=2012-02-12</pre>
Yes all of these are used to form the string to sign. One remarkable note is that the example  &#8221;Example: Put a Blob using a Container’s Shared Access Signature&#8221; is misleading, in that example, it uses a container signeresource instead of blob, from my experience, the signedresource has to be the full path with these format: /AccountName/ContainerName/BlobName in our case to put the blob into some container.</li> 

  * Now that you get some idea about the fields above, you can start to form the string like the following 
    <pre>StringToSign = w + \n 
           2009-02-09T08:49Z + \n
           2009-02-10T08:49Z + \n
           /myaccount/pictures + \n
           YWJjZGVmZw== + \n
           2012-02-12</pre>
    
    There are many (to me) tricks about these StringToSign in actual PHP implementation, I list them as follows and all of them have to be satisfied for the whole thing to work
    
      * in the PHP code, implode(&#8220;\n&#8221;, $strToSign), you must use double quotes instead of single quotes, single quotes would not treat \n as what really wanted in the example, that is, if you use single quite &#8216;\n&#8217;, then the strToSign would still be a single line string with this &#8216;\n&#8217; inserted between each fields. Isn&#8217;t this too tricky?! You can imagine how crazy I become after finding this fact, some online code just use single quotes and those codes never work correctly if you try to run or execute them (at least, on my computer)
      *  in the PHP code, you need to do Base64 Decoding for the pure access key string you copied from the Azure Portal. Otherwise, again you fail
      * in the url options, &#8220;sp=w&#8221; needs to be exactly the same as signedpermissions fields of the StringToSign. For example, if you specify signedpermissions = &#8220;wrl&#8221; (write, read, list) but you only specify &#8220;sp=w&#8221;, then the authentication would fail.
      * even if you do not specify a signedidentifier in the options part, you still need to specify an empty one when forming the StringToSign which is used to generate the signature.
      * the order of the $strToSign[] is important and has to be exactly the same in my code, if you change the order. Again, the authentication fail.
    
    As long as you have the above issues correctly resolved, I think the whole thing would be working!</li> </ol> 
    
    ## Summary
    
    PHP code as well as all the tricks to generate correct Shared Access Signature and the mechanism behind are explained and discussed in this post for the purpose of Browser Direct Upload Data to Azure Blob Storage. If you read the whole thing up, you will find that some of the tricks could really make people crazy. I hope the points I summarized here could reduce a bit of work for those who have similar problems. Please feel free to leave comments and discussions here in case I miss anything.
    
    <hr width="100%" />
    
    <div>
      <p align="center">
        <strong>(Please specify the source  <a title="http://www.sigmainfy.com" href="/" target="_blank">烟客旅人 sigmainfy — http://www.sigmainfy.com</a>  as well as the original permalink</strong>
      </p>
      
      <p align="center">
        <strong>URL for any reprints,  and please do not use it for commercial purpose)</strong>
      </p>
    </div>
Written on October 16, 2014