0

When calling CosmosDB API frequently via APIM send-request sometimes I get 401 Unauthorized and sometimes 200 which I expect. I tried generating same request and sending it frequently using Postman and all the time the response is 200.

Here is the APIM policy code snippet:

    <set-variable name="currentDate" value="@(DateTime.UtcNow.ToString("r"))" />
    <set-variable name="userMetadataCosmosDBKey" value="{{UserMetadataCosmosDBKey}}" />
    <set-variable name="userMetadataCosmosDBUri" value="{{UserMetadataCosmosDBUri}}" />
    <set-variable name="userMetadataResourceId" value="{{UserMetadataResourceId}}" />
    <set-variable name="token" value="@{
      var verb = "POST";
      var resourceType = "docs";
      var resourceId = (string)context.Variables["userMetadataResourceId"];
      var date = (string)context.Variables["currentDate"];
      var key = (string)context.Variables["userMetadataCosmosDBKey"];
      var keyType = "master";
      var tokenVersion = "1.0";
      var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(key) };                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
      verb = verb ?? "";
      resourceType = resourceType ?? "";
      resourceId = resourceId ?? "";
      string payLoad = string.Format("{0}\n{1}\n{2}\n{3}\n{4}\n",
      verb.ToLowerInvariant(),
      resourceType.ToLowerInvariant(),
      resourceId,
      date.ToLowerInvariant(),
      "");
      byte[] hashPayLoad = hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payLoad));
      string signature = Convert.ToBase64String(hashPayLoad);
      return String.Format("type={0}&ver={1}&sig={2}",
        keyType,
        tokenVersion,
        signature);        
     }" />
    <send-request mode="new" response-variable-name="userMetadataResponse" timeout="20" ignore-error="false">
        <set-url>@(new Uri(string.Format("{0}{1}/docs",(string)context.Variables["userMetadataCosmosDBUri"],(string)context.Variables["userMetadataResourceId"])).ToString())</set-url>
        <set-method>POST</set-method>
        <set-header name="x-ms-documentdb-isquery" exists-action="override">
            <value>true</value>
        </set-header>
        <set-header name="Content-Type" exists-action="override">
            <value>application/query+json</value>
        </set-header>
        <set-header name="x-ms-version" exists-action="override">
            <value>2017-02-22</value>
        </set-header>
        <set-header name="x-ms-date" exists-action="override">
            <value>@((string)context.Variables["currentDate"])</value>
        </set-header>
        <set-header name="x-ms-query-enable-crosspartition" exists-action="override">
            <value>true</value>
        </set-header>
        <set-header name="Authorization" exists-action="override">
            <value>@((string)context.Variables["token"])</value>
        </set-header>
        <set-body>@{  
                        return new JObject(  
                            new JProperty("query","SELECT * FROM c WHERE c.APIMUserId=@apiUserId"),  
                            new JProperty("parameters", 
                                new JArray(
                                    new JObject(  
                                        new JProperty("name","@apiUserId"),  
                                        new JProperty("value", context.User.Id)
                                        )
                                    )
                                )
                            ).ToString();  
                    }</set-body>
    </send-request>
    <!-- Store response body in userMetadata variable -->
    <set-variable name="userMetadata" value="
      @{
         var response = ((IResponse)context.Variables["userMetadataResponse"]).Body.As<JObject>(true);                                                                                                                                                                                                           
         var metadata = response["_count"].ToString() == "1" ? response["Documents"][0] : new JObject();                                                                                                                                                                                                                                                
         return metadata;                                                                                                                                                                                                                                                
       }" />

Any thought why I am getting 401 sometimes?

2
  • please check out if my solutions helps stackoverflow.com/questions/45600323/… Commented Jan 26, 2018 at 5:34
  • Thanks @KaiWalter I guess the difference between your implementation and mine is the partition key which I don't think I need to set 'x-ms-documentdb-partitionkey' in my header. Commented Jan 28, 2018 at 11:25

2 Answers 2

3

Got it working by adding System.Uri.EscapeDataString() to the returned token:

System.Uri.EscapeDataString(String.Format("type={0}&ver={1}&sig={2}",
                                    keyType,
                                    tokenVersion,
                                    signature));
Sign up to request clarification or add additional context in comments.

Comments

0

You can also use

<authentication-managed-identity 
    resource="https://cosmon-samples-234f1f22a3.documents.azure.com" 
    output-token-variable-name="msi-access-token" 
    ignore-error="false" />

<set-header name="Authorization" exists-action="override">
    <value>@("type=aad&ver=1.0&sig=" + context.Variables["msi-access-token"])</value>
</set-header>

to authenticate APIM requests to CosmosDB. Only make sure that you have granted permissions in CosmosDB RBAC https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-setup-rbac

Full sample with all the details can be found in my article https://byalexblog.net/article/azure-apimanagement-to-azure-cosmosdb/

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.