I have a hosted blazor web assembly app where I want to secure file downloads so that only a user with the correct role / permissions can download a file from the folder. I do not want an anonymous user to be able to download any files from this folder.
The files are located in a folder named Uploads in the server / api project, so they are not in the wwwroot folder.
The server pipeline looks like:
...
app.UseAuthorization();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine(builder.Environment.ContentRootPath, "Uploads")),
RequestPath = "/SellerFiles"
});
//app.Map("", appBuilder =>
//{
// appBuilder.UseFilter();
// appBuilder.UseFileServer(new FileServerOptions
// {
// FileProvider = new PhysicalFileProvider($@"{Path.Combine(builder.Environment.ContentRootPath, "Uploads")}"),
// RequestPath = new PathString("/SellerFiles"), //empty, because root path is in Map now
// EnableDirectoryBrowsing = false
// });
//});
app.MapControllers();
...
UseFilters looks like
public class FilterMiddleware
{
private readonly RequestDelegate _next;
public FilterMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
List<Claim> claims = httpContext.User.Claims.ToList();
await _next(httpContext);
//if (true)
//{
// //proceed serving files
// await _next(httpContext);
//}
//else
//{
// //return you custom response
// await httpContext.Response.WriteAsync("Forbidden");
//}
}
}
public static class FilterMiddlewareExtensions
{
public static IApplicationBuilder UseFilter(this IApplicationBuilder applicationBuilder)
{
return applicationBuilder.UseMiddleware<FilterMiddleware>();
}
}
When a user requests a file from https://mywebsite.com/SellerFiles/..., I want to serve the file if they are authenticated, but not if they are anonymous.
I tried implementing the commented code app.Map UseFileServer, but I wasn't able to configure it correctly. I played around with it alot and I was not able to get it to hit a breakpoint in public async Task InvokeAsync when I made a request to /SellerFiles.
Right now, an authenticated user can download a file from the Uploads folder by making a call to /SellerFiles, but so can an anonymous user.
How can I properly protect the files?
Edit I think I found the problem, but I am not quite there yet. I followed the guidance here: Microsoft Link to Static File Authorization
I set the authorization fallback policy, and it works well. I had to make some controller methods AllowAnonymous. So now the Uploads folder should require authorization, but I can still download files when not logged in.
builder.Services.AddAuthorizationCore(options =>
{
var type = typeof(Permissions);
foreach (var permission in type.GetFields())
{
options.AddPolicy(
permission.GetValue(null)?.ToString() ?? "",
policyBuilder => policyBuilder.RequireAssertion(
context => context.User.HasClaim(claim => claim.Type == "Permissions" && claim.Value == permission.GetValue(null)?.ToString())));
}
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
app.UseSwagger();
app.UseSwaggerUI();
}
else
{
app.UseResponseCompression();
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseCors("AllowAll");
app.UseAuthentication();
app.UseSerilogRequestLogging();
app.UseStaticFiles();
app.UseAuthorization();
app.UseBlazorFrameworkFiles();
app.MapControllers();
app.MapFallbackToFile("index.html");
app.MapHangfireDashboard();
app.MapHub<SignalR>("/chathub");
using (var scope = app.Services.CreateScope())
{
var jobService = scope.ServiceProvider.GetRequiredService<IScheduleJobs>();
jobService.Run();
//do your stuff....
}
var options = new DashboardOptions
{
Authorization = new IDashboardAuthorizationFilter[]
{
new HangfireDashboardJwtAuthorizationFilter(tokenValidationParameters, "Administrator")
}
};
app.UseHangfireDashboard("/hangfire", options);
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "Uploads")),
RequestPath = "/SellerFiles"
});
app.Run();
What am I missing?
Well, I solved my issue and wanted to show the solution. I ended up calling an API endpoint and streaming the file back to the client. Then I use a simple Javascript function to create an
A tagand invoke the download. The user chooses where to save the file. Also, I removed all of the modifications I had made to Program.cs.This is my razor.cs code which is invoked by the user clicking the download button.
This is my OrderService.DownloadFileStream method.
This is my controller endpoint.
And this is the Javascript function.
Since the files are in an Uploads folder outside of wwwroot, users cannot get to
https://mywebsite.com/Uploadsor any subfolders, so my files are protected. I plan to add checks in the controller endpoint to ensure that the user really is authorized to download the file, i.e., they paid for it.