microsoft / dotnet

This repo is the official home of .NET on GitHub. It's a great starting point to find many .NET OSS projects from Microsoft and the community, including many that are part of the .NET Foundation.
https://devblogs.microsoft.com/dotnet/
MIT License
14.25k stars 2.2k forks source link

.Net 7 API - Showing Static files only to authenticated users and performance issues #1396

Closed Uraharadono closed 10 months ago

Uraharadono commented 11 months ago

Hello everyone,

For one of my projects I am working on, I want to add a feature to add image posts.Not only that I want users to be able to add images, but I want to enable those images to be visible only to registered users.

The second thing I want to accomplish is even for registered users (who can see these image posts) to not be able to access "View image" or contents of the file. The reason being is, I don't want anyone to be able to download images from my app, because I want to avoid possible legal issues of "distributing content without consent" or some stupid thing like that.

Keep in mind that I have Vue 3 app on client, and .Net 7 Api on backend.

SOLUTION 1: For my original solution, I have implemented the following helper method (which works):

    [NonAction] // I don't want this to be called outside my controllers 
    internal async Task<FileStreamResult> DownloadFile(string filePath)
    {
        var fileContents = await GetFileStream(filePath);
        var fileName = filePath.Split('/').Last();
        var mimeTypeFetchResult = new FileExtensionContentTypeProvider().TryGetContentType(filePath, out var contentType);

        // Defaults to pdf
        if (mimeTypeFetchResult == false)
            contentType = MediaTypeNames.Application.Pdf;

        return File(fileContents, contentType, fileName);
    }

And if I am using my controller I am using it like (this is an example code):

[Authorize]
        [HttpGet, Route("GetImageOriginalSolution")]
        public async Task<IActionResult> GetImageOriginalSolution()
        {
            var filePath = Path.Combine("C:\\Files\\UploadedImages\\38c8617a-14a5-4bf5-9f39-1bca4bc0dd4e.jpg");
            return await DownloadFile(filePath);
        }

I am basically downloading file on my vue client app, and I am reconstructing the Blobs to set an image source. If people try to "View image" it will just be uncohersive string of content characters.

This is working fine, but it is performance killer. Even in my local dev enviroment, the first 5 posts on Dashboard are loaded OK (with slight delay), but every post after makes the system laggy and unusable.

SOLUTION 2: After looking at solutions after the fact, I have found the following documentation about static files on .Net: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/static-files?view=aspnetcore-7.0#static-file-authorization-1

I have implemented a recommended solution:

Startup.cs

       ` var cacheMaxAgeOneWeek = (60 * 60 * 24 * 7).ToString();
        app.UseStaticFiles(new StaticFileOptions
        {
            // FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "MyStaticFiles")),
            FileProvider = new PhysicalFileProvider(Settings.BaseFolder),
            RequestPath = "/StaticFiles",
            // This will control how long does browser cache the image user has opened
            OnPrepareResponse = ctx =>
            {
                ctx.Context.Response.Headers.Append(
                     "Cache-Control", $"public, max-age={cacheMaxAgeOneWeek}");
            }
        });`

And if in my controller I am using it like so (this is example code):

[Authorize]
        [HttpGet, Route("GetImageMicrosoftDocsSolution")]
        public async Task<IActionResult> GetImageMicrosoftDocsSolution()
        {
            var filePath = Path.Combine("C:\\Files\\UploadedImages\\38c8617a-14a5-4bf5-9f39-1bca4bc0dd4e.jpg");
            return PhysicalFile(filePath, "image/jpeg");
        }

Both of these solutions work with Authorization, and unauthorized people cannot access them. The second solution even adds automatic caching. It also keeps people from downloading them, and honestly I dgaf about screenshots, as I have done my due diligence to prohibit people from downloading stuff.

My questions are basically:

Apollo9999 commented 10 months ago

To show static files only to authenticated users in .Net 7 API, you can use the following steps:

Create a folder for your static files outside of the wwwroot folder. In your Startup.cs file, add the following middleware to the Configure method: app.UseStaticFiles(new StaticFileOptions { OnPrepareResponse = (ctx) => { var context = ctx.Context; if (!context.User.Identity.IsAuthenticated) { context.Response.Redirect("/login"); } } }); This middleware will check if the user is authenticated before serving the static file. If the user is not authenticated, they will be redirected to the login page.

To prevent users from downloading the static files, you can set the Cache-Control header to no-store. You can do this by adding the following code to your StaticFileOptions object: context.Response.Headers.Add("Cache-Control", "no-store"); This will prevent the browser from caching the static file, so the user will have to authenticate every time they try to access it.

Here is an example of how to serve static files only to authenticated users in .Net 7 API:

public class Startup { public void Configure(IApplicationBuilder app) { app.UseStaticFiles(new StaticFileOptions { OnPrepareResponse = (ctx) => { var context = ctx.Context; if (!context.User.Identity.IsAuthenticated) { context.Response.Redirect("/login"); } } }); } } This code will create a folder called staticfiles outside of the wwwroot folder. Only authenticated users will be able to access the static files in this folder.

As for the performance issues, you can try the following things to improve it:

Use a CDN (Content Delivery Network) to serve the static files. This will help to reduce the load on your server. Optimize the images in your static files. This will reduce the file size and improve the loading time. Use caching to store the static files in memory. This will prevent the need to fetch the files from disk every time a user requests them.

Uraharadono commented 10 months ago

@Apollo9999 What you have written above sound like either some form of ChatGPT or copy-paste from somewhere. Anyways, thank you for even trying.

I have solved this by following these docs: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/static-files?view=aspnetcore-7.0 but they felt incomplete.

So I have search over internet how protect folder stuff works etc. e.g. this site: https://odetocode.com/blogs/scott/archive/2015/10/06/authorization-policies-and-middleware-in-asp-net-5.aspx

and examples on how others have done it: https://github.com/sample-by-jsakamoto/ProtectStaticFilesWithAuthOnASPNETCore/blob/master/ProtectStaticFilesWithAuth/Startup.cs

and found my solution. Whoever looks for it, should look into these links as well.