1

I'm using .NET Core, so I can't use the Azure DocumentDb SDK. That is why I want to create a document via the REST interface. I've managed to query the database, but when I POST a JSON document, I get an Unauthorized response. This is my code:

const string DatabaseId = "DB-id";
const string CollectionId = "UserSettings";
var documentDbUrl = "Injected via DI";
var authorizationKey = "Also injected;

using (var httpClient = new HttpClient())
{
    var utcNow = DateTime.UtcNow;
    httpClient.DefaultRequestHeaders.Add("x-ms-date", utcNow.ToString("r"));
    httpClient.DefaultRequestHeaders.Add("x-ms-version", "2015-08-06");
    httpClient.DefaultRequestHeaders.Add("x-ms-documentdb-is-upsert", "true");

    var resourceLink = string.Format("dbs/{0}/colls/{1}/docs", DatabaseId, CollectionId);
    var baseUrl = new Uri(documentDbUrl);

    var masterKeyAuthorizationSignatureGenerator = new MasterKeyAuthorizationSignatureGenerator();
    var authHeader = masterKeyAuthorizationSignatureGenerator.Generate("POST", resourceLink, "docs", authorizationKey, "master", "1.0", utcNow);
    httpClient.DefaultRequestHeaders.Add("authorization", authHeader);

    var response = await httpClient.PostAsJsonAsync(new Uri(baseUrl, resourceLink), userSettings);

    // at this point, response.StatusCode is Unauthorized
}

The MasterKeyAuthorizationSignatureGenerator you see there contains the logic to create the hash, which works when querying the database:

public string Generate(string verb, string resourceId, string resourceType, string key, string keyType, string tokenVersion, DateTime requestDateTime)
{
    var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(key) };

    var payLoad = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}\n{1}\n{2}\n{3}\n{4}\n",
            verb.ToLowerInvariant(),
            resourceType.ToLowerInvariant(),
            resourceId,
            requestDateTime.ToString("r").ToLowerInvariant(),
            ""
    );

    var hashPayLoad = hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payLoad));
    var signature = Convert.ToBase64String(hashPayLoad);

    return WebUtility.UrlEncode(string.Format(System.Globalization.CultureInfo.InvariantCulture, "type={0}&ver={1}&sig={2}",
        keyType,
        tokenVersion,
        signature));
}

I'm sure the databaseid, collectionid, url and key are correct. The key and id's are the same I use to query, which works. And when I change the url, for example by adding the documentid (which I then generate myself), I get another message (MethodNotAllowed).

Update:

Using Postman, I can see this is the response I get:

{
    "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/MyDb/colls/MyColl\nsat, 23 apr 2016 09:44:39 gmt\n\n'\r\nActivityId: 1be76530-ad32-4b54-b96b-6e0d4ebbc851"
}

Any hints on what I'm doing wrong or how I can analyse this?

2
  • Use cURL to make the request outside of your code, it'll be much quicker to try different things. Commented Apr 20, 2016 at 18:52
  • I used Postman and put the response in my update. Commented Apr 23, 2016 at 9:51

2 Answers 2

0

Please note that there are two ways to specify a resource in the URI:

1) using the resource id.  The id is user settable.

2) using the resource _rid.  This is the system generated id for the resource.

When using the resource id (method 1), you must make sure the resource id in concatenated token used to hash the signature token is in the same casing used in the resource as resource ids are case sensitive. For example, if the resource id is MyCollection for a collection, the resource id should then be exactly MyCollection in the concatenated token.

This article provides additional details in constructing the hash signature token. https://msdn.microsoft.com/en-us/library/azure/dn783368.aspx

Sign up to request clarification or add additional context in comments.

1 Comment

I added the code for generating the hash. As you can see, I'm not uppercasing or lowercasing the resourceId.
0

This is a subtle one, but I was using the following as resource id to generate my hash:

var resourceLink = string.Format("dbs/{0}/colls/{1}/docs", DatabaseId, CollectionId);
// this becomes something like dbs/MyDb/colls/MyCollection/docs

But the /docs should be omitted. So it becomes:

var resourceLink = string.Format("dbs/{0}/colls/{1}", DatabaseId, CollectionId);
// this becomes something like dbs/MyDb/colls/MyCollection

However, when I do that, I get a method not allowed response. This is because I was using this link for the post uri too. But the post uri should be the link with the /docs included.

So my full code now becomes:

const string DatabaseId = "DB-id";
const string CollectionId = "UserSettings";
var documentDbUrl = "Injected via DI";
var authorizationKey = "Also injected;

using (var httpClient = new HttpClient())
{
    var utcNow = DateTime.UtcNow;
    httpClient.DefaultRequestHeaders.Add("x-ms-date", utcNow.ToString("r"));
    httpClient.DefaultRequestHeaders.Add("x-ms-version", "2015-08-06");
    httpClient.DefaultRequestHeaders.Add("x-ms-documentdb-is-upsert", "true");

    var resourceLink = string.Format("dbs/{0}/colls/{1}", DatabaseId, CollectionId);
    var baseUrl = new Uri(documentDbUrl);

    var masterKeyAuthorizationSignatureGenerator = new MasterKeyAuthorizationSignatureGenerator();
    var authHeader = masterKeyAuthorizationSignatureGenerator.Generate("POST", resourceLink, "docs", authorizationKey, "master", "1.0", utcNow);
    httpClient.DefaultRequestHeaders.Add("authorization", authHeader);

    var response = await httpClient.PostAsJsonAsync(new Uri(baseUrl, resourceLink + "/docs"), userSettings);
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.