Originally published 09-30-2015.
The ASP.NET Web API framework is a great choice for those that want a lightweight Service Oriented Architecture (SOA) to facilitate passing XML, JSON, BSON, and form-urlencoded data back and forth with a client application. Inevitably, you'll need to secure at least some of the endpoints.
At a minimum you'll need to have some sort of Authentication and Authorization mechanism in place.
- Authentication: The process of confirming that user is who they say they are.
- Authorization: The process of determining if the authenticated user has the proper roles/permissions to access a piece of functionality.
In Web API, the message pipeline looks something like this:
As the picture illustrates, you can handle authentication in 2 places. A host (IIS) HttpModule can handle authentication or you can write your own HttpMessageHandler. There are pros and cons to both but the main focus of this article is to discuss custom authorization filters that occur next in the pipeline after a user has been authenticated. Once authenticated by an HttpModule or a custom HttpMessageHandler an IPrincipal object has been set. This object represents both the user that authenticated and certain role membership information. Some applications have their own custom role and permission implementations which is where custom authorization attributes become useful.
Authorization filters are attributes that are used to decorate applications and can be applied at three different levels:
Globally: In the WebApiConfig class you can add:
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
config.Filters.Add(new MyCustomAuthorizationAttribute());
}
}
At the controller level:
[MyCustomAuthorization]
public class AController {}
Or at the endpoint level:
public class AController {
[MyCustomAuthorization]
public async Task<HttpResponseMessage> AnEndpoint() { return null; }
}
The code associated with each attribute gets executed in the same order as they are listed above, so you can nest functionality if need be. They are also inheritable so you can put an attribute on a base class and it will be inherited by any controller that inherits from it. The exception to this is the built in AllowAnonymous
attribute which can be applied and will short circuit the need for authorization.
When the AuthorizeAttribute is encountered the public method OnAuthorization is executed. The base implementation is below:
public override void OnAuthorization(HttpActionContext actionContext) {
if (actionContext == null) {
throw Error.ArgumentNull("actionContext");
}
if (SkipAuthorization(actionContext)) {
return;
}
if (!IsAuthorized(actionContext)) {
HandleUnauthorizedRequest(actionContext);
}
}
As you can see an error is thrown if there is no action context. Then SkipAuthorization is called to see if an AllowAnonymous
attribute is encountered and the authorization process should be skipped. Finally, IsAuthorized
is called and if it fails the HandleUnauthorizedRequest
is called. The overridable methods on this attribute are IsAuthorized
, HandleUnauthorizedRequest
and OnAuthorization
. There are a couple of ways a solution could be implemented. You could override OnAuthorization
and set the response message. The best solution to the scenarios I've run into is to override the IsAuthorized
method and let the OnAuthorization
method perform its base execution. I think the names of the methods also indicate that in the scenario of determining if a user is authorized that the IsAuthorized
method makes more sense.
Below is the very high level code representing the custom filter implementation:
public class MyCustomAuthorizationFilter : AuthorizeAttribute {
protected override bool IsAuthorized(HttpActionContext actionContext) {
if (!base.IsAuthorized(actionContext)) return false;
var isAuthorized = true;
// Do some work here to determine if the user has the
// correct permissions to be authorized anywhere this
// attribute is used. Assume the username is how
// you'd link back to a custom user permission scheme.
var username = HttpContext.Current.User.Identity.Name;
isAuthorized = username == "AValidUsername";
return isAuthorized;
}
}
There are a couple of things that should be pointed out about the above code. First the attribute inherits from AuthorizeAttribute
which is in the System.Web.Http
namespace. The namespace System.Web.Mvc
also contains an AuthorizeAttribute
which has similar behavior for the MVC framework but they are not compatible. All of the magic happens in the overridden function IsAuthorized
. Here you have access to the HttpActionContext
where you will have access to the Request, request header values, ControllerContext, ModelState as well as the Response. Within the IsAuthorized
function is where any work to decide if the user is authorized is done. If the user is not authorized (IsAuthorized
returns false) the response message is set with (Unauthorized) and returned. If custom processing of the request is needed you could then override the HandleUnauthorizedRequest
method in the case of an unauthorized user.
Proper use of these attributes can clean up your code so that authorization is a separate concern from the functionality of the endpoint itself. It also allows you to completely customize your role/permissions architecture as well.