In one of my projects where I’ve been refactoring a traditional .NET project into a .NET Core project, I used the Azure Storage nugets.
As of this posting, the current version of the NuGet supports .NET Core which is awesome - but the dependencies doesn’t.
Note: Since this was originally published, the NuGets have been updated, and all SDKs and dependencies fully support all versions of .NET Core. I am now back to using those.
If you are using the REST API, continue reading!
(When this was published) I chose to use the Azure Storage REST API instead for all my things.
The authentication/authorization bits were not really clear. Well, clear as mud perhaps - the documentation is there, but it’s quite confusing and lacks any good samples. So with that, I decided to make a sample.
Enjoy this tip-of-the-day post, and feel free to drop me a comment or e-mail.
Authenticate Azure Storage REST requests in C#
Everything I’ve built is based on information from this page: Authentication for the Azure Storage Services.
Pre-requisites
In order to use this code, there’s a few pre-requisites that I’d like to note down:
- You should have an Azure Storage account.
- You should have your Storage Account Key.
- You should have your Storage Account Secret.
- NO need for the Storage Connection string.
- The
Clientobject in my code is a normalnew HttpClient();
Required Headers
As mentioned in the public documentation, there’s a few headers that are required as of this posting:
- Date
- Authorization
The rest of the headers are optional, but depending on what operations you want to do, and which service you’re targeting, they will differ. This is focused on Table Storage currently, but can be applied to others as well.
Creating the Date Header
This is a required header, and the easiest way to demonstrate how to build it is like this:
var RequestDateString = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);
if (Client.DefaultRequestHeaders.Contains("x-ms-date"))
Client.DefaultRequestHeaders.Remove("x-ms-date");
Client.DefaultRequestHeaders.Add("x-ms-date", RequestDateString);
If there’s already a Date header present, remove it and add it again with the proper value.
Creating the Authorization Header
This is where the tricky part came into play. Seeing it now in retrospective, it’s fairly straight forward - but before figuring out in what order, and how to properly encode this header it was a slight struggle.
var StorageAccountName = "YourStorageAccountName";
var StorageKey = "YourStorageAccountKey";
var requestUri = new Uri("YourTableName(PartitionKey='ThePartitionKey',RowKey='TheRowKey')");
if (Client.DefaultRequestHeaders.Contains("Authorization"))
Client.DefaultRequestHeaders.Remove("Authorization");
var canonicalizedStringToBuild = string.Format("{0}\n{1}", RequestDateString, $"/{StorageAccountName}/{requestUri.AbsolutePath.TrimStart('/')}");
string signature;
using (var hmac = new HMACSHA256(Convert.FromBase64String(StorageKey)))
{
byte[] dataToHmac = Encoding.UTF8.GetBytes(canonicalizedStringToBuild);
signature = Convert.ToBase64String(hmac.ComputeHash(dataToHmac));
}
string authorizationHeader = string.Format($"{StorageAccountName}:" + signature);
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("SharedKeyLite", authorizationHeader);
As you can see, it’s not entirely straight forward. These are the steps and things to consider:
- You first decode the StorageKey from Base64
- You then pass this into the constructor for the HMACSHA256 class
- You then create a byte[] array to get the UTF8 bytes from the canonicalizedStringToBuild (the date + request information)
- You then Base64 encode the hmac.ComputeHash() results
- The result of this, is your signature, in combination with adding the Storage Account Name, so it ends up as a string looking like this format:
StorageAccountName: Signature. - Then you create a new Authorization Header called
Authorizationas you can see in the snippet above, withSharedKeyLiteand your signature added.
I’m going to be honest. This took some time to figure out - but once it was working, it’s blazingly fast and I love it.
Accept Header
Since I want the response to be application/json this is exactly what I need to tell the request:
Client.DefaultRequestHeaders.Accept.Clear();
Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Request version
I’m specifying which version to use, so if there’s new versions coming out I am still targeting the one I know works throughout all of my unit tests and tenants using the code.
This is done using the x-ms-version header.
if (Client.DefaultRequestHeaders.Contains("x-ms-version"))
Client.DefaultRequestHeaders.Remove("x-ms-version");
Client.DefaultRequestHeaders.Add("x-ms-version", "2015-12-11");
DataService Version Headers
Since I’m working with entities, I need to specify the DataServiceVersion headers as such:
if (Client.DefaultRequestHeaders.Contains("DataServiceVersion"))
Client.DefaultRequestHeaders.Remove("DataServiceVersion");
Client.DefaultRequestHeaders.Add("DataServiceVersion", "3.0;NetFx");
if (Client.DefaultRequestHeaders.Contains("MaxDataServiceVersion"))
Client.DefaultRequestHeaders.Remove("MaxDataServiceVersion");
Client.DefaultRequestHeaders.Add("MaxDataServiceVersion", "3.0;NetFx");
If-Match Header
In my specific case I’m doing PUT and DELETE operations sometimes, and when doing that, there’s an additional required header you need. The If-Match header.
if (httpMethod == HttpMethod.Delete || httpMethod == HttpMethod.Put)
{
if (Client.DefaultRequestHeaders.Contains("If-Match"))
Client.DefaultRequestHeaders.Remove("If-Match");
// Currently I'm not using optimistic concurrency :-(
Client.DefaultRequestHeaders.Add("If-Match", "*");
}
Known issues: Forbidden: Server failed to authenticate the request.
Before I hit the jackpot on how to format my Authorize header, it generated a lot of different errors. The most common one though, being this:
Status Code: Forbidden, Reason: Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
There was no “easy fix” for this, as it simply meant the header was incorrect for Authorization - but it doesn’t state what is incorrect or malformed (which I suppose is good, for security). So after a lot of Fiddler4 magic and experimentation with this, I could resolve the issue and the code you see in this post is the one that is currently (2016-11-01) working as expected throughout all of my projects.
Resources
The snippets here are part of a bigger project of mine, hence I can’t easily share the entire source. However, should you be inclined in a full working sample, please drop a comment and if there’s enough interest perhaps I’ll create a new github project for it.
- Required headers for querying entities (Microsoft Docs)
- Authentication for the Azure Storage Services (Microsoft Docs)
Comments are closed
Archived comments
YES! Thaaaank you for this! was struggling for long time to get working an query to rest api from also .netcore. i can make query fine now and work with adding this headers correct!!! THX!!
Hi Adrian,
Glad I could help. Cheers :-)
Tobias.
Hi Tobias,
Do you know where I can go to find APIs to get details of my Azure account (eg. spending, costs by resource etc-from the Portal)? I landed on this from your article https://docs.microsoft.com/... but that or anything in Googles returns what I'm after..
cheers (nice articles)
Hi there. I am trying to do a simple POST (using a JSON body) using the Azure REST API. Everything seems to work 100% when I use ("x-ms-version", "2015-07-08") - but as soon as I start using ("x-ms-version", "2015-12-11") and up - I start running into 415 error's. Any idea's what might be going wrong there..? I change nothing else...
Hi @geoffleroux:disqus,
I've been using the Azure API's in various degree the last few months and years, and the versions defined in the docs should always be used. When changing the version, it likely means changes in the API backend, which could result in unexpected behavior. So even if it's a "simple" change to the version number in your code, it could be a big change behind the scenes which works a bit differently.
The only way I've managed to cope with these changes is to have rigorous unit tests and integration tests in place, which runs on every commit. Then whenever we upgrade versions, we can usually see where it fails, if it fails.
I hope this helps.
Tobias.
I'm thinking about taking the same path as you. The Azure Storage package isn't truly asynchronous because of the OData dependency.
For those implementing their own Azure Storage client using HttpClient, please make sure to re-use the HttpClient as much as possible - or just make it static.
hi Tobias Zimmergren,
I have a topic that needs your help
i want to get all the Scale out (App Service plan) setting from Azure (C# code)
could you help me step by step ?
thank you very much !
Thanks u saved my day
Hi @sandeep_bhaskar:disqus ,
Thanks for your comment. I'm glad it works for you - I had my fair bit of stuggle with this myself before figuring it out ;-)
Cheers,
Tobias.
hey can i have his one in javascript, any links or content available to work around
Hey @sandeep_bhaskar:disqus ,
Sorry, I've only done things in dotnet core related to these API's.
If you stumble onto something, I wouldn't mind that you share it here :)
Tobias.
Hey I'm getting 403 error when I try to update entity even after adding all the required headers , using PutAsync method, can you send me the code to update entity using httpclient