Web API 2 security extensibility points part 2: custom authentication filter

Introduction

In the previous post we introduced the topic and main goals of this series. We also set up a demo Web API 2 project which we’ll use throughout. We also briefly investigated the HTTP request context and how we could extract information about the current user of the HTTP request from it.

In this post we’ll see how to write your own custom authentication filter attribute. We’ll only look at a simple example to show how to access the request context from an authentication filter. Further down you’ll find a reference to a post on http://www.asp.net which takes up a much more detailed example.

Authentication filters

If you’ve done any MVC .NET programming where user authentication is required then you’ll be familiar with the built-in authentication filters like AuthorizeAttribute. There’s nothing stopping us from writing our own authentication filter with our own custom authentication logic. That’s exactly what we’ll do here.

Open the CustomersApi project we started to build in the previous post. Add a new folder called Filters and in that folder add a new class called CustomAuthenticationFilterAttribute. It needs to derive from the Attribute class and implement the Web API 2 version of the IAuthenticationFilter. This latter point is important because there’s an IAuthenticationFilter in the System.Web.Mvc namespace but we need the one in the System.Web.Http namespace which is specific for Web API usage.

After the necessary using statements you should end up with the below stub:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http.Filters;

namespace CustomersApi.Filters
{
	public class CustomAuthenticationFilterAttribute : Attribute, IAuthenticationFilter
	{
		public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
		{
			throw new NotImplementedException();
		}

		public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
		{
			throw new NotImplementedException();
		}

		public bool AllowMultiple
		{
			get { throw new NotImplementedException(); }
		}
	}
}

Let’s start with the easiest case, the AllowMultiple property. According to the documentation on MSDN this boolean property…

“Gets or sets a value indicating whether more than one instance of the indicated attribute can be specified for a single program element.”

Clearly we only want to apply our authentication filter once on a controller or action method so let’s implement it to return false:

public bool AllowMultiple
{
	get { return false; }
}

AuthenticateAsync is relatively straightforward. As the name implies we’ll need to implement our authentication logic within this method. The MSDN documentation – “Authenticates the request.” – confirms this assumption. However, the fact that it returns a Task might look strange if you’re new to the asynchronous programming model in .NET4.5 and the async/await keywords. If you don’t know what these mean then start here.

ChallengeAsync is more mysterious. I haven’t found any documentation of this method on MSDN. However, this post on ASP.NET has a an excellent and detailed example of implementing the AuthenticateAsync and ChallengeAsync methods.

Within the body of the AuthenticateAsync method we can read the Principal from the HttpAuthenticationContext.ActionContext.RequestContext or directly from HttpAuthenticationContext.Principal. You can also set the Principal in the current request context.

The below implementation example checks the incoming principal and sets a new GenericPrincipal for the request context. We also provide a dummy implementation for ChallengeAsync so that it doesn’t throw a not implemented exception:

public class CustomAuthenticationFilterAttribute : Attribute, IAuthenticationFilter
{
	public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
	{
		await Task.Run(() =>
		{
			IPrincipal incomingPrincipal = context.ActionContext.RequestContext.Principal;
			Debug.WriteLine(String.Format("Incoming principal in custom auth filter AuthenticateAsync method is authenticated: {0}", incomingPrincipal.Identity.IsAuthenticated));
			IPrincipal genericPrincipal = new GenericPrincipal(new GenericIdentity("Andras", "CustomIdentification"), new string[] { "Admin", "PowerUser" });
			context.Principal = genericPrincipal;
		});		
	}

	public async Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
	{
		await Task.Run(() =>
		{
			IPrincipal incomingPrincipal = context.ActionContext.RequestContext.Principal;
			Debug.WriteLine(String.Format("Incoming principal in custom auth filter ChallengeAsync method is authenticated: {0}", incomingPrincipal.Identity.IsAuthenticated));
		});		
	}

	public bool AllowMultiple
	{
		get { return false; }
	}
}

You can decorate the controller with the custom authentication filter if you want it to run before each action method in that controller:

[CustomAuthenticationFilter]
public class CustomersController : ApiController

You can also set it on an action method level so that the filter only applies to that method:

[CustomAuthenticationFilter]
public IHttpActionResult Get()

If you want to apply the custom filter to all controllers by default you can register it in Global.asax:

GlobalConfiguration.Configuration.Filters.Add(new CustomAuthenticationFilterAttribute());

If you then run the /customers endpoint you’ll see an output similar to the following:

Incoming principal in custom auth filter AuthenticateAsync method is authenticated: False
Incoming principal in custom auth filter ChallengeAsync method is authenticated: True
Principal authenticated from extension method: True
Principal authenticated from shorthand property: True
Principal authenticated from User: True

As we set the principal in the authentication filter the user is authenticated from that point on.

In the next post we’ll see how we can build custom message handlers for the CustomerApi demo application. Message handlers run before any controller action or custom filters are executed. They can also be used to intercept the calls to your API with custom authentication logic.

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.

6 Responses to Web API 2 security extensibility points part 2: custom authentication filter

  1. kfir says:

    why do i get this error:
    System.Web.Http.controllers.HttpActionContext does not contain a definition for RequestContext

  2. Steve says:

    I copied your filter code to my ASP.NET Web API project. I changed the namespace accordingly. When I try to decorate my controller method with [CustomAuthenticationFilter]
    it shows the error: “The type or namespace name ‘CustomAuthenticationFilter’ could not be found.” I tried adding using Web_API.Filters; to my controller class (where Web_API is the root namespace of my project, but it won’t take that either. Am I missing something else in order for my controller to recognize the custom filter?

  3. Steve says:

    Andras, you can disregard my earlier message. I’m not sure where the problem was, but Visual Studio seemed to have corrupted the project. I re-loaded your code, re-built my project, and now it works. When I call a controller method decorated with the authentication attribute it hits my breakpoint in your filter code. Now I think I can play with this filter method and do the custom authentication that I want to implement. Thanks mucho.

  4. lavamantis says:

    A+ tutorial. The MS docs are not helpful. Thanks.

  5. I love this post, well written to the point. Thank you!

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.