Simple File Download Protection with ASP.NET

4.32 (50 votes)

When it comes to protecting files from unauthorised downloading, the vast majority of articles offer solutions that involve mapping common file types (.pdf, .txt, .doc etc) to ASP.NET within Internet Information Services.  However, often in a shared hosting environment, you don't have access to IIS and the hosting company will not agree to providing such mappings for you, so what do you do?

Let's start by examining the problem is a little more detail first.  Forms Authentication in ASP.NET only works with requests that are handled by ASP.NET in IIS 6.0 (typically Windows Server 2003), which means that non-ASP.NET content bypasses aspnet.dll, and is not subject to it. Let's say you have a folder within your application called Private.  You set up Forms Authentication to protect this folder, such as in the following web.config snippet:

  <authentication mode="Forms">
    <forms loginUrl="Private/Login.aspx" defaultUrl="Private/Default.aspx">

      <credentials passwordFormat="Clear">
        <user name="mike" password="test" />

<location path="Private">

      <deny users="?" />


The URL to your Private folder is  Any requests for that URL will invoke the default document, which in this case is Private/Default.aspx.  Since that has been protected under Forms Authentication, and since all .aspx files are mapped to aspnet.dll, Forms Authentication kicks in and users who have not already logged in will be redirected to Private/Login.aspx.  Login.aspx contains a straightforward Login Control:

<form id="form1" runat="server">

  <asp:Login ID="Login1" runat="server" onauthenticate="Login1_Authenticate" />


And the code-behind contains the authentication logic:

protected void Login1_Authenticate(object sender, AuthenticateEventArgs e)
  string username = Login1.UserName;
  string password = Login1.Password;
  if(FormsAuthentication.Authenticate(username, password))
    FormsAuthentication.RedirectFromLoginPage(username, false);

Users who successfully authenticate will be directed to Default.aspx, which contains links to downloadable files:

<form id="form1" runat="server">
<a href="HelloWorld.txt">Click Here to Get File</a>


This seems to work, but if a non-authenticated user just enters into their browser, the file will be served, as ASP.NET is not configured to handle .txt files.  As I mentioned before, the vast majority of articles on this topic show how to map .txt to aspnet.dll within IIS, create an HttpHandler to manage the file access, and then register that handler within the web.config file.  If you do not have access to IIS, there is a simple workaround.  The first thing to do is to move all download files to a location where they cannot be browsed.  Ideally, your web hosting company will have provided you with access to at least one folder above the root folder of your application.  This is ideal, because no one can browse that folder since it is not part of the application itself.  However, if you only have access to the root folder and its contents, there is still at least one other option - App_Data.  Anything placed in App_Data is protected by ASP.NET, and requests for items within it are met with a 403 - Forbidden error message.

Once you have moved your files, you need a means to serve them to authenticated users, and an HttpHandler will do the job easily.  Just go to Add... New Item, and select Generic Handler.  You should be met with a new file with a .ashx extension containing code like this:

public class MyFileHandler : IHttpHandler

  public void ProcessRequest(HttpContext context)
    context.Response.ContentType = "text/plain";
    context.Response.Write("Hello World");

  public bool IsReusable
      return false;

The Handler contains two methods - ProcessRequest and IsReusable.  The first houses the logic that needs to be run to process the current request, and the second dictates whether the handler can be pooled and reused for other requests.  For the sake of simplicity, the default value of false can be left as it is.  The point about the handler, created from the Generic Handler option with its .ashx extension is that it is already mapped to aspnet.dll, so it can take part in Forms Authentication.  Not only that, but it does not need to be registered within the web.config file. Now its simply a matter of adding some logic to validate the user, and retrieve the file they are after:

public class MyFileHandler : IHttpHandler


  public void ProcessRequest(HttpContext context)
    if (context.User.Identity.IsAuthenticated)
      string filename = context.Request.QueryString["File"];
      //Validate the file name and make sure it is one that the user may access
      context.Response.Buffer = true;
      context.Response.AddHeader("content-disposition", "attachment; filename=" + filename);
      context.Response.ContentType = "application/octet-stream";

      context.Response.WriteFile("~/App_Data/" + filename);

  public bool IsReusable
      return false;

This is really simple.  After establishing whether the current user is authenticated, the handler checks the querystring for a filename. At that point it is important to validate the filename, to make sure that it is one that the user can have access to. It is possible for a user to alter the querystring to point to folders above App_Data, such as the root directory, and request web.config by passing ../web.config into the querystring.  This will throw an exception on most servers, as the ../ notation for parent paths is disabled by default.  However, as a couple of commentators have mentioned below, this is not always the case.

Then the method simply uses Response.WriteFile() to deliver the file.  I have set the ContentType of the file to application/octet-stream, and the content-disposition to attachment above, which will cover any type of file and always force a Save or Open dialogue box.  You may prefer to check the file extension and set the ContentType accordingly.  You may also want to add some error checking logic to ensure that a querystring value has been passed, that the file exists etc.

However, if you are not using FormsAuthentication out of the box, you may be checking a session variable on each page to see if the user is logged in instead.  This being the case, you need to know that HttpHandlers do not have access to session state by default, so references to session variables will fail.  One change is all that is required, and that is to make your HttpHandler implement IReadOnlySessionState (or IRequiresSessionState if you want to modify session variables).  The HttpContext object that is passed in to the ProcessRequest() method provides access to session variables through its Session property.

public class MyFileHandler : IHttpHandler, IReadOnlySessionState



That just leaves one question - how does the file name get into the querystring?  Going back to Private/Default.aspx, we simply amend the link to point to the handler instead:

<form id="form1" runat="server">

<a href="MyFileHandler.ashx?File=HelloWorld.txt">Click Here to Get File</a>

There we have it Simple authentication checks made before delivering files, without having to register the handler in the web.config file, or mess about with IIS settings.  If you are being hosted on a server that runs IIS 7.0, things are a lot easier. With its new Integrated Pipeline model, a simple change to your application's web.config file will ensure that all content within your application is always handled by ASP.NET, so that non-ASP.NET content can take part in ASP.NET Forms Authentication.  This article details how to make that change.


You might also like...

Date Posted:
Last Updated:
Posted by:
Total Views to date: 124818


- bincoder

Mike, your site is great, thx again

- Abdul Rauf

Nice article. I was looking for that.

- ALi


fair and clean demonstration, i like it

- CyberFive

One problem with this solution. A logged in user suddenly has access to a lot of other files too. What is preventing the user from changing the href to "MyFileHandler.ashx?File=../web.config"
and getting the web.config send?
I know a lot of hosters prevent the use of .., but not all.

- rtpHarry

Mike, what would happen if I called for example MyFileHandler.ashx?File=../web.config

- Mike

@ CyberFive and rtpHarry

Both of you have made a very valid point. My article was intended simply to illustrate how to achieve the objective, and as with many articles of this type, I have not cluttered it with exception handling or validation. I'm always in two minds whether including these obscures the general points, given that I believe most of the people reading my site are at beginner level.

In this specific case, a simple validation check on the query string value, and a whitelist of file extensions will help prevent people downloading config files etc where parent paths have not been disabled. It will amend the article to highlight the need to validate the querystring.

It sort of goes without saying that you should never ever trust user input, and mentally, since I have made that point in many of my articles, I guess that I fall into the trap of thinking readers have been told. I keep forgetting that each article might be the first one a reader sees.

- rtpHarry

When I am writing my articles I am usually conscious that people will likely just copy and paste my code straight into their projects so I try to include the validation and error checking as it is essential for the code to be usable.

If you are trying to allow access to files that users have uploaded then storing the filenames in a database would work and then you can use the file id in the querystring which isn't susceptible to this kind of attack.

- Mike


I am also conscious that people may just be looking for a quick fix, in which case my articles are not geared for that. They are intended to be educational, and the breaks in code and absence of downloads hopefully discourages the copy/paste coder from lingering on my site too long. I keep getting asked to provide downloads, but consistently refuse to.

- liyou

good article what i need.

- A-Dubb

In order to bypass ../web.config, you could use the static System.IO.Path class to retrieve the file name portion of the path and ignore the rest. After that you could simply append the value to "~/App_Data" like so:

string fileToServe = string.Format("~/App_Data/{0}", Path.GetFileName(path));

You can even check to ensure that the provided filename has an extension like so:

// execute code here

- Taliesin

There a number of other issues related to compression, client & server side caching and partial downloads that should be taken care of when you serve content from http handlers. Otherwise visitors are going to be hitting your handler very hard and using up all your bandwidth.

For one that takes care of this features, check out:

- Catalin D.

Very nice your article.

- Cam Luc

This is very helpful.

- Jay

Using HttpResponse.TransmitFile() is propably the more appropiate way than using Response.WriteFile().

- abex

it is important for undergraduate students.10Q

- abhimanyu

very-very nice article. is there any way to restrict the files which is only zipped and exist in a folder named downloads inside wwwroot directory of shared hosting server. by default download directory will be accessible but i am wishing to protect this directory for all jobs. only authenticated user can access this.

- Mike


To prevent visitors browsing files, you need to put them in a folder which doesn't allow access, such as one outside of the root or App_Data.

That's the point of the article.

- Jonathan Wood

Note that with ASP.NET 4.0 and IIS7, it's now a simple matter to map a particular file type to a custom handler. And it can be done strictly through web.config without delving into IIS. I just covered this topic in an article at

- darcy

how do you add the filles to App_Data

- darcy

when i click on the link from the homepage it just takes me to a white page and does nothing

- Yogendra

this is that article I was searching a lot thanks mike for your experiance sharing.

- kisan

is it possible to protect video from download

- Mike

See either my reply to abhimanyu or the link that Jonathon Wood provided.

- gfox

thank you so much

- Bob

Instead of using the file name, use a GUID as the filename. Then check the directory for the guid filename. If it exists, send it, if not throw error or display message accordingly.

- Michael Hidalgo

You are not properly Sanitizing the request parameter
context.Request.QueryString["File"];. A bad guy could enter any arbitrary value in the File parameter and compromise the system.

- mikesdotnetting


I leave it to the user to validate the input. There is a comment in the code to that effect and the article also points out the importance of validating the file name and the potential dangers of not doing so.

- Kelsey piguet

I don't understand

- Jeff Easlick

Is any of this possible using ASP.NET Web Pages (WebMatrix friendly)? I'm utterly brand new to all of this and any help would be greatly appreciated.

- Mike


You can swap the handler for a cshtml file like in this article:

- shashasha

Thank you,
Nice and simple

- Ben

Is it possible to setup your project to publish files to outside of your root directory? I would prefer to be able to still track these files in TFS and have my project publish them to a separate directory in the wwwroot instead of me going and moving the files manually. I tried something like what is described at the following link: but can't get the <DestinationRelativePath> to traverse to the wwwroot it seems.

- Mike

You can use Server.MapPath to save uploaded files anywhere on the server so long as you have Write access to the chosen location. I am not familiar enough with TFS to advise on how to include them in source control.

- Miguel

This is awesome. I struggled for a while on how to prevent users from just downloading files but this clearly gave me what i needed the necessary steps. I'm must appreciative for this awesome step through.

- J_R


Thank you for taking the time to write this. It really helped me - though I could not figure out how to reference a directory above the root. Kept getting errors that I could not go beyond that, even though I have a dedicated server with full admin access. Is this an IIS issue? In any event, App_Data with relevant validation worked a treat - thank you so much.

- Mike


You just need to provide the full file path to WriteFile method

Recent Comments

Gfw 03/02/2017 09:48
In response to Free SSL Certificates On IIS With LetsEncrypt
I have used WinSimple for about the last 9 months - works great. One thing that you want to make of...

Ted Driver 02/02/2017 13:24
In response to Free SSL Certificates On IIS With LetsEncrypt
This looks great is you have command line access to your web server - what about those of us on Is...

Carl T. 06/11/2016 05:43
In response to Server.MapPath Equivalent in ASP.NET Core
Very succinct and easy to follow. Worked perfectly the first time for me. Thanks!!...

Manoj Kulkarni 04/11/2016 05:47
In response to Entity Framework Core DbContext Updated
Great post....

Sivu 19/10/2016 08:21
In response to Entity Framework Core TrackGraph For Disconnected Data
Oh that's very very very nice ! Thanks for the write up Mike, much appreciated for the taking the to...

Mark 12/10/2016 16:42
In response to ASP.NET Web Pages vNext or Razor Pages
Although "Web Pages" was removed from the roadmap, has it just been renamed to "Razor Pages"?...

Satyabrata 12/10/2016 09:20
In response to Entity Framework Core TrackGraph For Disconnected Data
Nice article. Please write more articles featuring ASP.Net web pages. Thank you...

Julian 26/09/2016 14:27
In response to Loading ASP.NET Core MVC Views From A Database Or Other Location
Fantastic, many thanks Mike! Had got half way down this road before finding your article - saved...

Abolfazl Roshanzamir 14/09/2016 05:36
In response to Loading ASP.NET Core MVC Views From A Database Or Other Location
Nice article. Thanke you so much ....

cyrus 02/09/2016 15:12
In response to ASP.NET Web Pages vNext or Razor Pages
I've got some news. As Damian stated in this link: “We...