7

In my previous question, I asked a generic question how to add permission for static content. Here I want to be more precise.

In my project I added a folder under wwwroot to simplify the code, where I save the html files I want to protect. This folder is called infographics.

enter image description here

The properties for each file are:

  • Build Action: Content
  • Copy to Output Directory: Do not copy

enter image description here

Follow the instruction from the Microsoft documentation, I changed the Startup.cs

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews();
        services.Configure<IdentityServerConfiguration>(Configuration.GetSection("IdentityServerConfiguration"));

        services.AddDistributedMemoryCache();

        services.AddSession(options =>
        {
            options.Cookie.Name = ".my.Session";
            options.IdleTimeout = TimeSpan.FromHours(12);
        });

        services.AddAuthentication(options =>
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie(options =>
        {
            options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
            options.Cookie.Name = "my.dashboard";
        })
        .AddOpenIdConnect("oidc", options =>
        {
            IdentityServerConfiguration idsrv = Configuration.GetSection("IdentityServerConfiguration")
                                                .Get<IdentityServerConfiguration>();
            options.Authority = idsrv.Url;
            options.ClientId = idsrv.ClientId;
            options.ClientSecret = idsrv.ClientSecret;

            #if DEBUG
            options.RequireHttpsMetadata = false;
            #else
            options.RequireHttpsMetadata = true;
            #endif

            options.ResponseType = "code";

            options.Scope.Clear();
            options.Scope.Add("openid");
            options.Scope.Add("profile");
            options.Scope.Add("email");
            options.Scope.Add("roles");
            options.Scope.Add("offline_access");

            options.ClaimActions.MapJsonKey("role", "role", "role");

            options.GetClaimsFromUserInfoEndpoint = true;
            options.SaveTokens = true;

            options.SignedOutRedirectUri = "/";

            options.TokenValidationParameters = new TokenValidationParameters
            {
                NameClaimType = JwtClaimTypes.Name,
                RoleClaimType = JwtClaimTypes.Role,
            };
        });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseHsts();
        }
        app.UseHttpsRedirection();

        app.UseAuthentication();

        app.UseStaticFiles(new StaticFileOptions
        {
            OnPrepareResponse = ctx =>
            {
                if (ctx.Context.Request.Path.StartsWithSegments("/infographics"))
                {
                    ctx.Context.Response.Headers.Add("Cache-Control", "no-store");

                    if (!ctx.Context.User.Identity.IsAuthenticated)
                    {
                        // respond HTTP 401 Unauthorized with empty body.
                        ctx.Context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                        ctx.Context.Response.ContentLength = 0;
                        ctx.Context.Response.Body = Stream.Null;

                        // - or, redirect to another page. -
                        // ctx.Context.Response.Redirect("/");
                    }
                }
            }
        });

        app.UseRouting();

        app.UseAuthorization();

        app.UseCookiePolicy();
        app.UseSession();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

What I expect is when the user asks for /infographics the OnPrepareResponse verifies the request and if the user is authenticated sees the page. But, after a lot of code changing, the result is always the same (on my local machine):

This localhost page can’t be found

I tried to add this code to map the folder html in the root of the project as infographics but without success.

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(env.ContentRootPath, "html")),
    RequestPath = "/infographics"
});

Any ideas?

Update

This is working with not HTML files. I think the problem comes from the HTML file because they are static content for ASP.NET.

I put a breakpoint on the OnPrepareResponse and call the page infographics/index.html. The page is displayed (red arrow) and then the application stops on the breakpoint (blue arrow).

enter image description here

2
  • If you place breakpoints in that check for the request path in OnPrepareResponse, is it hit? Are the other files within the wwwroot folder accessible? Commented Dec 10, 2020 at 14:26
  • No, the application doesn't arrive in the OnPrepareResponse and I can't understand why. I tried with an application in ASP.NET Core MVC and I can reach the OnPrepareResponse and the function is working: the issue is all static files in the wwwroot are protected and for example the CSS is not applying. Commented Dec 11, 2020 at 8:22

2 Answers 2

4

For any one else that ends up here searching for protecting secure static files.

For .NET Core 5. Documentation has been updated to include how to handle this

Per the documentation:

To serve static files based on authorization:

  1. Store them outside of wwwroot.
  2. Call UseStaticFiles, specifying a path, after calling UseAuthorization.
  3. Set the fallback authorization policy.

Make sure to add a fallback policy as well:

    services.AddAuthorization(options =>
    {
        options.FallbackPolicy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
    });

See: Static file authorization

Sign up to request clarification or add additional context in comments.

1 Comment

The problem with this is that everything under wwwroot becomes protected, and that is not desired in case you have assets used for the login page, for instance.
1

I would recommend to place that folder outside the wwwroot folder, which will remove the static file access to it.

You can then serve your files from a controller like so:

public IActionResult GetInfographic(string name)
{
    var infographicPath = resolvePath(name);
    return new FileStreamResult(new FileStream(infographicPath, FileMode.Open, FileAccess.Read), mime);
}

given this is a controller you can use the [Authorize] tag to secure it any way you'd like.

3 Comments

You need to be vary careful with this type of solution from a security perspective
@boggy could you elaborate? Or, from a security perspective, would you mind sharing what's the best practice in the most current .NET version (9)?
What I meant is that you need to make sure name doesn't contain some funky stuff that may lead the app to read files that you don't want to be accessed. That's why I said that. Hackers are usually very crafty. For instance, don't allow dots, apply a very strict validation to the value contained in the variable name.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.