Contact   •   Products   •   Search

Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs

A WebAPI Basic Authentication MessageHandler


In my last post I showed a very simple Basic Authentication Filter implementation and several people commented that the 'right' way to do this is by using a MessageHandler instead. In the post I discussed why I opted for a filter rather than the MessageHandler: A filter is much simpler to implement and keeps all the relevant code pieces in one place instead of scattering them throughout the Web API pipeline. This might not be the right choice for all authentication, but if you're doing custom authentication/authorization in your app you're not going to mix and match and plug a multitude of auth mechanisms together. For simple auth scenarios a filter is just fine, especially since even when you implement a MessageHandler you need to implement an AuthorizationFilter anyway.

Just as an exercise, I spend a little time today to put together a message handler based Basic Authentication implementation to contrast the two. There are a few more moving pieces to this implementation:

  • A MessageHandler to handle the Basic Auth processing
  • A custom Identity to pass the username and password around
  • An Authorization filter to validate the user

MessageHandler for Authentication

MessageHandlers in Web API are chainable components that hook into the request/response pipeline. You can plug many message handlers together to provide many module like features. MessageHandlers can handle processing on the inbound request cycle and the output response cycle, via a simple Task<T> abstraction that provides the asynchronous pipeline processing.

To implement the BasicAuthenticationHandler you can create a class derived from DelegatingHandler and override the SendAsync method:

public class BasicAuthenticationHandler : DelegatingHandler
{
    private const string WWWAuthenticateHeader = "WWW-Authenticate";

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, 
                                                           CancellationToken cancellationToken)
    {
        var credentials = ParseAuthorizationHeader(request);

        if (credentials != null)
        {
            var identity = new BasicAuthenticationIdentity(credentials.Name, credentials.Password);
            var principal = new GenericPrincipal(identity, null);

            Thread.CurrentPrincipal = principal;
            //if (HttpContext.Current != null)
            //    HttpContext.Current.User = principal;
        }

        return base.SendAsync(request, cancellationToken)
            .ContinueWith(task =>
            {
                var response = task.Result;
                if (credentials == null && response.StatusCode == HttpStatusCode.Unauthorized)
                    Challenge(request, response);


                return response;
            });
    }

    /// <summary>
    /// Parses the Authorization header and creates user credentials
    /// </summary>
    /// <param name="actionContext"></param>
    protected virtual BasicAuthenticationIdentity ParseAuthorizationHeader(HttpRequestMessage request)
    {
        string authHeader = null;
        var auth = request.Headers.Authorization;
        if (auth != null && auth.Scheme == "Basic")
            authHeader = auth.Parameter;

        if (string.IsNullOrEmpty(authHeader))
            return null;

        authHeader = Encoding.Default.GetString(Convert.FromBase64String(authHeader));

        var tokens = authHeader.Split(':');
        if (tokens.Length < 2)
            return null;

        return new BasicAuthenticationIdentity(tokens[0], tokens[1]);
    }


    /// <summary>
    /// Send the Authentication Challenge request
    /// </summary>
    /// <param name="message"></param>
    /// <param name="actionContext"></param>
    void Challenge(HttpRequestMessage request, HttpResponseMessage response)
    {
        var host = request.RequestUri.DnsSafeHost;                    
        response.Headers.Add(WWWAuthenticateHeader, string.Format("Basic realm=\"{0}\"", host));
    }

}
If you looked at my last post this should look fairly familiar - the basic auth logic is very similar to the filter. I reused the Challenge and ParseAuthorizationHeader changing just the inputs to the request and response messages respectively.

The message handler works in two distinct steps - the initial code that fires on the inbound request, which tries to parse the authentication header into a BasicAuthenticationIdentity and assigning that identity to the thread principle.

The second step - the part in the ContinueWith() Task block - handles the processing on the outbound response. Things have to be broken up like this in a MessageHandler because the Response doesn't exist on the inbound request yet. The code here is responsible for issuing the challenge if the response status is unauthorized.

So the logic goes like this:

  • Request is authenticated already - goes through
  • Request is not authenticated and returns a 401 (from an AuthFilter or explicit 401 ResponseMessage from code)
  • Request is not authenticated and returns something other 401 - request goes through

To make all this work there are a couple more things that need to be implemented.

BasicAuthenticationIdentity

Basic Authentication works via a username and password that is passed as a base64 encoded, clear text string. In order to authorize the user in a custom authorization scenario that username and password has to be passed up the pipeline into the AuthorizationFilter that actually handles the authorization of the user.

To do this I opted to create a BasicAuthenticationIdentity class. Using this identity the handler can set the username and password on the Identity and pass it to AuthorizeFilter. Here's the implementation:

/// <summary>
/// Custom Identity that adds a password captured by basic authentication
/// to allow for an auth filter to do custom authorization
/// </summary>
public class BasicAuthenticationIdentity : GenericIdentity
{
    public BasicAuthenticationIdentity(string name, string password) : base(name,"Basic")
    {            
        this.Password = password;
    }

    /// <summary>
    /// Basic Auth Password for custom authentication
    /// </summary>
    public string Password { get; set; }               
}

AuthorizeFilter

Next we need a filter to handle the authorization of the user. This logic most likely will be application specific. Because all we'll need to do here is validate the user's credentials and return yay or nay, an AuthorizeFilter is the easiest:

public class MyAuthorizationFilter : AuthorizeAttribute
{      
    protected override bool IsAuthorized(HttpActionContext actionContext)
    {             
        var identity = Thread.CurrentPrincipal.Identity;
        if (identity == null && HttpContext.Current != null)
            identity = HttpContext.Current.User.Identity;

        if (identity != null && identity.IsAuthenticated)
        {
            var basicAuth = identity as BasicAuthenticationIdentity;

            // do your business validation as needed
            var user = new BusUser();
            if (user.Authenticate(basicAuth.Name, basicAuth.Password))
                return true;                
        }

        return false;
    }
}

In the filter you can simply override the IsAuthorized() method and return true or false. If you return false WebAPI automatically fires a 401 status code, which triggers the Challenge() in the BasicAuthenticationHandler that's monitoring for 401's.

The IsAuthorized method implementation typically has business specific code in it that handles the authorization of the user. Basically you can capture the Thread Principal and the BasicAuthenticationIdentity and retrieve the username and password. You can then go to town on the username and password. In my example here a business object is fired up to authenticate the user.

By the way, notice that in my last post I used an AuthorizationFilter - and here I'm using an AuthorizeFilter. AuthorizeFilter works great if all you need to do is validate a user and return true or false. If there's more logic involved that, like creating a new response then an AuthorizationFilter is the better choice.

Configuration

Once the handler and filter exist they have to be hooked up. MessageHandlers have to be added in the configuration:

GlobalConfiguration.Configuration.MessageHandlers.Add(new BasicAuthenticationHandler());

The AuthorizationFilter can either be applied via global configuration or on the controller:

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

or you can apply it on the controller:

[MyAuthorizationFilter]
public class QueueController : ApiController

Filter or MessageHandler - you decide

Comparing the two modes of operation - Authentication MessageHandler or AuthorizationFilter - there's not a tremendous difference in implementation. To me the filter is more compact and easier to follow what's going on simply because everything is in one place. For most typical custom login scenarios that are tied to business logic, that'll be totally sufficient. The advantage of a message handler is that it's globally applied and is part of the WebAPI pipeline so if several components need to take advantage of BasicAuthentication with different Authorization that would work. But then again you can do that with a filter as well, especially since a MessageHandler still requires a filter for it's authorization. <shrug>

Either way you can take your pick from these two implementations.

Resources

Make Donation
Posted in Web Api  


Feedback for this Post

 
# re: A WebAPI Basic Authentication MessageHandler
by Paulo Morgado May 02, 2013 @ 9:11am
Rick,

Have you tried something like:

        var response = await base.SendAsync(request, cancellationToken);
 
        if (credentials == null && response.StatusCode == HttpStatusCode.Unauthorized)
                Challenge(request, response);
 
 
        return response;




Instead of:

        return base.SendAsync(request, cancellationToken)
            .ContinueWith(task =>
            {
                var response = task.Result;
                if (credentials == null && response.StatusCode == HttpStatusCode.Unauthorized)
                    Challenge(request, response);
 
 
                return response;
            });
# re: A WebAPI Basic Authentication MessageHandler
by Rick Strahl May 02, 2013 @ 12:54pm
@Paulo - that's essentially the same code. The await roughly translates into a task.ContinueWith().

If the code is strictly for .NET 4.5 then the await is the way to go, but if also support 4.0 then Task.ContinueWith() is it.
# re: A WebAPI Basic Authentication MessageHandler
by Paulo Morgado May 03, 2013 @ 5:05am
@Rick, is the Web API supported on .NET 4.0? I thought it was 4.5 only.
# re: A WebAPI Basic Authentication MessageHandler
by Rick Strahl May 03, 2013 @ 6:18pm
Yes it works just fine with .NET 4.0.
# re: A WebAPI Basic Authentication MessageHandler
by Tremayne September 23, 2013 @ 6:20am
My Authentication in IsAuthrorize:
if (identity != null && identity.IsAuthenticated)
{
var basicAuth = identity as BasicAuthenticationIdentity;
var User = DatabaseDb.Authenticate(basicAuth.Name, basicAuth.Password.CreateHash());
if (User != null)
return true;
}
return false;
This line of code throws error when the user fails authentication:

return base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
var response = task.Result; //AggregateException was unhandled by usercode
//Type 'BasicAuthenticationIdentity' in assembly is not marked as serializable.
if (credentials == null && response.StatusCode == HttpStatusCode.Unauthorized)
Challenge(request, response);


return response;
});

after debugging I notice task.Result shows as {not computed yet} ?
# re: A WebAPI Basic Authentication MessageHandler
by Al November 28, 2013 @ 2:52am
In the BasicAuthenticationHandler class definition, is there a reason to instantiate the BasicAuthenticationIdentity object twice?
# re: A WebAPI Basic Authentication MessageHandler
by Jamie February 10, 2014 @ 1:08pm
Thanks for the article. There's one thing that really makes me nervous about this approach, and that is that IsAuthenticated is set to true when the Identity is created. An Identity should be created only for authenticated users so that the standard Authorize attribute will work.

The biggest reason for preferring a MessageHandler over a filter is that the handler is invoked very early in the pipeline and can create the response directly, thus skipping the rest of the pipeline. It also prevents exploits targeting modules deeper in the pipeline -- code that isn't executed is very secure.
# re: A WebAPI Basic Authentication MessageHandler
by vpatel May 13, 2014 @ 12:06pm
Can you suggest what should be the IIS authentication settings if we want to implement custom authentication in Message Handler of Web API pipeline ? Do we have to set "Anonymous Authentication" ? I tried setting "Basic Authentication" but then IIS tries to validate it against windows authentication. i mean, it tries to validate username and password with windows domain.
# re: A WebAPI Basic Authentication MessageHandler
by Rick Strahl May 13, 2014 @ 2:28pm
@vpatel - yeah don't use IIS's authentication because it will validate against Windows account. It doesn't matter what else is enabled since other auth schemes (Windows, Digest, Forms) are separate, but Basic Authentication must be off in order for WebAPI to actually get called to authorize your request.
# re: A WebAPI Basic Authentication MessageHandler
by FH August 06, 2014 @ 12:00am
Just stumpled upon these posts and - thumbs up.

Also took a look at the source and discovered your toolbox.

I grabbed a copy and will have a look at it later but the least I can do shout out loud:

THANKS! (caps intended)

Though through a lot of confusing a finally have made my way through ASP.NET Identity and I have my with it - but you can never get enough of good code samples to feed your codemonkey when he is grumbling over stubborn code.

Have a very good day.

By the way - forgive me - I recently found that http://jw.org can feed your minds hunger for answers to big questions of life.
# re: A WebAPI Basic Authentication MessageHandler
by Nick Gilbert October 09, 2014 @ 1:03am
I have updated to WebAPI 2.2 from nuget and now I find this approach doesn't work. The code walks the same path but the service always returns unauthorized.

Is there a way to get this working again under the current version of WebAPI?
 


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2014