42

I have a collection of Excel spreadsheets that I'd like to serve in my ASP.NET 5 webapp only to authorized users.

  1. Where should I store the files? I assume in wwwroot (e.g., wwwroot/files).
  2. If in wwwroot, how do I allow access only to authorized users? (I'd like to serve them up as a [Authorize] FileResult from the controller, but this still leaves the files open to direct access through a URL I believe.)
  3. How do I reference a location in wwwroot through my FileResult action in the controller?

Thanks much!

Set
  • 47,577
  • 22
  • 132
  • 150
Gabe
  • 1,166
  • 1
  • 12
  • 15

6 Answers6

35

Yes, they should go in wwwroot. Currently there is no built-in way to secure wwwroot directories. But creating a middleware module to accomplish it is pretty straightforward. There is an easy to follow tutorial here.

If you're not familiar with developing middleware, I posted a GitHub project that shows how to create middleware in three easy steps. You can download the project here.

You don't need a controller to access static files.

Bharata
  • 13,509
  • 6
  • 36
  • 50
Clint B
  • 4,610
  • 2
  • 18
  • 22
  • 1
    Awesome. Thank you. Looking into it. (I'm only using an action because I want to send notifications when files are downloaded.) – Gabe Apr 21 '16 at 17:12
  • 7
    Or you could put them *outside* of wwwroot, where the static file middleware doesn't touch. Like a good old App_Data folder (which admittedly you have to create yourself) – blowdart Apr 21 '16 at 17:16
  • 1
    @Gabe - You could add that functionality to your middleware. And maybe mark my post as the accepted answer. :-) – Clint B Apr 21 '16 at 17:31
  • This worked like a charm. Only stumble I encountered from the tutorial you linked is to make sure that the reference to the middleware in startup.cs/Configure was after app.UseIdentity(), but still before app.UseStaticFiles(). – Gabe Apr 22 '16 at 13:49
  • 2
    Actually let me be clearer, you absolutely should *NOT* put them under wwwroot, as there is a risk that the static file middleware will server them, as it's meant to do for any file under wwwroot it understands. Putting things under wwwroot is a huge possible information disclosure risk if you mess up your middleware order. – blowdart Apr 22 '16 at 16:54
  • But if you put things *outside* wwwroot, in Core 2.0 at least... they're not Published to production. – philw Dec 13 '17 at 18:01
  • @philw: Not by default, but you just have to include the directory in your project file. – Chris Pratt Mar 14 '19 at 16:03
  • Just to back up @blowdart: The private spreadsheets should go in `wwwroot` only if _all_ the files in `wwwroot` are private. Public and private data should not be stored side by side. (See also [this answer](https://stackoverflow.com/a/48585284/9069356).) – Dominus.Vobiscum May 29 '19 at 19:20
  • Please modify your answer. There is build-in middleware UseAuthorization + FallbackPolicy, UseStaticFiles. It's explicitly described in the documentation (https://learn.microsoft.com/en-us/aspnet/core/fundamentals/static-files?view=aspnetcore-5.0#static-file-authorization) – Dmitriy Ivanov Mar 04 '21 at 10:29
15

For authentication check while retrieving file:

        app.UseStaticFiles(new StaticFileOptions()
        {
            OnPrepareResponse = (context) =>
            {
                if (!context.Context.User.Identity.IsAuthenticated && context.Context.Request.Path.StartsWithSegments("/excelfiles"))
                {
                    throw new Exception("Not authenticated");
                }
            }
        });
petrosmm
  • 528
  • 9
  • 23
john lee
  • 151
  • 1
  • 2
  • 2
    This will work only if the authentication scheme being used is the default authentication scheme, otherwise `HttpContext.User` will not be updated (see [this issue](https://github.com/aspnet/Security/issues/1318)). – Dominus.Vobiscum May 29 '19 at 19:06
10

in .net core create a dedicated directory www in same level as wwwroot, and use the following code:

public HomeController(IHostingEnvironment hostingEnvironment)
{
    _hostingEnvironment = hostingEnvironment;
}

[Authorize(Roles = "SomeRole")]
public IActionResult Performance()
{
    return PhysicalFile(Path.Combine(_hostingEnvironment.ContentRootPath,
                                     "www", "MyStaticFile.pdf"), "application/pdf");
}

Based on the following answer (for .netCore): static file authorization

ahsteele
  • 26,243
  • 28
  • 134
  • 248
Guy
  • 1,232
  • 10
  • 21
  • Note that the action above can be requested by a view. Say for example that the /Home/Performance returns an image/png. Another Action - kind of a Home/ParentPerformance, could return View() the standard way, and inside the view you could reference the protected static resource. For example: `` This has the advantage that you are still serving an HTML envelope with your shared _Layout menu system and look and feel, while still maintaining security on the static file. – Guy Oct 28 '17 at 18:32
  • BTW, I was serving PDF till recently, only to discover that **pdf experience on Android is terrible**, forcing download/cancel, and ending up with users not getting a smooth experience, unlike IOS, including some zooming out of screen boundaries. In short, not what you'd expect. Since then I'm exporting the PDF to PNG using [InkScape](https://inkscape.org), and this aligns perfectly with the `` inside an html mentioned above. – Guy Nov 06 '17 at 14:14
  • 3
    Also described here: [Static files in ASP.NET Core: Static file authorization](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/static-files?view=aspnetcore-3.1#static-file-authorization) – Jeppe Apr 19 '20 at 16:38
4

If you have a login form (Login.html), a simple solution is to redirect the user to the login page if user is not authenticated and he's requesting a protected resource (file under /protected folder). In Startup.cs, in Configure method insert this code:

app.Use(async (context, next) =>
{
    if (!context.User.Identity.IsAuthenticated && context.Request.Path.StartsWithSegments("/protected"))
    {
        context.Response.Redirect("/Login.html");
        return;
    }
    await next.Invoke();
});
Renzo Ciot
  • 3,746
  • 2
  • 25
  • 29
  • but how you will ensure that files under /wwwroot are protected ? – rahulaga-msft Sep 06 '18 at 09:10
  • If you just remove the control on path, everithing is protected: if (!context.User.Identity.IsAuthenticated ) {... – Renzo Ciot Sep 07 '18 at 08:17
  • This will work only if the authentication scheme being used is the default authentication scheme, otherwise `HttpContext.User` will not be updated (see [this issue](https://github.com/aspnet/Security/issues/1318)). – Dominus.Vobiscum May 29 '19 at 19:07
  • 1
    There is the Authorization/Authentication subsystem in Asp.Net Core that handles the login/logout process as well, and manual redirect to a login page is a bad practice. – Dmitriy Ivanov Mar 04 '21 at 10:38
3

This is a very simple example, but it can be changed to check for specific roles, and the code can be moved out of the Startup.cs for more flexibility.

app.Use(async (context, next) =>
               {
                   if (!context.User.Identity.IsAuthenticated
                       && context.Request.Path.StartsWithSegments("/excelfiles"))
                   {
                       throw new Exception("Not authenticated");
                   }
                   await next.Invoke();
               });
ahsteele
  • 26,243
  • 28
  • 134
  • 248
Pragmatic Coder
  • 484
  • 1
  • 4
  • 17
  • 1
    Might be better to use `context.Response.StatusCode = 403` instead of throwing an exception. – Mark G Jul 28 '18 at 20:09
  • I think it depends on the situation. In my apps exceptions are caught, logged, and then a generic error page displays. For API's I would return the 403. – Pragmatic Coder Sep 18 '18 at 19:28
  • This will work only if the authentication scheme being used is the default authentication scheme, otherwise `HttpContext.User` will not be updated (see [this issue](https://github.com/aspnet/Security/issues/1318)). – Dominus.Vobiscum May 29 '19 at 19:06
  • @Dominus.Vobiscum I believe that it is due to a problem of not configuring the middleware (Authentication middleware) correctly. An authentication scheme should always be specified (default or not). Default is just a fallback, but always one scheme should be configured/registered/specified. `HttpContext.User` is a very essential hub to get started authenticating/authorizing. – Hopeless Oct 18 '20 at 22:42
0

To secure your files and available them to the authenticated users, easily create a folder named 'staticfiles' outside of the 'wwwroot' folder. then suppose you want to restric user access to some books, so create a 'Books' folder under the 'staticfiles' and then update your "Program.cs" or your middleware pipeline like this:

app.UseAuthentication();

app.UseStaticFiles(new StaticFileOptions
{
    
    OnPrepareResponse = (ctx) =>
    {
        var context = ctx.Context;
        context.Response.Headers.Add("Cache-Control", "no-store");
        if (context.Request.Path.Value.StartsWith("/staticfiles/Books"))
        {
            if (!context.User.Identity.IsAuthenticated)
            {

                context.Response.Redirect($"{context.Request.Scheme}://{context.Request.Host}{context.Request.PathBase}" + "/Identity/Account/Login");

            }
        }
    },
    FileProvider = new PhysicalFileProvider(
           Path.Combine(builder.Environment.ContentRootPath, "staticfiles")),
    RequestPath = "/StaticFiles",

});

app.UseAuthorization();
S.A.Parkhid
  • 2,772
  • 6
  • 28
  • 58