title: Failed to authenticate account level SAS in Azure
body: <p>I have been following the <a href="https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-an-account-sas" rel="nofollow noreferrer">latest writeup from Microsoft</a> in an effort to generate an account level Shared Access Signature (SAS) for use with Azure storage services, notably Blobs.</p>
  
  <p>Every time I perform a <code>PUT</code> request to my Blob service, I receive a <code>403</code> response, with the message:</p>
  
  <blockquote>
    <p>Server failed to authenticate the request. Make sure the value of
    Authorization header is formed correctly including the signature.</p>
  </blockquote>
  
  <p>This is my function for generating signatures:</p>
  
  <pre class="lang-php prettyprint-override"><code>use MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings;
  use MicrosoftAzure\Storage\Blob\BlobRestProxy;
  
  public function generateUploadLink($container, $folder, $filename)
  {
      # get account settings
      $settings = StorageServiceSettings::createFromConnectionString(AZURE_BLOB);
      $accountName = $settings-&gt;getName();
      $accountKey = $settings-&gt;getKey();
  
      # define start and expire datetime stamps (ISO 8601)
      $startTime = (new DateTime('GMT'))-&gt;modify('-2 days')-&gt;format('Y-m-d\TH:i:s\Z');
      $expireTime = (new DateTime('GMT'))-&gt;modify('+2 days')-&gt;format('Y-m-d\TH:i:s\Z');
  
      $parameters = [];
      $parameters[] = $accountName;       # account name
      $parameters[] = 'wac';              # permissions
      $parameters[] = 'b';                # service
      $parameters[] = 'sco';              # resource type
      $parameters[] = $startTime;         # start time
      $parameters[] = $expireTime;        # expire time
      $parameters[] = '';                 # accepted ip's
      $parameters[] = 'https,http';       # accepted protocol
      $parameters[] = '2018-03-28';       # latest microsoft api version
  
      # implode the parameters into a string
      $stringToSign = utf8_encode(implode("\n", $parameters));
  
      # decode the account key from base64
      $decodedAccountKey = base64_decode($accountKey);
  
      # create the signature with hmac sha256
      $signature = hash_hmac("sha256", $stringToSign, $decodedAccountKey, true);
  
      # encode the signature as base64
      $sig = urlencode(base64_encode($signature));
  
      # construct the sas (shared access signature)
      $sas = "sv=2018-03-28&amp;ss=b&amp;srt=sco&amp;sp=wac&amp;se={$expireTime}&amp;st={$startTime}&amp;spr=https,http&amp;sig={$sig}";
  
      # create client
      $blobClient = BlobRestProxy::createBlobService(AZURE_BLOB);
  
      # generate upload link
      $blobUrlWithSAS = sprintf('%s%s?%s', (string)$blobClient-&gt;getPsrPrimaryUri(), "{$container}/{$folder}/{$filename}", $sas);
  
      # return upload link
      return $blobUrlWithSAS;
  }
  </code></pre>
  
  <p>A sample of my output, looks like this -- when making a <code>PUT</code> request to this URL, it fails with the aforementioned error message.</p>
  
  <p><code>https://batman.blob.core.windows.net/payroll-enroll/2019/test.txt?sv=2018-03-28&amp;ss=b&amp;srt=sco&amp;sp=wac&amp;se=2019-02-04T03:44:51Z&amp;st=2019-01-31T03:44:51Z&amp;spr=https,http&amp;sig=ox7RdKGTKRYvGz2u9ScFv4TP4ZfduKxFhYdpvJKjE4A%3D</code></p>
  
  <p>By comparison, if I generate an account level shared access signature directly from the Azure portal, it looks like this -- when making a <code>PUT</code> request to this URL, it succeeds.</p>
  
  <p><code>https://batman.blob.core.windows.net/payroll-enroll/2019/test.txt?sv=2018-03-28&amp;ss=b&amp;srt=sco&amp;sp=wac&amp;se=2019-02-02T16:29:17Z&amp;st=2019-02-02T08:29:17Z&amp;spr=https,http&amp;sig=omPc4ZwEdefDoHKqA4TqVOm3NUW%2BcKcNqTuD1hq94VU%3D</code></p>
  
  <p>I see no differences between the two, besides the start and expire times. As an aside, I'll also note that I have tried generating a service level SAS using the <a href="https://github.com/Azure/azure-storage-php" rel="nofollow noreferrer">azure-storage-php</a> package with the same result (<em>failed to authenticate the request</em>).</p>
  
  <p>I have confirmed and or tried the following things:</p>
  
  <ul>
  <li><code>$accountName</code> and <code>$accountKey</code> are returning the correct values, comparing to what is seen via the Azure portal</li>
  <li>tried each of <code>now</code>, <code>UTC</code>, and <code>GMT</code> as parameters to <code>DateTime()</code></li>
  <li>provided ample padding (2 days) on either side just to make sure it's not a time difference issue as has been frequently noted in other issues I've read through on Stack</li>
  <li>applied wildcard (<code>*</code>) CORS settings to my storage account for <code>ALLOWED METHODS</code>, <code>ALLOWED ORIGINS</code>, <code>ALLOWED HEADERS</code> and <code>EXPOSED HEADERS</code></li>
  <li>disabled <code>Secure transfer required</code> (allow both <code>http</code>, and <code>https</code>)</li>
  </ul>
  
  <p>I am stumped.</p>
  
  <p><strong>My Question</strong>: What am I doing wrong while constructing the SAS, and why do I continue to receive request failures related to authentication?</p>