Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

A WebAPI Basic Authentication MessageHandler


:P
On this page:

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

Posted in Web Api  

The Voices of Reason


 

Paulo Morgado
May 02, 2013

# re: A WebAPI Basic Authentication MessageHandler

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;
            });

Rick Strahl
May 02, 2013

# re: A WebAPI Basic Authentication MessageHandler

@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.

Paulo Morgado
May 03, 2013

# re: A WebAPI Basic Authentication MessageHandler

@Rick, is the Web API supported on .NET 4.0? I thought it was 4.5 only.

Rick Strahl
May 03, 2013

# re: A WebAPI Basic Authentication MessageHandler

Yes it works just fine with .NET 4.0.

Tremayne
September 23, 2013

# re: A WebAPI Basic Authentication MessageHandler

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} ?

Al
November 28, 2013

# re: A WebAPI Basic Authentication MessageHandler

In the BasicAuthenticationHandler class definition, is there a reason to instantiate the BasicAuthenticationIdentity object twice?

Jamie
February 10, 2014

# re: A WebAPI Basic Authentication MessageHandler

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.

vpatel
May 13, 2014

# re: A WebAPI Basic Authentication MessageHandler

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.

Rick Strahl
May 13, 2014

# re: A WebAPI Basic Authentication MessageHandler

@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.

FH
August 06, 2014

# re: A WebAPI Basic Authentication MessageHandler

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.

Nick Gilbert
October 09, 2014

# re: A WebAPI Basic Authentication MessageHandler

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?

Jk
February 20, 2015

# re: A WebAPI Basic Authentication MessageHandler

if I add a role in the principal:

var principal = new GenericPrincipal (identity, new string [] {role});

how can I authorize methods of the controller with the role?

Thank You

JK

mike
May 13, 2015

# re: A WebAPI Basic Authentication MessageHandler

I think you should move the comment about disabling basic authentication to the top of the article. It took me a while to find something that referenced that problem, and that 'disabling it for IIS' meant disabling it in web.config (if feature delegation is allowed).

This was the one problem I had, as the request would come in but nothing told me about the IIS/basic auth issue. For those who don't know, if basic authentication is enabled in web.config, IIS will intercept the authentication routine and your authorization/authentication handlers and attributes will never be hit while debugging.

Thanks, though. The rest of the article was great!

Jonny Knott
August 11, 2015

# re: A WebAPI Basic Authentication MessageHandler

Hi,

Great post(s), very helpful for me, especially as I'm a complete novice in this area.

I would like to persist the user information to my api controller...so that when I authenticate them I am able to tell which user exactly it is so that I can perform the correct tasks in my action methods. For instance if I call the API with my username and password encryption, and the method is to return my messages, I want to be able to know it is me in the API so I can query the db accordingly.

Have you any advice in this regard?

Also I'm having trouble integrating this because I have added Web Api into an existing MVC project, and when it fails this authorization it is strangely still returning a 200 response with a login page cshtml. Is there anything I need to change in my pipeline when integrating this with an existing MVC site?

Any help would be VERY appreciated, thanks a lot for the post.

Rick Strahl
August 11, 2015

# re: A WebAPI Basic Authentication MessageHandler

@Johnny - you can check the username in the Identity of the request - it's set there. As to redirect in MVC project that's the behavior forms authentication - which is 302 redirect followed by the login page access which is standard behavior for FormAuthentication or Identity. If you don't want that behavior you'd have to turn off Forms Auth or Identity or configure them to not redirect to the login page and fire just a 401 instead.

Panu
March 09, 2016

# re: A WebAPI Basic Authentication MessageHandler

I have a self hosting Web API targeting .net4.0 with this authentication scheme. Problem I'm having is that Thread.CurrentPrincipal will always be the same once it is set. This means that if I try to connect with client A without authorization header I get 401 as I should. If then I connect with client B with authorization header I get normal response as I should. But now when I connect again with client A without authorization header I get normal response. Even if I connect with client A with wrong authorization header I still get normal response.

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