Securing a REST API with a Secret and an Action Filter

A simple way to secure a private REST API is to insist that all incoming requests include a secret key in the HTTP header. This is surprisingly easy to do using a .Net Core Action Filter.

Action filters are classes that override one or more virtual methods found in the variety of abstract classes from which they may inherit. For example, if the APIKeyAuthAttribute filter, below, is associated with a controller action (typically by using an attribute), the overridden method OnActionExecutionAsync is inserted into the execution pipeline just before the controller action is invoked.

Much like services placed into the main middleware pipeline, OnActionExecutionAsync does some work and then awaits the next delegate, given by parameter ActionExecutionDelegate next. The next delegate, which is the controller method itself, releases control back to OnActionExecutionAsync after it’s done. The method may then do some processing on the outgoing execution pipeline, if needed.

As previously stated, the APIKeyAuthAttribute filter class (below) creates a ServiceFilter filter that rejects any request that does not contain a secret key in the HTTP header. The HTTP header is contained in the ActionExecutingContext parameter. (See: HttpContext.Request.Headers)

To apply the filter to an action method, you simply decorate the method with an attribute referencing the filter. Decorating a controller class with the attribute is merely shorthand for decorating each of the actions separately.

The filter gets the expected value of the secret key by reading it from the configuration, so we need access to the IConfiguration service.

Notice that the dependency injection of the configuration service used to be done “manually.” Line 31, now commented out, uses context.HttpContext.RequestServices to gain access to the dependency injection container, from which it requests the IConfiguration object registered there.

A better alternative, and the one included in the example, is to use a ServiceFilter. Using ServiceFilter allows you to inject dependencies directly into the filter’s constructor, which is the standard way to do it.

When creating a ServiceFilter attribute, you give the type of the desired attribute filter, like this:

[ServiceFilter(typeof(APIKeyAuthAttribute))]

Then you can use regular .Net Core dependency injection in the filter’s constructor:

public IConfiguration configuration { get; }
        
public APIKeyAuthAttribute(IConfiguration _config)
{
    configuration = _config ?? throw new ArgumentNullException(nameof(_config));
}

Note that when you use a ServiceFilter you have to include your filter class in the dependency injection container in Startup.cs, like this:

services.AddScoped<APIKeyAuthAttribute>();

By the way, if you try to inject IConfiguration into the constructor of APIKeyAuthAttribute without changing the attribute syntax to ServiceFilter and adding the filter class to the DI container, you get an error because the old [APIKeyAuth] attribute, sufficient if you don’t need dependency injection, doesn’t include the filter class type like the ServiceFilter one does.

Note: Line 10 below contains an attribute that indicates the APIKeyAuth attribute can be placed on classes or methods.

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;

namespace ProjectsAPI.Filters
{

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class APIKeyAuthAttribute : ActionFilterAttribute
    {
        private const string APIKeyName = "APIKey";  // the name of the Http header field
        public IConfiguration configuration { get; }
        public APIKeyAuthAttribute(IConfiguration _config)
        {
            configuration = _config ?? throw new ArgumentNullException(nameof(_config));
        }

        public override async Task OnActionExecutionAsync(ActionExecutingContext context, 
                                                          ActionExecutionDelegate next)
        {
            if (!context.HttpContext.Request.Headers.TryGetValue(APIKeyName, 
                                                                 out var potentialKey))
            {
                context.Result = new UnauthorizedResult();
                return;
            }

            // The old way: RequestServices gives access to the dependency injection container.
            // var configuration = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>();

            // The new way: ServiceFilter allows us to inject the configuration 
            // into this class's constructor
            var apikey = configuration.GetValue<string>(APIKeyName);
            if (!apikey.Equals(potentialKey))
            {
                context.Result = new UnauthorizedResult();
                return;
            }

            await next();

            // Executed on the way back out of the pipeline.
            // We don't need to do anything here.
        }
    }
}

Here’s more on filters in .Net Core

https://www.devtrends.co.uk/blog/dependency-injection-in-action-filters-in-asp.net-core

You may also like...