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
Other Posts you might also like