Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DocumentDB REST API - Authorization Token Error

Problem

We are seeing this error returned from the DocumentDB REST API whenever we request a list or query, but not when we fetch objects by name/id:

The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used.

Background

We have been successfully using the node.js sdk with DocumentDB for over a year now, but as we want to migrate our back-end restful API code from a node.js App Service to Azure Functions we are seeing 10-30 second lag times come into play as the DocumentDB sdk loads slowly when the Function hasn't been called in a while. We know that the Function instance is hot, and this isn't a cold instance issue based on previous communication with the Azure Functions team.

To work around this we want to test the DocumentDB REST API which requires zero external libraries to run in a node.js Function and should execute as quickly as possible.

Code

This is the test harness running in local node.js. We'll move this to an Azure Function once it's working.

var express = require('express');
var router = express.Router();
var crypto = require("crypto"); 
var request = require('request');

router.get('/', function (req, res, next) {

  var key = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
  var uri = "https://xxxxxx.documents.azure.com";

  var verb = 'GET';
  var type = 'dbs';
  var link = 'dbs';
  var url = `${uri}/${link}`;

  var headers = getDefaultRequestHeaders();

  // var body = `{"query":"SELECT * FROM c", "parameters": []}`;
  var body = '';

  headers['content-length'] = body.length;
  headers['authorization'] = getAuthorizationTokenUsingMasterKey(verb, type, link, headers['x-ms-date'], key);

  request[verb.toLowerCase()]({"url": url, "headers": headers, "body": body}, function (error, response, body) {

    // console.log(`error is ${error}`);
    // console.log(`response is ${JSON.stringify(response, null, 2)}`);
    console.log(`body is ${body}`);

    res.status(response.statusCode).json(body);

  });

});

function getDefaultRequestHeaders(isQuery, date) {

  var headers = {
    "content-type": "application/json", 
    "x-ms-date": new Date().toUTCString(),
    "x-ms-version": "2017-02-22",
    "accept": "application/json",
    "cache-control": "no-cache",
    "user-agent": "xxxxxx/1.0"
  };

  if(isQuery) {
    headers["x-ms-documentdb-isquery"] = true;
    headers["content-type"] = "application/query+json";
  }

  if(date) {
    headers["x-ms-date"] = date;
  }

  return headers;

}

function getAuthorizationTokenUsingMasterKey(verb, resourceType, resourceLink, date, masterKey) {  
    var key = new Buffer(masterKey, "base64");  

    var text = (verb || "").toLowerCase() + "\n" +   
               (resourceType || "").toLowerCase() + "\n" +   
               (resourceLink || "") + "\n" +   
               date.toLowerCase() + "\n" +   
               "" + "\n";  

    var body = new Buffer(text, "utf8");  
    var signature = crypto.createHmac("sha256", key).update(body).digest("base64");  

    var MasterToken = "master";  

    var TokenVersion = "1.0";  

    return encodeURIComponent("type=" + MasterToken + "&ver=" + TokenVersion + "&sig=" + signature);  
}  


module.exports = router;

We are using the getAuthorizationTokenFromMasterKey function verbatim from the Access control in the DocumentDB API page.

The key, app name, and user-agent have been replaced with x's for privacy/security.

Test Results

List Databases

When I try the most basic call to list dbs the server returns the token error:

  var verb = 'GET';
  var type = 'dbs';
  var link = 'dbs';

Response:

"{\"code\":\"Unauthorized\",\"message\":\"The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'get\ndbs\n\nsat, 12 aug 2017 12:28:41 gmt\n\n'\r\nActivityId: acbf19d9-6485-45c5-9c30-6aa21f14d5b3\"}"

Get Database

However, when I perform the get database request it works fine:

  var verb = 'GET';
  var type = 'dbs';
  var link = 'dbs/00001';

Response:

"{\"id\":\"00001\",\"_rid\":\"0eUiAA==\",\"_ts\":1441256154,\"_self\":\"dbs\/0eUiAA==\/\",\"_etag\":\"\\"00007d4a-0000-0000-0000-55e7d2da0000\\"\",\"_colls\":\"colls\/\",\"_users\":\"users\/\"}"

List Collections

Similarly, requesting the list of collections from this database returns a token error:

var verb = 'GET';
var type = 'colls';
var link = 'dbs/00001/colls';

Respose:

"{\"code\":\"Unauthorized\",\"message\":\"The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'get\ncolls\ndbs/00001\nsat, 12 aug 2017 12:32:19 gmt\n\n'\r\nActivityId: 8a9d4ff8-24ef-4fd2-b400-f9f8aa743572\"}"

Get Collection

But when I call get collection I get a valid response:

var verb = 'GET';
var type = 'colls';
var link = 'dbs/00001/colls/00001';

Response:

"{\"id\":\"00001\",\"indexingPolicy\":{\"indexingMode\":\"consistent\",\"automatic\":true,\"includedPaths\":[{\"path\":\"\/*\",\"indexes\":[{\"kind\":\"Range\",\"dataType\":\"Number\",\"precision\":-1},{\"kind\":\"Range\",\"dataType\":\"String\",\"precision\":-1},{\"kind\":\"Spatial\",\"dataType\":\"Point\"}]}],\"excludedPaths\":[]},\"_rid\":\"0eUiAJMAdQA=\",\"_ts\":1454200014,\"_self\":\"dbs\/0eUiAA==\/colls\/0eUiAJMAdQA=\/\",\"_etag\":\"\\"00000100-0000-0000-0000-56ad54ce0000\\"\",\"_docs\":\"docs\/\",\"_sprocs\":\"sprocs\/\",\"_triggers\":\"triggers\/\",\"_udfs\":\"udfs\/\",\"_conflicts\":\"conflicts\/\"}"

List Documents

Requesting list documents on that collection gives me this error:

var verb = 'GET';
var type = 'docs';
var link = 'dbs/00001/colls/00001/docs';

Response:

"{\"code\":\"Unauthorized\",\"message\":\"The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'get\ndocs\ndbs/00001/colls/00001\nsat, 12 aug 2017 12:34:48 gmt\n\n'\r\nActivityId: 57097e95-c41b-4770-b91a-370418ef2cce\"}"

Get Document

Not surprisingly, fetching a single document works fine:

var verb = 'GET';
var type = 'docs';
var link = 'dbs/00001/colls/00001/docs/e7fe638d-2152-2097-f9c6-9801d7cf5cdd';

Response:

"{\"name\":\"test rest api\",\"id\":\"e7fe638d-2152-2097-f9c6-9801d7cf5cdd\",\"_rid\":\"0eUiAJMAdQCbHgAAAAAAAA==\",\"_self\":\"dbs\/0eUiAA==\/colls\/0eUiAJMAdQA=\/docs\/0eUiAJMAdQCbHgAAAAAAAA==\/\",\"_etag\":\"\\"0d00d1ee-0000-0000-0000-598ef7d40000\\"\",\"_attachments\":\"attachments\/\",\"_ts\":1502541779}"

Query Documents

Finally, sending a query also results in a token error:

var verb = 'POST';
var type = 'docs';
var link = 'dbs/00001/colls/00001/docs';
var body = `{"query":"SELECT * FROM c", "parameters": []}`;

Response:

"{\"code\":\"Unauthorized\",\"message\":\"The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'post\ndocs\ndbs/00001/colls/00001\nsat, 12 aug 2017 12:35:42 gmt\n\n'\r\nActivityId: b8b95f8c-1339-423e-b0e7-0d15d3056180\"}"

like image 675
Graham Avatar asked Aug 12 '17 00:08

Graham


2 Answers

I believe the documentation is incorrect. Where they say resourceLink, they should actually say resource id. If you look at the Node SDK code, this is how they are calculating the authorization header (notice the use of resourceId):

getAuthorizationTokenUsingMasterKey: function (verb, resourceId, resourceType, headers, masterKey) {
    var key = new Buffer(masterKey, "base64");

    var text = (verb || "").toLowerCase() + "\n" +
               (resourceType || "").toLowerCase() + "\n" +
               (resourceId || "") + "\n" +
               (headers["x-ms-date"] || "").toLowerCase() + "\n" +
               (headers["date"] || "").toLowerCase() + "\n";

    var body = new Buffer(text, "utf8");

    var signature = crypto.createHmac("sha256", key).update(body).digest("base64");

    var MasterToken = "master";

    var TokenVersion = "1.0";

    return "type=" + MasterToken + "&ver=" + TokenVersion + "&sig=" + signature;
},

So if you want to list the databases, because there is no resource id you will need to use an empty string for your link variable. Similarly, if you want to list collections in the database, the link should actually be the id of the database (e.g. dbs/00001 and not dbs/00001/colls).

like image 95
Gaurav Mantri Avatar answered Sep 17 '22 17:09

Gaurav Mantri


I was getting the same issue. For querying documents I was getting authorization token error. It was due to wrong ResourceId/ResourceLink

var verb = 'POST';
var type = 'docs';
var link = 'dbs/{db-id}/colls/{coll-id}/docs';
var url = `${uri}/${link}`;
var resourceLink = "dbs/{db-id}/colls/{coll-id}"

getAuthorizationTokenUsingMasterKey(verb, type, resourceLink, headers['x-ms-date'], key)

the only correction is required from the given question data is to change the appropriate resourceLink while generating AuthorizationToken. For querying Documents the resourceLink is <dbs/{db-id}/colls/{coll-id}> instead of <dbs/{db-id}/colls/{coll-id}/docs>

like image 31
avinash kumar Avatar answered Sep 18 '22 17:09

avinash kumar