Swagger UI: Custom HMAC hash authentication headers

Last year I launched a new API for an integration project. It’s using Microsoft’s WebApi framework. I was looking for a fast way to document the API so I wouldn’t have to do much work and clients could use the API easily. After some research it was obvious that I needed Swagger.

Swagger

Swagger (https://swagger.io) is an open source framework that makes it really easy to design, build and document APIs. At it’s core, it’s a specification for describing APIs. Once you have an API documented in swagger you get access to a huge number of free tools. There are tools available for automatically generating clients on rails, iOS, node, angular or Android and it has tools for generating detailed html documentation right off of your specification.

Swashbuckle

Swashbuckle (https://github.com/domaindrivendev/Swashbuckle) is an awesome wrapper around some of the swagger tools for .Net WebApi projects. You can just install it from nuget package manager. After installing, if you visit https://your-api.com/swagger you will see the resulting documentation. There are loads of options to make the documentation better and describe your API, mostly using attributes and XML comments on the controllers and models.

The hmac authentication problem

Swagger-ui supports HTTP Basic Authentication and OAuth2. Our API supports a cookie token authentication and it allows some endpoints to be accessed using an HTTP request hashing authentication based on Amazon Web Services (http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html). I needed to find a way to get swagger-ui to accept the credentials from the user, hash the details of the request and finally add the hash in to some headers we read on the server.

It took a bit of trial and error and some investigation to figure it out so I want to share it here.

Adding custom javascript and css to the swagger-ui page

You can inject css and javascript files onto the swagger-ui index using the swashbuckle configuration as you can see below. You just need add them in the configuration section as below.

I use moment.js to handle datetime manipulation in javascript – used for getting UTC time in ISO 8601 format. enc-base64, sha256 and hmac-sha1 are all from Google’s CryptoJS project. You can get them here: https://cdnjs.com/libraries/crypto-js. Import them using whatever module loader you prefer.

The content files must be set to Build Action: Embedded Resource in your .Net solution. Then you refer to them in the configuration using their resource names which is basically AssemblyName.Folder.Path.Filename.js.

Embedded Resource

 

.EnableSwaggerUi(c =>
{
c.InjectStylesheet(assemblyType, "Api.SwaggerExtensions.swaggeroverrides.css");

c.InjectJavaScript(assemblyType, "Api.SwaggerExtensions.hmac-sha1.js");
c.InjectJavaScript(assemblyType, "Api.SwaggerExtensions.moment.js");
c.InjectJavaScript(assemblyType, "Api.SwaggerExtensions.enc-base64.js");
c.InjectJavaScript(assemblyType, "Api.SwaggerExtensions.sha256.js");
c.InjectJavaScript(assemblyType, "Api.SwaggerExtensions.apihmacheaderauth.js");

c.CustomAsset("index", assemblyType, "Api.SwaggerExtensions.custom.index.html");
}

The custom index.html is just used to set some titles. You should change the custom.html as little as possible so you can easily upgrade to the next version of Swashbuckle or swagger.

Overriding the swagger authentication

Everything for the HMAC authentication is done in apihmacheaderauth.js. The swagger-ui library exposes an authorizations pattern that gives you access to the HTTP request context and all of it’s properties as you can see below. I’ve over commented and consoled just for this tutorial.

This file adds some custom headers for authentication and authorization based on Amazon’s hmac pattern for AWS. I’ll get in to why I chose to do this in another post. In retrospect it was too heavy handed for sure. It also has to use custom headers rather than the Authorization header to lazily support multiple Authentication scenarios with precedence. Sorry about the lack of indentation, it got destroyed by wordpress.

(function () {
$(function () {

/*I add the ui elements I need here. I thought it would be better put them in here at runtime rather than hard code them in the index.html in case the index.html from my version of swashbuckle is made obsolete in a future version of the package. */
var hmacAuthUi =
'
<div class="input"><label for="input_api_username">Api Username: </label><input placeholder="Api Username" id="input_api_username" name="input_api_username" type="text" size="20"></div>
' +
'
<div class="input"><label for="input_api_hmackey">Api Key: </label><input placeholder="ApiKey" id="input_api_hmackey" name="input_api_hmackey" type="text" size="20"></div>
' +
'
<div class="input"><label for="input_customer_api_username">Customer Username: </label><input placeholder="User Name" id="input_customer_api_username" name="input_customer_api_username" type="text" size="20"></div>
';
$(hmacAuthUi).insertBefore('#api_selector div.input:last-child');

/*Hide the standard controls provided by the swagger-ui index.html. We won't need them. */
$("#input_apiKey").hide();
$("#input_baseUrl").hide();

/* This just sets a default value for this username field  so we don;t have to enter everytime in test*/

$("#input_api_username").val("testUser");

var CustomHmacRequestSigner = function (name) {
this.name = name;
};

/* This is the main hashing function. It takes the HTTP request context and uses various parts of that request to create a hash */

function customHash(obj, datestamp, apiKey) {
var stringToSign = obj.method + obj.url + datestamp;
if (obj.body) {
var bodyUtf8 = CryptoJS.enc.Utf8.parse(obj.body);
console.log('utf8 of body: ' + bodyUtf8);
var bodysha256 = CryptoJS.SHA256(bodyUtf8);
console.log('sha256 of utf8 of body: ' + bodysha256);
var bodybase64Hash = CryptoJS.enc.Base64.stringify(bodysha256);
console.log('base 64 of body hash: ' + bodybase64Hash);
stringToSign += bodybase64Hash;
}
console.log('raw string to sign: ' + stringToSign);
var utf8StringToSign = CryptoJS.enc.Utf8.parse(stringToSign);
console.log('utf8 string to sign: ' + utf8StringToSign);
var utf8OfApiKey = CryptoJS.enc.Utf8.parse(apiKey);
console.log('utf8 api key: ' + utf8OfApiKey);
var hash = CryptoJS.HmacSHA1(utf8StringToSign, utf8OfApiKey);
console.log('hmac sha1 hash of string with key: ' + hash);
var base64Hash = CryptoJS.enc.Base64.stringify(hash);
console.log('base 64 of hash: ' + base64Hash);
return base64Hash;
}

/* This is the function that is called every time the ui tries to make a request. We take the username and key that the user has entered in to the form fields. We hash the request with those values and add some new headers. This is where you can do any authentication you might want to do. */

CustomHmacRequestSigner.prototype.apply = function (obj, authorizations) {
var datestamp = moment().toISOString();
var hashFunction = customHash;
var apiKey = $('#input_api_hmackey').val();
var customerUserName = $('#input_api_customer_username').val();
var apiUserName = $('#input_api_username').val();

var hash = hashFunction(obj, datestamp, apiKey);

obj.headers['X-Custom-Identity'] = customerUserName;
obj.headers['X-Custom-Authorization'] = apiUserName + ":" + hash;
obj.headers['X-Custom-Timestamp'] = datestamp;

return true;
};
authorizations.add("custom-hmac-authorization", new CustomHmacRequestSigner());
});
})();

Here are the new fields on my index page that replace the HTTP basic authentication for swagger

Showing new fields on html page

And here is the console output of the HMAC authentication when I try to make a request

Console output for request

That’s it! I hope it helps you.

One thought to “Swagger UI: Custom HMAC hash authentication headers”

  1. I have tried out your script and it works for most of it. However, when it came to the hashing function, none of the CryptoJS libraries worked. The only way to get it working was by injecting also the “crypto-js.js” library before all of the other libraries. Your tutorial does not mention that library. I am mentioning this for future reference to other tutorial users.

Leave a Reply

Your email address will not be published. Required fields are marked *