Conditional FilterProvider for WebAPI

2.1k views Asked by At

I have an ASP.NET MVC / WebAPI / AngularJS application in which I want to implement CSRF protection. I have found some great solutions on this, but am missing one piece of the puzzle. I want to be able to automatically apply a custom AuthorizationAttribute to API nonsafe methods (POST, PUT etc). I hope someone can help.

On the MVC side, I am using the MVC AntiForgeryToken in conjunction with Phil Haacked's ConditionalFilterProvider, configured to apply the ValidateAntiForgeryToken attribute to all POST methods. The technique is explained in this post. The code for the ConditionalFilterProvider is listed below:

using System.Web.Mvc;

public class ConditionalFilterProvider : IFilterProvider
{
    private readonly
      IEnumerable<Func<ControllerContext, ActionDescriptor, object>> _conditions;

    public ConditionalFilterProvider(
      IEnumerable<Func<ControllerContext, ActionDescriptor, object>> conditions)
    {
        _conditions = conditions;
    }

    public IEnumerable<Filter> GetFilters(
        ControllerContext controllerContext,
        ActionDescriptor actionDescriptor)
    {
        return from condition in _conditions
               select condition(controllerContext, actionDescriptor) into filter
               where filter != null
               select new Filter(filter, FilterScope.Global, null);
    }
}

For WebAPI, I'm using AngularJS's CSRF protection mechanism, packaged it up with some helper classes for easy implementation on the server, and I'm decorating the API methods with a custom AuthorizeAttribute, as described here.

All this works as intended, but I'd like to go one step further. Following the pattern used for the MVC controllers, I want to create a ConditionalFilterProvider that can be used with WebAPI controllers so that I can ensure that all POST methods are automatically decorated with my custom AuthorizeAttribute.

However, though I've had a stab at creating a ConditionalFilterProvider for WebAPI, I'm not sure I have it right, and can't test it as I don't know how to register it. What I've come up with is:

using System.Web.Http;

public class ConditionalApiFilterProvider : IFilterProvider
{
    private readonly
      IEnumerable<Func<HttpConfiguration, HttpActionDescriptor, object>> _conditions;

    public ConditionalApiFilterProvider(
      IEnumerable<Func<HttpConfiguration, HttpActionDescriptor, object>> conditions)
    {
        _conditions = conditions;
    }

    public IEnumerable<FilterInfo> GetFilters(
        HttpConfiguration configuration,
        HttpActionDescriptor actionDescriptor)
    {
        return from condition in _conditions
               select condition(configuration, actionDescriptor) into filter
               where filter != null
               select new FilterInfo(filter as IFilter, FilterScope.Action);
    }
}

And to register it in Application_Start (this doesn't work):

    private void ConfigureValidateCsrfHeaderAttribute()
    {
        //Configure a conditional filter
        string[] nonSafeMethods = { "post", "put", "delete" };
        IEnumerable<Func<HttpConfiguration, HttpActionDescriptor, object>> conditions =
            new Func<HttpConfiguration, HttpActionDescriptor, object>[] {
                    ( c, a ) => nonSafeMethods.Contains("need HTTP method here") ?
                    new ValidateCsrfHeaderAttribute() : null
            };

        var provider = new ConditionalApiFilterProvider(conditions);

        // This line adds the filter we created above
        FilterProviders.Providers.Add(provider);        //incorrect provider registration
    }
1

There are 1 answers

0
Paul Taylor On BEST ANSWER

Discovered how to register a custom IFilterProvider for WebAPI, tested the solution and it works correctly. The code needed to register the provider is:

using System.Web.Http;
using System.Web.Http.Filters;

private void ConfigureValidateCsrfHeaderAttribute()
{
    //Configure a conditional filter
    HttpMethod[] nonSafeMethods = { HttpMethod.Post, HttpMethod.Put, HttpMethod.Delete };
    IEnumerable<Func<HttpConfiguration, HttpActionDescriptor, object>> conditions =
        new Func<HttpConfiguration, HttpActionDescriptor, object>[] {
                ( c, a ) => nonSafeMethods.Any(m => a.SupportedHttpMethods.Contains(m)) ?
                new ValidateCsrfHeaderAttribute() : null
    };

    var provider = new ConditionalApiFilterProvider(conditions);

    // This line adds the filter we created above
    GlobalConfiguration.Configuration.Services.Add(typeof(IFilterProvider), provider);
}