Asp net core action filters and requests html encoding
Normally you need to accept text user input in your REST Apis, in that case XSS can become a security concern, you can but certainly shouldn’t rely on the frontend to send safe text input o to rendered it back properly for many reasons. The Asp Net MVC Framework (the classic controllers and views) does an html encoding when rendering the view models, but if you are using an Api based application or you want to make sure you only save sanitized input you will have to do this yourself. In this article I will explain what Filters are in the context of asp net and how we can apply them to make sure all requests are properly html encoded. You can follow along with the explanation or look at the source code here.
Filters
Let’s begin by explaining what Filters are, in asp net core a MVC filter is a piece of code that its being executed at specific stage in the request/response pipeline and they can inspect, change or stop the pipeline. You can have different types of Filters: Authorization Filters, Resource Filters, Actions filters Result filters and Exception, Filters (the last two are more useful when dealing with responses so let’s ignore them).
Authorization Filters can use the circuit braker pattern to prevent the action to be executed while Resource Filters that are useful to implement patterns like caching and so on.
On the other hand, you have Action Filters which are executed after the model binding process and right before the controller’s action, meaning you will get a ActionArguments property containing the actual arguments that are going to be passes to the controller action making it a perfect fit for inspecting and modifying the request objects using reflection.
Action Filters
To create a custom action filter, you can need a class that implement the IActionFilter interface provided by the framework or you can derive the abstract class ActionFilterAttribute as this allows you to only implement the method you are interested in. Either way the you get the same contract.
After looking at the contract you can probably guess that OnActionExecuting is executed before the target controller action, while OnActionExecuted comes after. In this article we'll put the focus on the first method as given that we need to modify the request. The ActionExecutingContext has several properties that describe the action being executed, here is a list with the specific properties that we'll be using for quick reference.
Name | Description |
---|---|
HttpContext | Provides details of the current HTTP request and the HTTP response. |
ActionArguments | A dictionary of the arguments that will be passed to the action method (indexed by name). |
ActionDescriptor | Describes the action method. |
The Code
Now that we now how this filters work let’s put this in practice and create one. First, we need to create a marking interface and an attribute.
We'll use the first interface to mark our request view models since html encoding may be a thing that you would not want to apply to the whole application. The attribute will allow us to skip a given property of the model or the entire action. Here is example how it can be used.
Of course, contracts don’t do magic so let’s create the filter itself.
You can see that the class derives from the abstract class ActionFilterAttribute provided by the framework. The next step would be applying the filter only when a Post or Put request arrives.
To access the current http request you can access the HttpContext from ActionExecutingContext object. Then we need to check if the action has been marked with the NeverEncodeAsHtmlAttribute as that would be signaling that no encoding is required.
The ActionExecutingContext.ActionDescriptor.EndpointMetadata returns a list of the attributes applied in the action itself. In the next listing I show you how to get access to the Parameters that are going to be passed to the action after the model binding has been applied.
public override void OnActionExecuting(ActionExecutingContext context)
{
...
var inspectArguments = context
.ActionArguments
.Where(x => ShouldInspectObject(x.Value))
.ToList();
if (!inspectArguments.Any())
return;
}
private static bool ShouldInspectObject(object? value)
{
if (value is null)
return false;
bool hasIgnoreAttribute = value.GetType()
.GetCustomAttribute<NeverEncodeAsHtmlAttribute>() is not null;
if (hasIgnoreAttribute)
return false;
return value is IRequireHtlmEncoding;
}
The list of arguments is contained in ActionExecutingContext.ActionArguments, the ShouldInspectObject makes sure that two conditions are meet: the Argument cannot be marked with NeverEncodeAsHtmlAttribute and it should implement the IRequireHtlmEncoding interface.
Now that we have the list of arguments to be updated, we need to start the introspection, lets create a method for that.
Before changing any property of the object, we need to make sure again that such property is not being mark with the same NeverEncodeAsHtmlAttribute, we can use reflection for that.
The next step is the actual encoding the property as you can see in the next listing.
Before changing a property, we need to make sure that it has some sort of ‘set’, then we can modify it with the same value but this time properly encoded, the actual encoding can be done through the WebUtility class. Lastly let’s deal with nested objects.
Reflection and recursiveness are our friends here, we basically need check value and based on its type call the same InspectAndEncodeObjectSringProperties method. We’ve just have finished with the filter, but we need to register the filter with the container.
To register the filter, you need to added it to the Filters collection of the MvcOptions object while registering the controllers.
Summary
Actions filters, and filters in general are part of the MVC pipeline and can be useful when it comes to expanding the framework itself and are not that difficult to implement. Today we saw a practical example of how you can do one filter to make sure request are safely using html encoding without polluting the code in your controllers or you application. Hope you find it useful and remember that you can check out the source code here.