2

I've been struggling with what appears to be a common problem: formatting my authorization header for the Azure Table Service REST API. I have been unable to find an example using PowerShell and SharedKey, and am worried that I am making some dumb mistake in working backwards from other examples.

The specific (though unspecific) error is: Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.

I have been referencing these articles, as well as other examples:

I've confirmed that my key is correct, the table exists, has at least one row, and a number of other suggestions without luck.

If there is another approach I should take to accomplish the same goal, I'm all ears. I would like to use the REST API to allow for maximum compatibility on the clients.

$tableEndpoint = 'https://STORAGEACCOUNTNAME.table.core.windows.net/'
$tableName = 'TABLENAME'
$StorageAccountName = 'STORAGEACCOUNTNAME'
$Key = "STORAGEACCOUNTKEY"

Function New-AuthorizationHeader
{
    param ($canonicalizedString)

    [byte[]]$Bytes = [system.convert]::FromBase64String($Key)
    $HMACSHA256 = [System.Security.Cryptography.HMACSHA256]::new($Bytes)
    $dataToHmac = [System.Text.Encoding]::UTF8.GetBytes($canonicalizedString)
    $Signature = [System.Convert]::ToBase64String($HMACSHA256.ComputeHash($dataToHmac))

    [string]$AuthorizationHeader = "{0} {1}:{2}" -f "SharedKey",$StorageAccountName,$Signature

    $AuthorizationHeader
}

Function New-Entity
{
    param ($jsonContent)

    $requestMethod = "POST"
    $contentMD5 = [string]::Empty
    $storageServiceVersion = '2016-05-31'
    $reqDate = (Get-Date -Format r)
    $contentType = "application/json"
    $canonicalizedResource = "/{0}/{1}" -f $StorageAccountName,($tableEndpoint + $tableName)

    $stringToSign = "{0}`n{1}`n{2}`n{3}`n{4}" -f $requestMethod,$contentMD5,$contentType,$reqDate,$canonicalizedResource

    $authorizationHeader = New-AuthorizationHeader -canonicalizedString $stringToSign
    $content = [System.Text.Encoding]::UTF8.GetBytes($jsonContent)

    $fullURI = New-Object -TypeName System.Uri -ArgumentList ($tableEndpoint + $tableName)
    $httpWebRequest = [System.Net.WebRequest]::Create($fullURI)
    $httpWebRequest.Accept = 'application/json;odata=fullmetadata'
    $httpWebRequest.ContentLength = $content.length
    $httpWebRequest.ContentType = $contentType
    $httpWebRequest.Method = $requestMethod
    $httpWebRequest.Headers.Add("x-ms-date", $reqDate)
    $httpWebRequest.Headers.Add("x-ms-version", $storageServiceVersion)
    $httpWebRequest.Headers.Add("Authorization", $authorizationHeader)
    $httpWebRequest.Headers.Add("Accept-Charset", "UTF-8")
    $httpWebRequest.Headers.Add("DataServiceVersion", "3.0;NetFx")
    $httpWebRequest.Headers.Add("MaxDataServiceVersion", "3.0;NetFx")

    $requestStream = $httpWebRequest.GetRequestStream()
    $requestStream.Write($content, 0, $content.length)
    $requestStream.Close()

    $response = $httpWebRequest.GetResponse()
    $dataStream = $response.GetResponseStream()

    $reader = New-Object -TypeName System.IO.StreamReader($dataStream)

    $responseFromServer = $reader.ReadToEnd()
}

$jsonContent = @"
{
    "ExecutionStatus"="smapledata",
    "PartitionKey"="$ENV:Username",
    "RowKey"="PrinterScript"
}
"@

New-Entity -jsonContent $jsonContent

2 Answers 2

0

Please make 2 changes above:

  1. Canonical resource should not have table endpoint. So it should be:

    $canonicalizedResource = "/{0}/{1}" -f $StorageAccountName,$tableName

  2. JSON body should be properly formatted. So it should be:

    $jsonContent = @" { "ExecutionStatus":"smapledata", "PartitionKey":"$ENV:Username", "RowKey":"PrinterScript" } "@

Once you make these changes, the code should work just fine.

2
  • Hi Gaurav, thank you for the response. I've made your recommended changes, but am seeing the same results. My JSON formatting was incorrect, which I fixed.
    – Matthew
    Mar 14, 2017 at 20:36
  • If you're still seeing 403 errors, please check for 2 things: 1) Your account name/key combination is correct and 2) The clock on the machine where you're running the code is not slow by more than 15-20 minutes. I was able to use your code and with my updates and successfully created an entity. Mar 15, 2017 at 2:19
0

Thank you again for your responses, Gaurav.

I verified that my clock is not skewed. In the end, I switched to using a Shared Access Signature, which is probably a better practice anyway.

Using a SAS obviates the need for an Authorization header (and getting that correctly formatted).

Here's the relevant updated PowerShell:

$tableEndpoint = 'https://STORAGEACCOUNT.table.core.windows.net/'
$tableName = 'TABLENAME'
$SAS = "?sv=2016-05-31&ss=t&srt=o&sp=wa&se=2017-09-01T04:08:11Z&st=2017-03-14T20:08:11Z&spr=https&sig=SIGNATURE"
$URI = $tableEndpoint + $tableName + $SAS
If (-NOT $script:RunLogKeyTime) 
{
    $script:RunLogKeyTime = (Get-Date -Format 'yyyyMMdd-HHmmss')
}

$RequestBody = ConvertTo-Json -InputObject @{
        "TagetName"= $TargetName;
        "Message"= $Message;
        "ComputerName"= $ENV:ComputerName;
        "Username"= $ENV:Username;
        "EntryType"= $EntryType;
        "PartitionKey"= "$Username`_$ScriptIdentifier";
        "RowKey"= "$EntryType"}

$EncodedRequestBody = [System.Text.Encoding]::UTF8.GetBytes($RequestBody)

$RequestHeaders = @{
    "x-ms-date"=(Get-Date -Format r);
    "x-ms-version"="2016-05-31";
    "Accept-Charset"="UTF-8";
    "DataServiceVersion"="3.0;NetFx";
    "MaxDataServiceVersion"="3.0;NetFx";
    "Accept"="application/json;odata=nometadata";
    "ContentLength"=$EncodedRequestBody.Length}

Invoke-WebRequest -Method POST -Uri $URI -Headers $RequestHeaders -Body $EncodedRequestBody -ContentType "application/json"

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.