Protecting static files in ASP.NET Core using custom middleware
Image from Pexels by Anete Lusina

Protecting static files in ASP.NET Core using custom middleware

Restricting access to specific static content with middleware in ASP.NET Core

The most common scenario for static files is just to serve them as they are without any additional hassle or access rules.
To serve static content which are often just images, css, javascript or other common static web content, you just need to involve static content middleware in your ASP.NET core application pipeline.

Adding static content in ASP.NET Core applications is as simple as adding wwwroot folder to root of your web application and adding StaticFiles middleware to your pipeline. By default these files will become publicly available, but in the further text we'll deal with how restrict access to these static files you have in your application.

Environment based restriction

In rare cases you may want to expose this content only while application is in development, which means you depend on the environment name and you base your login on this.
This means that if environment is not Production you allow access to this content. Unfortunately, StaticContent middleware does not (at least yet) have this feature out of the box, but thanks to flexibility and extendability of ASP.NET Core we can easily write a simple middleware to tackle this.

public class ProtectStaticContent
{
private readonly RequestDelegate _next;
private readonly PathString _path;
public ProtectStaticContent(RequestDelegate next, PathString path)
{
_next = next;
_path = path;
        }


        public async Task Invoke(HttpContext httpContext)
        {
            if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.CurrentCultureIgnoreCase))
            {
                httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                return;
            }
            await _next(httpContext);
        }
    

If you take a look at this simple logic, basically any requests that starts with the path provided will be responded with 403 status code. This allows us to block the content not only based on the current environment but pretty much any condition we can add to the pipeline. For now we'll stick to environment example.

Now since we do not want our pipeline to look weird and want to keep it as clean as possible, we'll just abstract the middleware usage with a simple wrapper extension method which will be more self-explanatory what it does.

    public static class ProtectStaticContentExtensions
    {
        public static IApplicationBuilder ProtectStaticContent(this WebApplication builder, PathString path)
        {
            return builder.UseMiddleware<ProtectStaticContent>(path);
        }
    }
    

The final step is to add the middleware via extension method call to our application pipeline, but we also need to enable static content in our pipeline, so we'll add middleware extension call for that as well

if (app.Environment.IsProduction())
    app.ProtectStaticContent("/images/tent.jpg");

app.UseStaticFiles();
    
Note

Unlike when registering services, order of adding middlewares to a pipeline matters. Order in which middlewares are added to the pipeline defines the order of the execution when the request comes in. For this reason it is important that our middleware is added before the StaticFiles middleware so that when request path matches the one for our middleware and we want to restrict access to the static resource it does not execute ASP.NET Core StaticFiles middleware

To test this, you do not really have to change the environment variables on your machine. It is enough to update your launchSettings.json in Properties forlder of your solution.

{
  "$schema": "https://json.schemastore.org/launchsettings.json",
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:28083",
      "sslPort": 0
    }
  },
  "profiles": {
    "StaticResources.Api": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "launchUrl": "swagger",
      "applicationUrl": "http://localhost:5000",
      "environmentVariables": {
        //"ASPNETCORE_ENVIRONMENT": "Development"
        "ASPNETCORE_ENVIRONMENT": "Production"
      }
    },
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "launchUrl": "swagger",
      "environmentVariables": {
        //"ASPNETCORE_ENVIRONMENT": "Development"
        "ASPNETCORE_ENVIRONMENT": "Production"
      }
    }
  }
}

    

Authorization based restriction

You may not want to simply restrict access to your static resource just based on the environment, but you may rather want to protect the resource with authorization mechanism. So beside your endpoints you protect your static content with the same authorization which is in place in your application.
Of course, maybe not the same rule applies for the static resource, but that is why you have the freedom to define a custom authorization policy you want to use.
Before I go into middleware implementation, I added basic authentication to a sample WebAPI application, just for the purpose of demonstrating it.

I will not dig into the Basic Authentication implementation in ASP.NET Core since you can find all the details in Basic authentication with Swagger and ASP.NET Core article.

Instead well focus on a simple policy and how to reference it in the setup. We'll just setup a simple authorization policy whci only checks for authenticated user.

builder.Services.AddAuthentication("BasicAuthentication")
    .AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("Authenticated", policy => policy.RequireAuthenticatedUser());
});
    

Now we need to extend the middleware we use for environment based authorization to handle the authorization policy option too. Since we are not going to take out previous functionality, we'll just extend the existing middleware.

    public class ProtectStaticContent
    {
        private readonly RequestDelegate _next;
        private readonly PathString _path;
        private readonly string _policyName;

        public ProtectStaticContent(RequestDelegate next, PathString path)
        {
            _next = next;
            _path = path;
        }

        public ProtectStaticContent(RequestDelegate next, PathString path, string policyName) : this(next, path)
        {
            _policyName = policyName;
        }


        public async Task Invoke(HttpContext httpContext, IAuthorizationService authorizationService)
        {
            if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.InvariantCultureIgnoreCase))
            {
                if (!string.IsNullOrEmpty(_policyName))
                {
                    var authorized = await authorizationService.AuthorizeAsync(httpContext.User, null, _policyName);
                    if (!authorized.Succeeded)
                    {
                        await httpContext.ChallengeAsync();
                        return;
                    }
                }
                else
                {
                    httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                    return;
                }
            }

            await _next(httpContext);
        }
    }
    

Since we added additional constructor for the middleware class, we need to create a new extension method overload which will supply the policy name to the middleware constructor method.

        public static IApplicationBuilder ProtectStaticContent(this WebApplication builder, PathString path, string policy)
        {
            return builder.UseMiddleware<ProtectStaticContent>(path, policy);
        }
    

Lastly, we need to add the middleware to the pipeline by calling our new extension method. Remember that this middleware needs to be invoked before UseStaticFiles middleware extension method as it decides whether request will return the resource or restrict it.

app.ProtectStaticContent("/images/tent.jpg", "Authenticated");

app.UseStaticFiles();
    

 

References

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.


About the author

DEJAN STOJANOVIC

Dejan is a passionate Software Architect/Developer. He is highly experienced in .NET programming platform including ASP.NET MVC and WebApi. He likes working on new technologies and exciting challenging projects

CONNECT WITH DEJAN  Loginlinkedin Logintwitter Logingoogleplus Logingoogleplus

JavaScript

read more

SQL/T-SQL

read more

Umbraco CMS

read more

PowerShell

read more

Comments for this article