I couldn't like the way of "Microsoft Docs" recommended to me...
A few weeks ago, I started a new ASP.NET Core web application project.
I had to protect static files with authorization on that application, but I had no idea how to implement it at that time.
Therefore, I started internet searching with the keywords "protect static files, authorization, asp.net core".
I got numerous search results, and many of the search results were questions and answers on "stackoverflow.com".
A part of those answers recommended us to try to one of the following solutions:
- (A) Insert ASP.NET Core middleware that your custom implementation into HTTP process pipeline at the before of static files middleware, and reject requests that aren't authorized.
- (B) Place static files you want to authorize to outside of "wwwroot", and serve it by ASP.NET Core MVC controller that your custom implementation, instead.
Especially, the solution (B) is explained by Microsoft official document site as you can see in the following URL:
However, I couldn't use those solutions for some reasons specified with the constraint of this project.
So, I continued reading the search results, and finally, I found the solution that I prefer.
The solution that I prefer is, hook the OnPrepareResponse
call back point of static files middleware.
"Static Files" middleware built-in ASP.NET Core provides a good hook point! 👍
When we register the "Static Files" middleware built-in ASP.NET Core into the HTTP process pipeline, we can also specify a StaticFileOptions
option argument.
StaticFileOptions
class has the property of a good hook point that allows us to insert a process before serving static files.
That property name is OnPrepareResponse
.
We can set a call back function to the OnPrepareResponse
property, then that function will be called back before serving each static file, and we can change the response rely on authorization state!
So I copied sample code from the answer on "stackoverflow.com" (following URL), and tried it.
Of course, we have to register "Authentication" middleware at the before of "Static Files" middleware to be available detect authenticated or not.
My sample code was like this:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseAuthentication();
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
if (!ctx.Context.User.Identity.IsAuthenticated)
{
// respond HTTP 401 Unauthorized.
ctx.Context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
...
Oh, NO! Anyone can see my secret file!
I built my sample code and run it, but...
Oh, NO! Anyone can see my secret file!
My call back function exactly returned the "401 Unauthorized" HTTP status to the browser, but it didn't stop the response body!
I have to stop the entire responding!
I have to not only return HTTP 401 but stop the entire of responding.
To do this, I appended 2 lines in my call back function like this:
...
// respond HTTP 401 Unauthorized, and...
ctx.Context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
// Append following 2 lines to drop body from static files middleware!
ctx.Context.Response.ContentLength = 0;
ctx.Context.Response.Body = Stream.Null;
..
This code drops the writing to the stream of response body from "Static Files" middleware, because that code replaces the body stream to System.Null
.
The all of contents that wrote into System.Null
is discarded, and it doesn't cause any effects.
Finally, I could protect the secret static files.
Please remember, to protect those secret static files, we have to concern browser caches.
In some cases, I could see the secret file from browser cache even if I wasn't authenticated after signed out from the application.
I avoided this problem by adding the "Cache-Control" header to the response.
ctx.Context.Response.Headers.Add("Cache-Control", "no-store");
If you want to redirect...
If you want to redirect to another page such as "Sign in" page instead of returning "HTTP 401", yes, you can do it by like this code:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseAuthentication();
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
if (!ctx.Context.User.Identity.IsAuthenticated)
{
// Can redirect to any URL where you prefer.
ctx.Context.Response.Redirect("/")
}
...
Conclusion
- We can protect static files with authorization on the ASP.NET Core web application by using the
OnPrepareResponse
property of the options argument for "Static Files" middleware. - Don't forget that place the calling
UseAuthentication()
at before of the callingUseStaticFiles(...)
. - We have to drop the entire of the response body from "Static Files" middleware when the request is unauthorized.
- Please consider cache control to protect static files perfectly.
- We can also redirect to another page such as "Sign in" page instead of returning "HTTP 401".
The entire of my sample code is public on the GitHub repository of the following URL:
The live demo site is also available on the following URL:
I'm happy if this article helps your ASP.NET Core programming life.
Happy coding :)
Top comments (5)
Nice article. Thanks for sharing. I'd like to add that when setting up your files outside of "www" root (in my case "/docs"), you will need to add the following:
This only works with cookie authentication. If you have another scheme, say JWT, authorization using the OnPrepareResponse hook won't work. It's also important to note that it's a bad practice to store sensitive data under wwwroot. if you want to secure files, say uploaded attachments, they should be stored outside of the webroot directory and delegated to a controller method.
Doubt.
Did you try it?
I tried access token-based authentication scenario, and it works fine as expected.
You can get the project file that I tried from the link below.
In the first place,
HttpContext.User.Identity
does not depend on any kind of authorization scheme.The role of authenticating user and building
HttpContext.User.Identity
isAuthentication
middleware.What I did is just referenced the
HttpContext.User.Identity
that is a result ofAuthentication
middleware's work.Yes, we have to consider well about this point.
However, I can't agree with all of your opinions, at this time.
Essentially and ultimately, there is no difference between
StaticFiles
middleware and custom controller method (that you said) on the side that both of them respond to static files to a user agent.A few years ago, I have saw the custom controller that respond static files to user agents had a "directory traversal" security hole.
And also, I have heard another story that the custom controller stored uploaded file to AmazonS3 but the endpoint of AmazonS3 bucket was allowed public access due to configuration mistake.
These stories tell me that protecting static content is not simple.
Therefore, I could not agree with the simple judgment that storing some secret static files under the wwwroot folder is bad practice.
After days I may change my opinion to the same as your opinion, but it is not now, yet.
I need to check if user is admin and so by putting UseStaticFiles after UseAuthorization, I'm losing access to all static files, no JS or CSS working... seems like a bug?
I'm facing a funny issue. The security part is working but without authentication, the browser couldn't find CSS or JavaScript under the wwwroor folder.
Do you have the same issue?