Ben Ben - 3 months ago 36
ASP.NET (C#) Question

how to authenticate documentdb stored procedure REST call

EDIT: Problem was parameters.ToString(). Also like Lengning Liu pointed out you need to add '/sprocs/sprocname'

I'm trying to execute a stored procedure in Azure DocumentDB with a REST call in ASP.NET Core 1.0 / C#.

I use the method on this MSDN page to generate the hash:
https://msdn.microsoft.com/library/azure/dn783368.aspx

I'm getting a 401 Unauthorized repsonse. Is "sprocs" the right document type, or do i have to enter a different type?

public class Database
{

public async Task<HttpResponseMessage> StoredProcedure(string database, string collection, string storedProcedure, string[] parameters)
{
//https://{databaseaccount}.documents.azure.com/dbs/{db-id}/colls/{coll-id}/sprocs/{sproc-name}

string resourceLink = $"dbs/{database}/colls/{collection}";
var client = Client("POST", resourceLink, "sprocs");

StringContent content = new StringContent(parameters.ToString(), null, "application/query+json");
var uri = new Uri(_endpointUri, $"dbs/{database}/colls/{collection}/sprocs/{storedProcedure}");
HttpResponseMessage response = await client.PostAsync(uri, content);

return response;
}

private HttpClient Client(string verb, string resourceLink, string resourceType)
{
var client = new HttpClient();
var utc_date = DateTime.UtcNow.ToString("r");
client.DefaultRequestHeaders.Add("x-ms-date", utc_date);
client.DefaultRequestHeaders.Add("x-ms-version", "2015-12-16");
client.DefaultRequestHeaders.Add("x-ms-max-item-count", "10000");

var authHeader = GenerateMasterKeyAuthorizationSignature(utc_date, verb, resourceLink, resourceType, _authorizationKey, "master", "1.0");

client.DefaultRequestHeaders.Add("authorization", authHeader);

return client;
}

private static string GenerateMasterKeyAuthorizationSignature(string utc_date, string verb, string resourceId, string resourceType, string key, string keyType, string tokenVersion)
{

var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(key) };

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

byte[] hashPayLoad = hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payLoad));
string signature = Convert.ToBase64String(hashPayLoad);

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

Answer

it seems that when you generated the auth token, in the resourceId part, you left out "/sprocs/{storedProcedure}". You have included it in the uri, which is correct.

I am attaching a sample powershell script that, hopefully, will also help you to see how auth token is generated.

Add-Type -AssemblyName System.Web 

$accountName  = "<db account name>"
$connectionKey = "<secret key>"
$collectionName = "<coll name>"
$databaseName = "<db name>"

Write-host ("Account " + $accountName)
Write-host ("Database  " + $databaseName)
Write-host ("Collection " + $collectionName)


    function GetKey([System.String]$Verb = '',[System.String]$ResourceId = '',
            [System.String]$ResourceType = '',[System.String]$Date = '',[System.String]$masterKey = '') {
        $keyBytes = [System.Convert]::FromBase64String($masterKey) 
        $text = @($Verb.ToLowerInvariant() + "`n" + $ResourceType.ToLowerInvariant() + "`n" + $ResourceId + "`n" + $Date.ToLowerInvariant() + "`n" + "" + "`n")
        $body =[Text.Encoding]::UTF8.GetBytes($text)
        $hmacsha = new-object -TypeName System.Security.Cryptography.HMACSHA256 -ArgumentList (,$keyBytes) 
        $hash = $hmacsha.ComputeHash($body)
        $signature = [System.Convert]::ToBase64String($hash)

        Write-Host($text)

        [System.Web.HttpUtility]::UrlEncode($('type=master&ver=1.0&sig=' + $signature))

    }

    function GetUTDate() {
        $date = get-date
        $date = $date.ToUniversalTime();
        return $date.ToString("r", [System.Globalization.CultureInfo]::InvariantCulture);
    }

    function GetDatabases() {
        $uri = $rootUri + "/dbs"

        $hdr = BuildHeaders -resType dbs

        $response = Invoke-RestMethod -Uri $uri -Method Get -Headers $hdr
        $response.Databases

        Write-Host ("Found " + $Response.Databases.Count + " Database(s)")

    }

    function GetCollections([string]$dbname){
        $uri = $rootUri + "/" + $dbname + "/colls"
        $headers = BuildHeaders -resType colls -resourceId $dbname
        $response = Invoke-RestMethod -Uri $uri -Method Get -Headers $headers
        $response.DocumentCollections
        Write-Host ("Found " + $Response.DocumentCollections.Count + " DocumentCollection(s)")
   }

    function BuildHeaders([string]$action = "get",[string]$resType, [string]$resourceId){
        $authz = GetKey -Verb $action -ResourceType $resType -ResourceId $resourceId -Date $apiDate -masterKey $connectionKey
        $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
        $headers.Add("Authorization", $authz)
        $headers.Add("x-ms-version", '2015-12-16')
        $headers.Add("x-ms-date", $apiDate) 
        $headers
    }

    function PostDocument([string]$document, [string]$dbname, [string]$collection){
        $collName = "dbs/"+$dbname+"/colls/" + $collection
        $headers = BuildHeaders -action Post -resType docs -resourceId $collName
        $headers.Add("x-ms-documentdb-is-upsert", "true")
        $uri = $rootUri + "/" + $collName + "/docs"

        Write-host ("Calling " + $uri)

        $response = Invoke-RestMethod $uri -Method Post -Body $json -ContentType 'application/json' -Headers $headers
        $response
    }

    function PostSprocQuery([string]$dbname, [string]$collection){
        $sprocName = "dbs/"+$dbname+"/colls/" + $collection + "/sprocs/samplesproc"
        $headers = BuildHeaders -action Post -resType sprocs -resourceId $sprocName
        $uri = $rootUri + "/" + $sprocName

        Write-host ("Calling " + $uri)
        write-host $authz
        write-host $apiDate

        $response = Invoke-RestMethod $uri -Method Post -Body $json -ContentType 'application/json' -Headers $headers
        $response
    }

    $rootUri = "https://" + $accountName + ".documents.azure.com"
    write-host ("Root URI is " + $rootUri)

    #validate arguments

    $apiDate = GetUTDate

    $db = GetDatabases | where { $_.id -eq $databaseName }

    if ($db -eq $null) {
        write-error "Could not find database in account"
        return
    } 

    $dbname = "dbs/" + $databaseName
    $collection = GetCollections -dbname $dbname | where { $_.id -eq $collectionName }

    if($collection -eq $null){
        write-error "Could not find collection in database"
        return
    }

    $json = @"
{
    "id": "3"
}
"@ 
    PostDocument -document $json -dbname $databaseName -collection $collectionName

    $json = @"
[
    "samplesproc"
]
"@ 
    PostSprocQuery -document $json -dbname $databaseName -collection $collectionName