Question:
I was trying to deploy cosmos database using Cosmos DB REST Api. I’m using a function to build the authorisation header and I got the script from https://gallery.technet.microsoft.com/scriptcenter/How-to-query-Azure-Cosmos-0a9aa517 link. It works perfectly fine for GET & POST however when I tried to execute a PUT command I’m always getting below error.
Invoke-RestMethod : The remote server returned an error: (401)
Unauthorized.
Im trying to update the offer for Cosmos collection but it always ends with the error and I couldn’t understand whats the reason. I also checked my headers and authorisation with Microsoft documentation and looks fine to me. Refer https://learn.microsoft.com/en-us/rest/api/documentdb/replace-an-offer for Uri and headers required. My request and response are below
Request
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
PUT https: //mycosmosdb.documents.azure.com:443/offers/mycollection HTTP/1.1 authorization: type % 3dmaster % 26ver % 3d1.0 % 26sig % 3dIgWkszNS % 2b94fUEyrG8frByB2PWSc1ZEszc06GUeuW7s % 3d x - ms - version: 2017 - 02 - 22 x - ms - date: Wed, 02 Aug 2017 08: 40: 37 GMT User - Agent: Mozilla / 5.0(Windows NT; Windows NT 10.0; en - US)WindowsPowerShell / 5.1.15063.483 Content - Type: application / json Host: mycosmosdb.documents.azure.com Content - Length: 269 { "offerVersion": "V2", "offerType": "Invalid", "content": { "offerThroughput": 500, "offerIsRUPerMinuteThroughputEnabled": false }, "resource": "dbs/xterf==/colls/STuexopre=/", "offerResourceId": "STuexopre=", "id": "xiZw", "_rid": "xiZw" } |
Response
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
HTTP / 1.1 401 Unauthorized Transfer - Encoding: chunked Content - Type: application / json Content - Location: https: //mycosmosdb.documents.azure.com/offers/variantstockquantity Server: Microsoft - HTTPAPI / 2.0 x - ms - activity - id: 6f7be3c8 - cfa2 - 4d5e - ad69 - fb14ef218980 Strict - Transport - Security: max - age = 31536000 x - ms - gatewayversion: version = 1.14.57.1 Date: Wed, 02 Aug 2017 08: 40: 35 GMT 163{ "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: 'put\noffers\mycollection\nwed, 02 aug 2017 08:40:37 gmt\n\n'\r\nActivityId: 6f7be3c8-cfa2-4d5e-ad69-fb14ef218980" } 0 |
My Powershell Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
Function Generate-MasterKeyAuthorizationSignature { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)][String]$verb, [Parameter(Mandatory=$true)][String]$resourceLink, [Parameter(Mandatory=$true)][String]$resourceType, [Parameter(Mandatory=$true)][String]$dateTime, [Parameter(Mandatory=$true)][String]$key, [Parameter(Mandatory=$true)][String]$keyType, [Parameter(Mandatory=$true)][String]$tokenVersion ) $hmacSha256 = New-Object System.Security.Cryptography.HMACSHA256 $hmacSha256.Key = [System.Convert]::FromBase64String($key) If ($resourceLink -eq $resourceType) { $resourceLink = "" } $payload = "$($verb.ToLowerInvariant())`n$($resourceType.ToLowerInvariant())`n$resourceLink`n$($dateTime.ToLowerInvariant())`n`n" $hashPayload = $hmacSha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($payload)) $signature = [System.Convert]::ToBase64String($hashPayload); [System.Web.HttpUtility]::UrlEncode("type=$keyType&ver=$tokenVersion&sig=$signature") } Function Modify-Offer { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)][String]$DocumentDBApi, [Parameter(Mandatory=$true)][String]$EndPoint, [Parameter(Mandatory=$true)][String]$MasterKey, [Parameter(Mandatory=$true)][String]$CollectionName ) $Verb = "PUT" $ResourceType = "offers"; $ResourceLink = "offers" $body = '{ "offerVersion": "V2", "offerType": "Invalid", "content": { "offerThroughput": 500, "offerIsRUPerMinuteThroughputEnabled": false }, "resource": "dbs/xterf==/colls/STuexopre=/", "offerResourceId": "STuexopre=", "id": "xiZw", "_rid": "xiZw" }' $dateTime = [DateTime]::UtcNow.ToString("r") $authHeader = Generate-MasterKeyAuthorizationSignature -verb $Verb -resourceLink $ResourceLink -resourceType $ResourceType -key $MasterKey -keyType "master" -tokenVersion "1.0" -dateTime $dateTime $header = @{authorization=$authHeader;"x-ms-version"=$DocumentDBApi;"x-ms-date"=$dateTime} $contentType= "application/json" $queryUri = "$EndPoint$ResourceLink/$CollectionName" $result = Invoke-RestMethod -Method $Verb -ContentType $contentType -Uri $queryUri -Headers $header -Body $body $result | ConvertTo-Json -Depth 10 } Modify-Offer -EndPoint $CosmosDBEndPoint -MasterKey $MasterKey -DocumentDBApi $DocumentDBApiVersion -CollectionName $ColName |
Can someone throw me some help as why my PUT requests are failed with authorisation error, what I’m missing and how can I correct it.
Answer:
Response message clearly states used payload for verification. Tracing ‘$payLoad’ in Generate-MasterKeyAuthorizationSignature will quickly revel the issue.
You need to address at-least below two issues for this to work
- RepalceOffer documentation states RID of the offer, instead you are
passing the collection name. - ResourceLin hardcoded: $ResourceLink
= “offers” in Modify-Offer where as it needs to point to the RID of the resource.
Here is slightly modified code which should do job
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
Function Generate-MasterKeyAuthorizationSignature { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)][String]$verb, [Parameter(Mandatory=$true)][String]$resourceLink, [Parameter(Mandatory=$true)][String]$resourceType, [Parameter(Mandatory=$true)][String]$dateTime, [Parameter(Mandatory=$true)][String]$key, [Parameter(Mandatory=$true)][String]$keyType, [Parameter(Mandatory=$true)][String]$tokenVersion ) $hmacSha256 = New-Object System.Security.Cryptography.HMACSHA256 $hmacSha256.Key = [System.Convert]::FromBase64String($key) If ($resourceLink -eq $resourceType) { $resourceLink = "" } $payLoad = "$($verb.ToLowerInvariant())`n$($resourceType.ToLowerInvariant())`n$resourceLink`n$($dateTime.ToLowerInvariant())`n`n" $hashPayLoad = $hmacSha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($payLoad)) $signature = [System.Convert]::ToBase64String($hashPayLoad); Write-Host $payLoad [System.Web.HttpUtility]::UrlEncode("type=$keyType&ver=$tokenVersion&sig=$signature") } Function Modify-Offer { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)][String]$DocumentDBApi, [Parameter(Mandatory=$true)][String]$EndPoint, [Parameter(Mandatory=$true)][String]$MasterKey, [Parameter(Mandatory=$true)][String]$OfferRID ) $Verb = "PUT" $ResourceType = "offers"; $body = '{ "offerVersion": "V2", "offerType": "Invalid", "content": { "offerThroughput": 600, "offerIsRUPerMinuteThroughputEnabled": false }, "resource": "dbs/xterf==/colls/STuexopre=/", "offerResourceId": "STuexopre=", "id": "xiZw", "_rid": "xiZw" }' $dateTime = [DateTime]::UtcNow.ToString("r") $authHeader = Generate-MasterKeyAuthorizationSignature -verb $Verb -resourceLink $OfferRID -resourceType $ResourceType -key $MasterKey -keyType "master" -tokenVersion "1.0" -dateTime $dateTime $header = @{authorization=$authHeader;"x-ms-version"=$DocumentDBApi;"x-ms-date"=$dateTime} $contentType= "application/json" $queryUri = "$EndPoint$ResourceType/$OfferRID" $result = Invoke-RestMethod -Method $Verb -ContentType $contentType -Uri $queryUri -Headers $header -Body $body $result | ConvertTo-Json -Depth 10 } Modify-Offer -EndPoint $CosmosDBEndPoint -MasterKey $MasterKey -DocumentDBApi $DocumentDBApiVersion -OfferRID $ColName |
Other alternative recommended approach if possible is to consume client SDK in Powershell. Here is a sample code which updates first offer of the account.
1 2 3 4 5 6 7 8 9 10 |
Add-Type -Path "...\Microsoft.Azure.Documents.Client.dll" $client=New-Object Microsoft.Azure.Documents.Client.DocumentClient($CosmosDBEndPoint, $MasterKey) $offersEnum=$client.ReadOffersFeedAsync().Result.GetEnumerator(); if ($offersEnum.MoveNext()) { $targetOffer=$offersEnum.Current $offerUpdated=New-Object Microsoft.Azure.Documents.OfferV2($targetOffer, 600, $FALSE) $client.ReplaceOfferAsync($offerUpdated).Result } |