piotrwalat.net

HMAC authentication in ASP.NET Web API

5 comments

In this article I will explain the concepts behind HMAC authentication and will show how to write an example implementation for ASP.NET Web API using message handlers. The project will include both server and client side (using Web API’s HttpClient) bits.

HMAC based authentication

HMAC (hash-based message authentication code) provides a relatively simple way to authenticate HTTP messages using a secret that is known to both client and server. Unlike basic authentication it does not require transport level encryption (HTTPS), which makes its an appealing choice in certain scenarios. Moreover, it guarantees message integrity (prevents malicious third parties from modifying contents of the message).

On the other hand proper HMAC authentication implementation requires slightly more work than basic HTTP authentication and not all client platforms support it out of the box (most of them support cryptographic algorithms required to implement it though). My suggestion would be to use it only if HTTPS + basic authentication does not suit your requirements.

One prominent example of HMAC usage is Amazon S3 service.

The basic idea behind HMAC authentication in HTTP can be described as follows:

  • both client and server have access to a secret that will be used to generate HMAC – it can be a password (or preferably password hash) created by the user at the time of registration,
  • using the secret client generates a message signature using HMAC algorithm (the algorithm is provided by .NET ‘for free’),
  • signature is attached to the message (eg. as a header) and the message is sent,
  • the server receives the message and calculates its own version of the signature using the secret (both client and server use the same HMAC algorithm),
  • if the signature computed by the server matches the on the message it means that the message is authorized.

As you can see the secret key (eg. password hash) is only shared between client and server once (eg. during user registration). Noone will be able to produce a valid signature without the access to the secret also any modification of the message (eg. appending content) will result in server calculating a different signature and refusing authorization.

Broadly speaking to create a HMAC authenticated client/server pair using ASP.NET Web API we need:

  • method that will return a string representing given http request,
  • method that based on secret string and message representation calculates HMAC signature,
  • client side – message handler that uses these methods to calculate the signature and attaches it to the request (as HTTP header),
  • server side – message handler that calculates signature of incoming request and compares it with the one contained in the header.

Web API client

Ok, so let’s start by writing the first piece.

A couple of points worth mentioning:

  • we construct message representation by concatenating ‘important’ headers, http method and uri,
  • instead of using incorporating the content we use its md5 hash (base64 encoded),
  • all parts of the message (eg. headers) that can affect its meaning and have side effects on the server side should be included in the representation (otherwise an attacker would be able to modify them without changing the signature).

Now lets look at that component that will calculate authentication code (signature).

The signature will be encoded using base64 so that we can pass it easily in a header. What header you may ask? Well, unfortunately there is no standard way of  including message authentication codes into the message (as there is no standard way of constructing message representation). We will use Authorization HTTP header for that purpose providing a custom schema (ApiAuth).

The HMAC will be calculated and attached to the request in a custom message handler.

In a real life scenario you could retrieve the hashed password from the a persistent store (a database). If you remember how we constructed our message representation you will notice that we also need to set content MD5 header. We could do it in HmacSigningHandler, but to have separation of concerns and because Web API allows us to combine handlers in a neat way I moved it to a separate (dedicated) handler.

For simplicity the HMAC handler derives directly from HttpClientHandler. Here is how we would make a request:

And that’s basically it as far as http client is concerned. Let’s have a look at server part.

Web API service

The general logic will be that we will want to authenticate every incoming request (we can us per route handlers to secure only one route for example). Each request’s authentication code will be calculated using the very same IBuildMessageRepresentation and ICalculateSignature implementations. If the signature does not match (or the content md5 hash is different from the value in the header) we will immediately return a 401 response.

The bulk of work is done by IsAuthenticated() method. Also please note that we do not sign the response, meaning the client will not be able verify the authenticity of the response (although response signing would be easy to do given components that we already have). I have omitted IsMd5Valid() method for brevity, it basically compares content hash with MD5 header value (just remember not to compare byte[] arrays using == operator).

Configuration part is simple and can look like that (per route handler):

Replay attack prevention

There is one very important flaw in the current approach. Imagine a malicious third party intercepts a valid (properly authenticated) HTTP request coming from a legitimate client (eg. using a sniffer). Such a message can be stored and resent to our server at any time enabling attacker to repeat operations performed previously by authenticated users. Please note that new messages still cannot be created as the attacker does not know the secret nor has a way of retrieving it from intercepted data.

To help us fix this issue lets make following three observations/assumptions about dates of  requests in our system:

  • requests with different Date header values will have different signatures, thus attacker will not be able to modify the timestamp,
  • we assume identical, consecutive messages coming from a user will always have different timestamps – in other words that no client will want to send two or more identical messages at a given point in time,
  • we introduce a requirement that no http request can be older than X (eg. 5) minutes – if for any reason the message is delayed for more than that it will have to be resent with a refreshed timestamp.

Once we know the above we can introduce following changes into IsAuthenticated() method:

For simplicity I didn’t test the example for sever and client residing in different timezones (although as long as we normalize the dates to UTC we should be save here).

The code is available as usually on bitbucket.

Hope this article helps some of you!

Written by Piotr Walat

February 28th, 2013 at 10:45 am