7
votes

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\"}"

3
Looking at the error message, it seems server is not including resource link in calculating signature (get\ndbs\n\nsat... ... shouldn't server be using something like get\ndbs\n/dbs\nsat...)? - Gaurav Mantri
You're right, I'm looking into that now. Thank you. - Graham
Please try by removing the / in the beginning of request links from the requests that are failing. So the resource link should be dbs and dbs/00001/colls in your example. - Gaurav Mantri
I tried that, still doesn't work for dbs, colls, docs - Graham

3 Answers

7
votes

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).

1
votes

I want to provide another thing to consider in addressing this issue. In my case I had to add this header: x-ms-documentdb-query-enablecrosspartition: true, because I created my container with a partitionKey.

I also want to confirm how my main parameters were setup to calculate the authorization header for querying over Documents:

resourceType: docs
resourceLink: dbs/<databaseId>/colls/<containerId>

I had thought, up to this point, that the resourceLInk had to match with the request URL but this is showing me I was wrong. Similarly notice that the resourceType is not present in the resourceLink.

POST /dbs/<databaseId>/colls/<containerId>/docs HTTP/1.1
accept: application/json
x-ms-documentdb-isquery: true
x-ms-version: 2018-12-31
authorization: type%3Dmaster%26ver%3D1.0%26sig%***********************
x-ms-date: Sat, 03 Apr 2021 22:34:24 GMT
x-ms-documentdb-query-enablecrosspartition: true
x-correlation-id: be1b1fe1-94cc-11eb-a0a4-38f9d3924940
Host: <host>.documents.azure.com
User-Agent: AHC/1.0
Connection: keep-alive
Content-Type: application/query+json
Content-Length: 72

{
  "query": "SELECT * FROM <containerId>",
  "parameters": [
    
  ]
}
0
votes

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>