Authenticating Web API with Hawk

  2014-03-30


Security is not easy. There are a lot of options when it comes to implementing web security and it takes a rather deep understanding of the security landscape just to be able to even pick the right technology for your specific case. In the most recent feat to secure my new Web API project, I ended up spending more than a week reading about web security and trying to find the right solution for my problem and getting grasp of security fundamentals.

My goal was to use Mozilla Person as an Identity Provider. However once user was authenticated with Persona I needed to be able to issue Web API requests.

The interesting thing about Web API is that it doesn’t support sessions, and while it is possible to enable session state, it is not a recommended practice, since the idea of the Web API is to embrace the statelessness of the HTTP protocol. This means that one needs to authenticate each HTTP request separately and independently of other requests.

Our options boil down to these three

  • Basic authentication. The problem with Basic authentication, however is that it sends credentials in plaintext (or the value might be hashed) and therefore is vulnerable to Man In The Middle attacks and replay attacks. So unless you are using TLS, Basic authentication isn’t a viable option.

  • Digest access authentication. This is a viable solution and suitable for authentication over non-secure channels (without TLS). The downside of this approach is that it generates quite a bit more HTTP requests to perform the handshake and therefore can be noticeably slower on lower Internet connection speeds.

  • HMAC authentication. This is the recommended strategy for clients not using TLS. With this approach, each HTTP is augmented with a limited-lifetime authentication token. This token is generated cryptographically and therefore is not susceptible to MITM attacks. The replay attacks are possible, but its risk can be minimized by limiting the token’s expiration to a short window of time (e.g. - 60 seconds).

After extensive reading, it was clear to me that HMAC authentication was the way to go. Luckily there is a very nice implementation of the HMAC authentication - Hawk - which was written by Eran Hammer (one of the original creators of OAuth). For our use case, all that is needed, is the browser.js file, which will take care of signing the requests.

On the server side we would need a mechanism to decrypt the Hawk authentication token and to validate it. This is easily done with Thinktecture.IdentityModel.Hawk nuget.

Below is the detailed description of the steps I’ve taken to implement basic setup of the Hawk authentication for my Web API.

1. Pull in your dependencies

Once you you have created your Web API project, you will need to

Install-Package Thinktecture.IdentityModel.Hawk

Then you will need to copy browser.js into your ~/scripts folder.

2. Create ApiController

Now let’s create the HelloWorldController which we will want to protect:

public class HelloController : ApiController
{
    [Authorize]
    public string Get()
    {
        return "Hi " + this.User.Identity.Name + "!";
    }
}

3. Set project properties

Next you’ll want to ensure that your project properties resemble the ones in the screenshot:

project-properties

4. Enable Hawk authentication

To make things simpler I created this class

using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using Thinktecture.IdentityModel.Hawk.Core;
using Thinktecture.IdentityModel.Hawk.Core.Helpers;
using Thinktecture.IdentityModel.Hawk.Core.MessageContracts;
using Thinktecture.IdentityModel.Hawk.WebApi;

public class HawkAuthenticator
{
    public static void EnableHawkAuthentication(HttpConfiguration config)
    {
        var options = new Options
        {
            ClockSkewSeconds = 60, 
            LocalTimeOffsetMillis = 0, 
            CredentialsCallback = CredentialsCallback, 
            ResponsePayloadHashabilityCallback = r => true, 
            VerificationCallback = OnVerificationCallback
        };

        var handler = new HawkAuthenticationHandler(options);
        config.MessageHandlers.Add(handler);
    }

    private static bool OnVerificationCallback(IRequestMessage request, string ext)
    {
        if (string.IsNullOrEmpty(ext))
        {
            return true;
        }

        const string Name = "X-Request-Header-To-Protect";
        return ext.Equals(Name + ":" + request.Headers[Name].First());
    }

    private static Credential CredentialsCallback(string id)
    {
        var credentialStorage = new List
        {
            new Credential
            {
                Id = "123", 
                Algorithm = SupportedAlgorithms.SHA256, 
                User = "John", 
                Key = "secret"
            }
        };

        return credentialStorage.FirstOrDefault(c => c.Id == id);
    }
}

and the called it from the ~/App_Start/WebApiConfig.cs:

public class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional });

        HawkAuthenticator.EnableHawkAuthentication(config);
    }
}

Now our server is ready to accept requests authenticated with Hawk token.

5. Write the client

Now when making requests to the Web API, we need to sign each request using browser.js like so:

<a id="gethello" href="#">say hello</a>

<script src="/Scripts/jquery.js"></script>
<script src="/Scripts/browser.js"></script>
<script>
    (function () {
        var url = 'http://localhost:50374/api/hello';

        var credentials = {
            id: '123',
            key: 'secret',
            algorithm: 'sha256'
        };

        var req = {
            method: 'GET',
            url: url,
            host: 'localhost',
            port: 50374
        };

        $("#gethello").click(function () {
            var header = hawk.client.header(url, req.method, { credentials: credentials });

            $.ajax({
                dataType: "json",
                headers: { Authorization: header.field },
                url: url,
                success: function (error, response, body) {

                    var isValid = hawk.client.authenticate(body, credentials, header.artifacts, {
                        payload: body.responseText,
                        required: true
                    });

                    console.log(isValid ? body.responseText : "not valid");
                }
            });
        });
    }());
</script>

And that’s it.

If you inspect the http request you will see each request signed with the Authorization header and server responds with the Server-Authorization header, which can be used by the client to ensure that the response is authentic.

f12 console

I am by no means a security professional, so if you spot any problems with any of my advice, I will be more than happy to hear it in the comments below. Cheers!

22bugs.co © 2017. All rights reserved.