Locking App Service to Azure Front Door via Middleware

2024, May 18

When hosting public internet applications, like a website, or a BFF (back-end for front-end), not only there are infrastructure considerations to secure the application against attacks, but also application security considerations.

In this post, I am sharing an application lock to only accept requests from a gateway/proxy/load balancer, a service that you control, in this instance Azure Front Door. This is an application security strategy, part of a larger security strategy to control and filter incoming requests.

The challenge

Consider an App Service, created using ASP.NET Core, allowing requests from Azure Front Door only.

diagram

The solution

An ASP.NET Core Middleware in .NET 8.0 can be used for such lock. Basically what you can do is filter requests based on the request header X-Azure-FDID that Azure Front Door1 provides in all of its requests:

/// <summary>
/// Front Door Request Header Middleware
/// </summary>
public class FrontDoorRequestHeaderMiddleware
{
    private readonly RequestDelegate _next;

    /// <summary>
    ///     Initializes a new instance of the <see cref="FrontDoorRequestHeaderMiddleware" /> class.
    /// </summary>
    public FrontDoorRequestHeaderMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    /// <summary>
    /// Middleware Invoke Async
    /// </summary>
    public async Task InvokeAsync(HttpContext context)
    {
        if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != "Development") // not for local development
        {
            var appSettings = context.RequestServices.GetRequiredService<IConfiguration>();
            var apiKey = appSettings.GetValue<string>("FrontDoorId"); // this should be on appsettings

            var isHeaderAvailable = context.Request.Headers.TryGetValue("X-Azure-FDID", out StringValues headerValue);
            var isFrontDoorIdValid = apiKey.Equals(headerValue);

            if (!isHeaderAvailable || !isFrontDoorIdValid)
            {
                context.Response.StatusCode = StatusCodes.Status403Forbidden;
                return;
            }
        }

        await _next.Invoke(context);
    }
}

/// <summary>
/// FrontDoor request header extension
/// </summary>
public static class FrontDoorRequestHeaderExtensions
{
    /// <summary>
    /// Use FrontDoor extension
    /// </summary>
    public static IApplicationBuilder UseFrontDoorRequestHeader(
        this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<FrontDoorRequestHeaderMiddleware>();
    }
}

Then from Program.cs you can add this middleware to the App, like this:

...
app.UseFrontDoorRequestHeader();
app.UseAuthentication();
app.UseAuthorization();
...

What I like about this approach is that all requests go through the middleware, and it simply does short-circuit2 the request pipeline in case the request is forbidden, not passing it to the next delegate, avoiding unnecessary work.