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
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
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
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.
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
Here is slightly modified code which should do job
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.
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With