0
votes

azure table query rest api is failing with AuthenticationFailed error.

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
  <error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
      <code>AuthenticationFailed</code>
      <message xml:lang="en-US">Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.</message>
   </error>

The winjs app code snippet to form and make the rest call is:

var date = new Date().toGMTString().replace('UTC', 'GMT');
var xhrOption = {
    type: 'GET',
    url: url,
    headers: {
        'content-type': 'application/atom+xml;charset="utf-8"',
        'content-length': 0,
        dataserviceversion: '1.0;NetFx',
        maxdataserviceversion: '2.0;NetFx',
        'x-ms-version': '2011-08-18',
        'x-ms-date': date,
        accept: 'application/atom+xml,application/xml',
        'Accept-Charset': 'UTF-8',
    },
};

xhrOption.headers.Authorization = AuthorizationHeader().computeForTableService(options, xhrOption);

The code to compute the authorization header is little long. It is listed below:

_getSignatureStringForTableService: function getSignatureStringForTableService()
{
    var headers = this.xhrOptions.headers;
    var httpVerb = this.xhrOptions.type.toUpperCase();
    var sigItems = [];
    sigItems.push(httpVerb);
    var contentMD5 = this._getHeaderOrDefault(headers, 'Content-MD5');
    sigItems.push(contentMD5);
    var contentType = this._getHeaderOrDefault(headers, 'content-type');
    sigItems.push(contentType);
    var date = this._getHeaderOrDefault(headers, 'x-ms-date');
    if (!date)
        date = this._getHeaderOrDefault(headers, 'Date');
    sigItems.push(date);
    var canonicalizedResource = this._getCanonicalizedResource();
    sigItems.push(canonicalizedResource);
    var result = sigItems.join('\n');
    return result;
},
_getCanonicalizedResource: function getCanonicalizedResource()
{
    var items = [];
    var path;
    if (config.storageAccount.isDevStorage)
        path = "/" + config.storageAccount.name + '/' + config.storageAccount.name;
    else
        path = "/" + config.storageAccount.name;

    path += "/" + this.options.resourcePath;
    items.push(path);
    var result = items.join('\n');
    return result;
},
computeForTableService: function computeForTableService(options, xhrOptions)
{
    this.options = options;
    this.xhrOptions = xhrOptions;
    var sig = this._computeSignatureForTableService();
    var result = 'SharedKey ' + config.storageAccount.name + ':' + sig;
    return result;
},
_computeSignatureForTableService: function computeSignatureForTableService()
{
    var sigString = this._getSignatureStringForTableService();

    // TODO: use crypto from windows api. currently uses, google cryptoJS lib
    var key = CryptoJS.enc.Base64.parse(config.storageAccount.primaryKey);
    var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key);
    hmac.update(sigString);
    var hash = hmac.finalize();
    var result = hash.toString(CryptoJS.enc.Base64);
    return result;
},

Interestingly, I have the whole code working fine 2 days before. I have updated service code to use updated azure nodejs sdk. I wonder if the update caused some incompat in the publisher/consumer code?

Other observations

  1. The service code that uses azure nodejs module, is able to query the table storage without error.
  2. I debugged through the azure nodejs module, looked through the stringToSign and matched with what winjs code is producing. both are same afaik.
  3. service was upgrade to use 0.10.x node and respective latest azure nodejs sdk.
  4. Example: stringToSign

    GET\n\napplication/atom+xml;charset="utf-8"\nWed, 5 Jun 2013 14:43:30 GMT\n/devstoreaccount1/devstoreaccount1/mytable()

Thanks for going through details.

1

1 Answers

1
votes

Finally - the root cause of the bug is out. The issue is value of x-ms-date header.

Expected value - Thu, 06 Jun 2013 08:09:50 GMT Value computed in the code above - Thu, 6 Jun 2013 08:20:34 GMT

The 0 missing before the date is the root cause of this bug. Because of that, stringToSign used in computing the authorization header is incorrect. Hence, Authorization Header is incorrect leading to AuthenticationFailed error. This also explains the reason why this code worked couple of days back (end of may - date had two digits).

If someone from MS is reading this, it will be so much useful to have right amount of details along with the error code. AuthenticationFailed error code alone does not give any clue to developer.

I had used azure storage blob rest api earlier. It returns better error for the same AuthenticationFailed error code. It sends across the expected stringToSign and found stringToSign along with the AuthenticationFailed error code. It is so much more helpful and bug gets resolved in couple of minutes.

Used Network monitor from Microsoft. Wrote c# code snippet to make the azure table query using azure .net sdk, and compared every header character by character to hit the issue.