Gabe
Gabe

Reputation: 1186

How do I serve static files only to authorized users?

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!

Upvotes: 44

Views: 34705

Answers (8)

Yas Ikeda
Yas Ikeda

Reputation: 1096

The sample code 8.x/StaticFileAuth_ and 9.x/StaticFileAuth in GitHub dotnet/AspNetCore.Docs repo provides a different solution from the sample code for 6.x.

The solution is an endpoint returning TypedResults.PhysicalFile. It requires neither UseStaticFiles nor UseFileServer.

The code below is essential parts of the sample code with some modifications. For a complete sample, please follow the link above.

I modified TypedResults.PhysicalFile to Results.File.

I modified /private/{fileName} to /private/{**fileName}, which allows the route mapping to match paths like /private/docs/index.html.

Don't forget to also change the string fileName argument to nullable, string? fileName. It makes the mapping work for /private as well.

I also added FileExtensionContentTypeProvider to open the content on browsers.

const string ROOT_DIR_OF_PRIVATE_CONTENTS = "wwwprivate";

builder.Services.AddAuthentication();
builder.Services.AddAuthorization(option =>
{
    option.AddPolicy("AuthenticatedUser", b => b.RequireAuthenticatedUser());
});

var app = builder.Build();

app.UseRouting();

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

app.MapGet("/private/{**fileName}", IResult (string? fileName) =>
    {
        var filePath = Path.Combine(builder.Environment.ContentRootPath, ROOT_DIR_OF_PRIVATE_CONTENTS, fileName ?? "");
        if (File.Exists(filePath))
        {
            var provider = new FileExtensionContentTypeProvider();
            if (!provider.TryGetContentType(filePath, out var contentType))
            {
                contentType = "application/octet-stream";
            }
            return Results.File(filePath, contentType);
        }
        return Results.NotFound("No file found with the supplied file name");
    })
    .WithName("GetFileByName")
    .RequireAuthorization("AuthenticatedUser");

Upvotes: 0

Ryan O'Neill
Ryan O'Neill

Reputation: 5687

This has changed to a simpler setup for .Net 8. Microsoft has a full writeup here https://learn.microsoft.com/en-us/aspnet/core/fundamentals/static-files?view=aspnetcore-8.0#static-file-authorization.

Essentially, it involves setting a FallbackPolicy as follows;

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

var app = builder.Build();

app.UseStaticFiles();

app.UseRouting();

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

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
           Path.Combine(builder.Environment.ContentRootPath,     "MyStaticFiles")),
    RequestPath = "/StaticFiles"
});

Note from the page on secure outside of wwwroot;

In the preceding code, the fallback authorization policy requires all users to be authenticated. Endpoints such as controllers, Razor Pages, etc that specify their own authorization requirements don't use the fallback authorization policy. For example, Razor Pages, controllers, or action methods with [AllowAnonymous] or [Authorize(PolicyName="MyPolicy")] use the applied authorization attribute rather than the fallback authorization policy.

RequireAuthenticatedUser adds DenyAnonymousAuthorizationRequirement to the current instance, which enforces that the current user is authenticated.

Static assets under wwwroot are publicly accessible because the default Static File Middleware (app.UseStaticFiles();) is called before UseAuthentication. Static assets in the MyStaticFiles folder require authentication. The sample code demonstrates this.

They link to a full demo with source on GitHub at https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/fundamentals/static-files/samples/6.x

Upvotes: 0

S.A.Parkhid
S.A.Parkhid

Reputation: 2868

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();

Upvotes: 0

john lee
john lee

Reputation: 151

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");
                }
            }
        });

Upvotes: 15

Renzo Ciot
Renzo Ciot

Reputation: 3846

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();
});

Upvotes: 4

Clint B
Clint B

Reputation: 4700

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.

Upvotes: 36

Pragmatic Coder
Pragmatic Coder

Reputation: 514

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();
               });

Upvotes: 4

Guy
Guy

Reputation: 1502

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

Upvotes: 10

Related Questions