Basic HTTP authentication in ASP.NET Web API using message handlers

In one of my previous posts I was investigating how to implement Basic HTTP authentication in ASP.NET Web API by extending AuthorizeAttribute (controller or action level filter). This approach has several disadvantages including quite messy implementation and necessity to couple concrete authentication mechanism with an abstract (i.e. authentication mechanism agnostic) [Authorize] attribute.

To address these problems we can implement a custom message handler (by deriving from DelegateHandler class) that will provide Basic HTTP authentication mechanism.

The actual work done by the handler will be twofold:

  1. to intercept HttpRequestMessage and check for Authorization header being present (in which case we want to authenticate user with credentials provided)
  2. to intercept HttpResponseMessage and make sure that for every 401 http response we inform client of expected authentication method (by including WWW-Authenticate header in http response)
Remember that Basic HTTP authentication sends credentials in plaintext (unencrypted) which means you must use secure transport layer (SSL) to provide encryption.

Let's jump into code as it is pretty straightforward. To implement custom message handler, we simply derive DelegatingHandler class and override SendAsync(HttpRequestMessage request,CancellationToken cancellationToken) method.

public class BasicAuthMessageHandler : DelegatingHandler  
{
    private const string BasicAuthResponseHeader = "WWW-Authenticate";
    private const string BasicAuthResponseHeaderValue = "Basic";

    public IProvidePrincipal PrincipalProvider { get; set; }

    protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        AuthenticationHeaderValue authValue = request.Headers.Authorization;
        if (authValue != null && !String.IsNullOrWhiteSpace(authValue.Parameter))
        {
            Credentials parsedCredentials = ParseAuthorizationHeader(authValue.Parameter);
            if (parsedCredentials != null)
            {
                Thread.CurrentPrincipal = PrincipalProvider
                    .CreatePrincipal(parsedCredentials.Username, parsedCredentials.Password);
            }
        }
        return base.SendAsync(request, cancellationToken)
           .ContinueWith(task =>
           {
               var response = task.Result;
               if (response.StatusCode == HttpStatusCode.Unauthorized
                   && !response.Headers.Contains(BasicAuthResponseHeader))
               {
                   response.Headers.Add(BasicAuthResponseHeader
                       , BasicAuthResponseHeaderValue);
               }
               return response;
           });
    }

    private Credentials ParseAuthorizationHeader(string authHeader)
    {
        string[] credentials = Encoding.ASCII.GetString(Convert
                                                        .FromBase64String(authHeader))
                                                        .Split(
                                                        new[] { ':' });
        if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0])
            || string.IsNullOrEmpty(credentials[1])) return null;
        return new Credentials()
                   {
                       Username = credentials[0],
                       Password = credentials[1],
                   };
    }
}
public class Credentials  
{
    public string Username {get;set;}
    public string Password {get;set;}
}

 

In SendAsync() we check for the presence of http auth header in the request, if it is present we parse it (ParseAuthorizationHeader()) to extract username and password. Once this is done we use IProvidePrincipal implementation (custom interface), which should be provided by whoever is instantiating our message handler (either manually or by dependency injection). We use this approach as we don't want our credential verification mechanism (be it SQL Server membership provider, flat text file or some kind of enterprise security service) to be baked into the code responsible for http authentication.

public interface IProvidePrincipal  
{
    IPrincipal CreatePrincipal(string username, string password);
}

Once the principal is retrieved we set Thread.CurrentPrincipal property. This is actually a property used by Web API's AuthorizeAttribute to verify if user has been authorized or not. Please note that we are not changing Web.HttpContext.Current.User (used by MVC AuthorizeAttribute).

Let's create a simple IProvidePrincipal implementation for testing purposes:

public class DummyPrincipalProvider : IProvidePrincipal  
{
    private const string Username = "username";
    private const string Password = "password";

    public IPrincipal CreatePrincipal(string username, string password)
    {
        if(username != Username || password != Password )
        {
            return null;
        }

        var identity = new GenericIdentity(Username);
        IPrincipal principal = new GenericPrincipal(identity, new []{"User"});
        return principal;
    }
}

Once we have all of above in place, we can simply add BasicAuthMessageHandler instance to web app global configuration:

 public class WebApiApplication : System.Web.HttpApplication  
    {
        protected void Application_Start()
        {
            GlobalConfiguration.Configuration.MessageHandlers
              .Add(new BasicAuthMessageHandler(){
                 PrincipalProvider = new DummyPrincipalProvider()
               });
            //...
        }
        //...
    }

From now on, whenever we use Authorize attribute, our handler will intercept and modify 401 http response and make the browser present the user with a standard login dialog.

[Authorize]  
public class ValuesController : ApiController  
{
    // GET api/values
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }
}

Once the user enters credentials we authenticate him against mechanism of our choice. We don't have to modify any of our existing controllers nor actions and thus is a much cleaner solution that custom action filters.

You can find source code here (.zip archive here).

 

comments powered by Disqus