Wiring up a custom authentication method with OWIN in Web API Part 2: the headers

Introduction

In the previous post we discussed the main topic of this series and started building a demo application with a single Customers controller. The goal of this series is to show how to register your custom authentication mechanism with OWIN as a Katana component.

In this post we’ll be concentrating on the request headers. We’ll also see a couple of functions that will later be part of the OWIN middleware when we’re ready to convert the code.

Reading the request headers

First let’s quickly see how to get the headers from a request. We have a Customers controller with single Get action method. Every controller has access to a Request property which holds the values of the incoming request. If you type…

Request.

…within the body of the Get method then IntelliSense will give you a range of useful properties and methods. The Headers property will hold as it says: the headers of the current request. It is of type HttpRequestHeaders which resides in the System.Net.Http.Headers namespace.

Note that there’s a HttpRequestHeaders object in the System.Web namespace as well. However, that’s used in MVC web projects, as opposed to the Web API whose components are mainly located in the System.Net.* namespace. A number of objects are available in both MVC and Web API that have the same name but are located in different libraries. This can lead to some confusion when you’re importing the object from the incorrect library and you don’t have access to the same properties and methods.

We can enumerate the headers in a number of ways. We’ll go for the ToList extension method that converts the headers into a list of key value pairs. The key is the header name and the value is a list of values of that header. A single header can have multiple values, hence the list of strings as the value in the key-value pair.

The following bit of code within the Get action method in CustomersController.cs…:

HttpRequestHeaders requestHeaders = Request.Headers;
List<KeyValuePair<string, IEnumerable<string>>> headers = requestHeaders.ToList();

foreach (var kvp in headers)
{
	string values = string.Join(";", kvp.Value.ToList());
	Debug.WriteLine("Header {0}: {1}", kvp.Key, values);
}

…will list the header names and values in the debug window. I got the following header values when running the customers controller:

Header Connection: keep-alive
Header Accept: text/html;application/xhtml+xml;application/xml; q=0.9;image/webp;*/*; q=0.8
Header Accept-Encoding: gzip;deflate;sdch
Header Accept-Language: sv-SE;sv; q=0.8;en-US; q=0.6;en; q=0.4;hu; q=0.2;nb; q=0.2
Header Cookie: __test=1; lastUrl=kJo7k8BIbQYlRrs7QjggtprIQIw6RmfvtzuDrdOOM/0hS54QUjUEVJaf98QLMeyYWZCTmxwP0yk8Z2hUqTrMaG/NpAGv5qeT; rememberme=false; m=34e2:|ca3:t|4a01:t|47ba:t
Header Host: localhost:4086
Header User-Agent: Mozilla/5.0;(Windows NT 6.1; WOW64);AppleWebKit/537.36;(KHTML, like Gecko);Chrome/46.0.2490.80;Safari/537.36
Header Upgrade-Insecure-Requests: 1

The HttpRequestHeaders has the TryGetValues method to test for the presence of a certain header. It returns true if the header was found. The header values are returned in an “out” parameter:

IEnumerable<string> acceptLanguageValues;
bool acceptLanguageHeaderPresent = requestHeaders.TryGetValues("Accept-Language", out acceptLanguageValues);
if (acceptLanguageHeaderPresent)
{
	acceptLanguageValues.ToList().ForEach(h => Debug.WriteLine(h));
}

…which gives the following output for me:

sv-SE
sv; q=0.8
en-US; q=0.6
en; q=0.4
hu; q=0.2
nb; q=0.2

Adding request headers

Next we’ll see how to add request headers in a calling application. Add a C# console application to the solution. You can call it “CustomAuthenticationTester” or something similar. Add the following Web API portable client library to it from NuGet:

Web API client library in NuGet

The package will install all the libraries necessary for calling a Web API endpoint.

Here’s the complete example code in Program.cs that will send a request to the /customers endpoint and set the x-company-auth header value:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CustomAuthenticationTester
{
	class Program
	{
		static void Main(string[] args)
		{
			string username = "elvispresley";
			string delimiter = "|";
			int pin = GenerateRandomPin();

			string customAuthHeaderValue = string.Concat(username, delimiter, pin);

			HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, "http://localhost:4086/customers");
			requestMessage.Headers.Add("x-company-auth", customAuthHeaderValue);
			HttpClient httpClient = new HttpClient();
			httpClient.Timeout = TimeSpan.FromMinutes(60);
			Task<HttpResponseMessage> httpRequest = httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, CancellationToken.None);
			HttpResponseMessage httpResponse = httpRequest.Result;
			HttpStatusCode statusCode = httpResponse.StatusCode;
                        Debug.WriteLine("Response status code: {0}", statusCode);
			HttpContent responseContent = httpResponse.Content;

			if (responseContent != null)
			{
				Task<String> stringContentsTask = responseContent.ReadAsStringAsync();
				String stringContents = stringContentsTask.Result;
				Debug.WriteLine(stringContents);
                                Debug.WriteLine("Response headers: ");
				foreach (var header in responseContent.Headers)
				{
					string values = string.Join(";", header.Value.ToList());
					Debug.WriteLine("Response header {0}: {1}", header.Key, string.Join(";", values));
				}
			}
		}

		private static int GenerateRandomPin()
		{
			Random r = new Random(DateTime.UtcNow.Millisecond);
			int pin = r.Next(100000, 1000000);
			return pin;
		}
	}
}

Make sure you modify the port number in the target URL if necessary. It is 4068 in my local solution but it’s probably different for you. We set the connection timeout to a large value – an hour – so that the client doesn’t throw an exception if you want to step through the code in the Web API demo. Finally we read the string content from the response and also extract the response headers.

Run the web project first and then start an instance of the console application. You should see that the new header value is available in the web project:

Header x-company-auth: elvispresley|743182

Also, the response was correctly extracted in the console tester:

[{“Name”:”Nice customer”,”Address”:”USA”,”Telephone”:”123345456″},{“Name”:”Good customer”,”Address”:”UK”,”Telephone”:”9878757654″},{“Name”:”Awesome customer”,”Address”:”France”,”Telephone”:”34546456″}]

I got the following response headers:

Response header Content-Length: 201
Response header Content-Type: application/json; charset=utf-8
Response header Expires: -1

…and the following status code:

Response status code: OK

Processing the custom authentication header

We’ll put the custom header processing logic in a private method in CustomersController to begin with. We’ll keep the logic simple: if the PIN is greater than or equal to 500000 then we accept the request, otherwise it’s rejected. Obviously a proper implementation will check the list of PIN numbers, see if the next PIN is equal to the one in the header and reject the call in case of a mismatch. However, a full implementation would mean a lot of diversion from the main topic right now.

The following method in CustomersController will be enough to start with:

private bool IsAuthorised(HttpRequestHeaders requestHeaders)
{
	IEnumerable<string> acceptLanguageValues;
	bool acceptLanguageHeaderPresent = requestHeaders.TryGetValues("x-company-auth", out acceptLanguageValues);
	if (acceptLanguageHeaderPresent)
	{
		string[] elementsInHeader = acceptLanguageValues.ToList()[0].Split(new char[]{'|'}, StringSplitOptions.RemoveEmptyEntries);
		if (elementsInHeader.Length == 2)
		{
			int pin;
			if (int.TryParse(elementsInHeader[1], out pin))
			{
				if (pin >= 500000)
				{
					return true;
				}
			}
		}
	}

	return false;
}

That should be fairly simple to follow. We try to extract the x-company-auth header and process its value. Finally we decide based on the PIN whether the call is authenticated or not.

We can call this method from the Get action method as follows:

if (!IsAuthorised(requestHeaders))
{
	AuthenticationHeaderValue headerValue = new AuthenticationHeaderValue("WWW-Authenticate", "x-company-auth");
	return Unauthorized(headerValue);
}

We return an UnauthorizedResult from the Unauthorized method of the Web API.

The Unauthorized method requires one or more AuthenticationHeaderValue objects. These are the so-called challenges in case the call is not authenticated. Here we simply return a WWW-Authenticate header with the required request header type.

You can test the applications a couple of times. As soon as PIN is less than 500000 the console application receives the following response status:

Response status code: Unauthorized

We’ll continue our discussion in the next part.

You can view the list of posts on Security and Cryptography here.

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

One Response to Wiring up a custom authentication method with OWIN in Web API Part 2: the headers

  1. Pingback: Wiring up a custom authentication method with OWIN in Web API Part 2: the headers | Dinesh Ram Kali.

Leave a comment

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.