Skip to content

hamdiceylan/heimdall

Repository files navigation

 Heimdall

Downloads Version
NuGet Total NuGet Version

Easy to use HMAC Digest Authentication for WebAPI with various client implementations (C#, NodeJS and Browser versions)

##How it works

Let's first take a look at the component parts. Heimdall is broken up into two distinct logical parts, namely, server and client. Both create compatible signed messages using the the individual message representation for each request using certain key dimensions. So for example if we were to make a get request like the one below:

Insecure GET Request

A simple vanilla request with no authentication.

Headers

Accept: */*
Content-Type: application/json

Path

GET /api/mysecureresource/1

You would require a username and a secret to sign the message, this is already implemented for you in a Heimdall C# and JavaScript client which we will cover in more detail later on. Once the message is sent to the server a delegating handler will then verify the message and then decide whether it is valid or not. So if our username was 'username' and the secret was 'secret' then our example request would now look something like this:

Heimdall GET Request

A request generated by a Heimdall client.

Headers

Accept: */*
Content-Type: application/json
X-ApiAuth-Username: username
X-ApiAuth-Date: Thu, 23 Jul 2015 10:48:42 GMT
Authorization: ApiAuth CLcQbLlK3HajC/PPpwwxLoqHCnCrlM1VBjN8TGnYjuM=

Path

GET /api/mysecureresource/1

Here you can see how Heimdall calculates an authorisation hash using the message representation and adds additional headers so that the server can identify the user in order to rebuild the authorisation hash on the server. If these hashes match, then the request is allowed through, if not then a http response code 401 will be returned.

##Server

To install the Heimdall delegating handler in your WebAPI project you have two options. As always there is a Castle Windsor version that can be automatically picked up by FluentWindsor or if you are not using proper IoC then there is a vanilla version. Let's take a look at both.

###IGetSecretFromUsername

You will need to supply an implementation of IGetSecretFromUsername, without this you wont be able to instantiate your delegating handler. This is important because it does a server side lookup of the users secret and uses that to create the comparison cryptographic hash and ultimately compares that to what is in the Authorisation header. Below is a contrived example of what this looks like, typically you would use a persistence medium.

public class DummyGetSecretFromUsername : IGetSecretFromUsername
{
    public string Secret(string username)
    {
        if (username.ToLower().Equals("username"))
            return "secret";
        return string.Empty;
    }
}

If you are using FluentWindsor you have two options, you can either install it via the service locator(least preferred) or create a new IWindsorInstaller for it. Let's take a look at both:

FluentWindsor.ServiceLocator.Register(Component.For<IGetSecretFromUsername>().ImplementedBy<DummyGetSecretFromUsername>());

OR

public class ProvidersInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component.For<IGetSecretFromUsername>().ImplementedBy<DummyGetSecretFromUsername>());
    }
}

####Excluding Paths

You can also exclude certain paths from Heimdall if you would not like any security applied to a particular end point or resource. This can be done like so in your application start up sequence:

HeimdallConfig.AllowPath("/api/values");

###Installing

Now lets take a look at the packages you will need below. Pick only one.

####Heimdall.Server

This is the non Castle Windsor version. First start by installing the NuGet Heimdall.Server. Once this is complete you have to add the delegating handler to your WebAPI configuration on application startup in your Global.asax file like so:

// Manually install heimdall
var authenticateRequest = new AuthenticateRequest(new DummyGetSecretFromUsername());
GlobalConfiguration.Configuration.MessageHandlers.Add(new HmacAuthenticationHandler(authenticateRequest));

####Heimdall.Server.Windsor

This is the Windsor ready version that will be automatically picked up and installed if you using FluentWindsor. If you are using FluentWindsor already then you dont have to do anything. By merely installing the NuGet your WebAPI will be secured providing you have supplied a implementation for IGetSecretFromUsername.

This exclude the entire path from Heimdall. Plans in the near future are to allow for specific verbs.

##Clients

There are 3 versions of clients that can be found in the examples folder of the source code for Heimdall. Let's take a look at each one starting with the C# client first.

###The C# Client

This client comes in two flavours. One that is Castle Windsor ready(for use with FluentWindsor) or one without. Use the latter if you are not using Castle Windsor as your IoC container.

####Heimdall.Client

This is the non windsor version. Start by installing the Heimdall.Client NuGet. Once this is done let's look at how you would make a simple signed get request using a HttpClient.

HttpClient client = HeimdallClientFactory.Create("myusername", "mysecret");
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("firstName", "Alex"),
    new KeyValuePair<string, string>("lastName", "Brown")
});
var result = client.PostAsync("http://requestb.in/14nmm871", content).Result;

If you would like to see a working example of this, please see the console application .\examples\Example.Client after opening the solution in Visual Studio.

####Heimdal.Client.Windsor

Let's look at how we initialise a client using FluentWindsor. First start by installing the Heimdall.Client.Windsor NuGet. Next you would have to make sure that FluentWindsor is initialised(this should only be called once on startup).

FluentWindsor.NewContainer(typeof(Program).Assembly).WithArrayResolver().WithInstallers().Create();

This will automatically pick up the IWindsorInstaller and install an instance of the IHeimdallClientFactory which can then be injected for consumption via any constructor known to the container. For demonstration purposes we are going to use the ServiceLocator of FluentWindsor.

var client = FluentWindsor.ServiceLocator.Resolve<IHeimdallClientFactory>().Create("username", "secret");
var content = new FormUrlEncodedContent(new[]
{
    new KeyValuePair<string, string>("firstName", "Alex"),
    new KeyValuePair<string, string>("lastName", "Brown")
});

var result = client.PostAsync("http://requestb.in/14nmm871", content).Result;

If you would like to see a working example of this, please see the console application .\examples\Example.Client.FluentWindsor after opening the solution in Visual Studio.

###The NodeJs Client

A proper client has not yet been published to NPM but we have plans to do this in the near future. This is an example of how you would roll a Heimdall request using NodeJs.

####Dependencies

First start by installing the REST client 'request' like so:

npm install request

Once this is done, create a file called app.js and place the following code at the top of the file. These are the two libraries we would require, one for doing requests and one for doing encryption so we can hash message representations.

var request = require('request');
var crypto = require('crypto');

####Encryption

Next you are going to need a little utility function that can do HMAC encryption using SHA256 with base64 encoding like so:

function encrypt(data, secret) {
    var hmacSignature = crypto.createHmac("sha256", secret || '');
    hmacSignature.update(messageRepresentation);
    return hmacSignature.digest("base64");
}

####Message representation

Next lets set about the task of building up a message representation. A message representation is logically comprised of the following elements:

HTTP Method ['GET','POST','PUT','DELETE']
HTTP Path [Example: '/api/values']
Content-Type Header [Example: 'application/json']
Content-MD5 Header [Content Checksum, applicable to 'POST','PUT']
Timstamp: [Example: Thu, 23 Jul 2015 13:04:27 GMT]

So now let's go ahead and create our message representation in JavaScript like so:

var httpMethod = 'GET';
var httpPath = '/api/values';
var contentType = 'application/json';
var contentMD5 = '';
var timestamp = new Date().toUTCString();
var messageRepresentation = [httpMethod, httpPath, contentType, contentMD5, timestamp].join('\n');

You will notice that the contentMD5 portion is empty. This is intentional as GET requests do not have content in their body. A POST or PUT however does generally have a body. You can easily setup a hash using the encrypt function without a secret like so:

var body = { any:'value' };
var contentMD5 = encrypt(JSON.stringify(body));

####Wrapping it up

Next let's build our request object and finally make the request to our Heimdall example server, pay special attention to what is going on with the headers:

var req = {
    url: 'http://localhost:12345/api/values',
    headers: {
        'X-ApiAuth-Date': timestamp,
        'X-ApiAuth-Username': 'username',
        'Content-MD5': contentMD5,
        'Content-Type': 'application/json',
        'Authorization': 'ApiAuth ' + encrypt(messageRepresentation, 'secret')
    }
};

console.log('Request: ');
console.log(req);
console.log();

request(req, function (error, response, body) {
    if (!error) {
        console.log("Response:");
        console.log(response.statusCode);
        console.log(response.body);
    } else {
        console.log(error);
    }
});

Download the source and start up the Example.IIS project followed by running the NodeJs example at the following location:

https://github.com/cryosharp/heimdall/blob/master/Example.Client.NodeJs/app.js

###The Pure Js Client

Here is another example of a Heimdall client that runs in a browser. Very useful if you are doing web development. You will need a few helper libraries to achieve this namely crypto-js and jquery. You can get both using bower, not sure if crypto-js is available on NuGet.

####Dependencies

If you have NodeJs installed then run the following commands:

npm install bower -g
bower install jquery
bower install crypto-js

The bower packages should be installed in a folder called bower_components in the directory from which you ran the console commands above.

####Script references

Next you would need to put the following script references somewhere in your document:

<script src="~/Scripts/bower_components/jquery/dist/jquery.min.js"></script>
<script src="~/Scripts/bower_components/crypto-js/crypto-js.js"></script>

Make sure that the relative path matches to the location from where you installed the bower packages. Next you will need to manually embed the browser based Heimdall JS client. You can do this by copying the file from the link below:

https://github.com/cryosharp/heimdall/blob/master/Example.IIS/Scripts/heimdall.js

You can also embed this using a script tag like the following:

<script src="~/Scripts/heimdall.js"></script>

####Example requests

Make sure this script reference is placed after the jquery and crypto script reference or else it wont work. Once complete you are then free to start rolling Heimdall browser based requests using the following javascript:

//Example GET
var _h = new Heimdall('http://localhost:12345', 'username', 'secret');

_h.get('/api/values', function (err, res) {
    if (!err) {
        alert('GET Success!');
    } else {
        alert('GET Error!');
    }
});

Similarly if you were doing a POST or PUT request where you have a body then you would do something like so:

//Example GET
var _h = new Heimdall('http://localhost:12345', 'username', 'secret');
_h.post('/api/values', 'hello world', function(err, res) {
    if (!err) {
        alert('POST Success!');
    } else {
        alert('POST Error!');
    }
});

There are also plans to make this client available on bower in the near future.

##Problems?

For any problems please sign into github and raise issues.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published