Click here to Skip to main content
15,878,814 members
Articles / Web Development / ASP.NET
Article

Secure File Download Page

Rate me:
Please Sign up or sign in to vote.
4.24/5 (40 votes)
19 Apr 20055 min read 354.1K   6.3K   95   62
How to design a page that protects your downloadable files.

Introduction

If you've developed any kind of e-commerce system that offers downloadable files, you've faced this problem. How do I protect my files from theft? Very often, the problem doesn't even concern payment. Many web-sites offer useful files for free. All they ask in return is that the person downloading the file simply enters some personal details, or fills out a form for the administrator's records. All too often, people don't even want to do that and they seek ways to circumvent the web server's security.

The key problem in this scenario is the fact that, no matter which way you look at it, ultimately, you have to offer users a link to the downloadable file. Once someone has that link, they can offer it to their friends and colleagues, who then simply circumvent all of your security features and download the file directly. None of the security features you put in place are worth anything if a user knows that final link to the downloadable file itself. I've seen sites that go to great lengths to "hide" these links (including masking the link by writing specific text in the browser's status bar when a user hovers over it). A simple "View Source" circumvents all these security measures.

The problem lies in the fact that your downloadable file (whether it be a .pdf, .doc, or .zip - or even a .exe) has no way to decide whether it should serve itself or not. You can't place logic in these files to decide whether or not to serve them.

One alternative is to place system security on the folder in which these files reside. This is not really viable though, as you would need to create a new system (Windows) account for each registration, send the person their username & password, and then remove their user account from the system once they have downloaded the file. This makes for an administration nightmare (not to mention an even bigger system security issue). And if you're hosting your site with an external ISP, it's highly unlikely that they'll allow you to create and delete user accounts on their server at will.

The simple solution below closes the above security hole by storing the downloadable file outside a web-site and then serving the file directly from a web page. In other words, you might link to MyFileServer.aspx and receive MyPdfDownload.pdf, for example - with no actual link to MyPdfDownload.pdf offered anywhere on your web-site.

Using the code

The code is fairly straightforward. What we're going to do is open a file stream and serve that instead of the usual text content. In other words, our .aspx page is going to behave more like a file-download. I'm using VB.NET, but this can just as easily be done using C#.

My code below assumes that you've created a folder called C:\My External\File Path\. Note, this folder exists in the C:\ drive and doesn't appear anywhere near C:\Inetpub\wwwroot or any other folder that serves as a web-site on the server. I'm assuming a file called MyPdfDownload.pdf exists in the above folder. You can add any file/ path you choose. Simply update the variables in the appropriate line of code below.

To design the application, first create a folder called C:\My External\File Path\. Then find a .pdf file (you can select any downloadable (.zip, .exe, .doc, .xls etc.) and store it in the above directory. Name it MyPdfDownload.pdf, or change the code below to reflect the file name you've saved in the above folder..

Now check that the directory has appropriate access for the ASP.NET account:

  • navigate to c:\My External\.
  • right-click on the File Path folder and click on Properties.
  • Select the Security tab.
  • If you can't see the ASP.NET account, add it.
  • Highlight the account and give the user Full Control.
  • Click Apply or OK.

Good. Now you have a folder outside the web. Now create a new ASP.NET web application in Visual Studio .NET.

Add a new Web Form and call it MyFileServer.aspx. Double-click on the page. This will open the code-behind (MyFileServer.aspx.vb) file and create your Page_Load event handler.

We're going to place our code inside this Page_Load() subroutine. This means that every time users link to our page, it will validate them and decide whether to download our external page or not. Think of this as a delegate function, or an alias for the actual file we want to download. The only difference is that now we can put server-side logic in place to decide whether or not we are prepared to serve the file. In other words, we're wrapping some intelligence around our downloadable file that verifies whether or not it should download itself.

Let's start coding.

VB
' First, we need to declare our variables

' We'll need a filestream object to fetch the pdf file from its location
' outside the web-site.
Dim fs As FileStream

' As we're not serving standard html,
' we'll need to tell the browser what type of content we're serving
' This value will need to be added to our response header.
Dim strContentType As String

' This is the path to the folder where our file will be kept.
' Note, we DON'T use the 
' Server.Mappath() method, as this would defeat the object.
' We want to keep the file outside the web application, so
' we can't use standard server object methods.
' In a real-world application you would store this value in a database
' or otherwise in web.config so that you can change it easily
' without having to recompile your code. I've hard-coded it here
' for readability. Note, you will have to ensure that the ASP.NET
' user account on your system has appropriate access rights to this folder
Dim strPath = "C:\My External\File Path\"

' We need to use this file name
' in the headers that we send to the browser. Obviously
' we don't want to show the user the full path
' to our file on the server,
' so I'm not including this in the path variable above.
' Even though they won't be able to access
' it without admin rights on your server, it will only confuse them.
' Users only need to see the file name,
' which they can then save on their own system once downloaded
Dim strFileName As String = "MyPdfDownload.pdf"

' Next we call our validation function to determine
' whether or not we are prepared to offer the user this file
' I'm not including that function in this article.
' You can create your own validation
' function based on your site's requirements
' You can even include code in this page to indicate
' whether a user has downloaded their file.
' Then save a cookie on their machine and make
' a note in your database. In any future attempts,
' you can check for the cookie to ensure that the user can ONLY download
' the file to the same machine. That way you can prevent
' them from giving their login details to others who could then 
' download the file from their machines.
If UserIsValid() Then

   ' Great. The user is valid and we're happy to give
   ' them the file. Now we open our file using the file stream object
   fs = File.Open(strPath & strFileName, FileMode.Open)

   ' Declare a byte array to hold our stream information.
   ' Note, we initialize the byte array to the file stream's size
   Dim bytBytes(fs.Length) As Byte

   ' Write the stream to the byte array
   fs.Read(bytBytes, 0, fs.Length)

   ' Close the file stream to release the resource
   ' This is important. If you don't close the resource,
   ' the next person trying to download your file
   ' may get an error saying the resource
   ' is still being used by another application.   
   fs.Close()
   
   ' Next we need to add some header information.
   ' These headers will tell the browser what it needs to do
   ' with the content we're serving

   ' The first header ensures that the file name
   ' is correct on the client side. This is extremely important! If you
   ' don't add this header, the browser will make
   ' some sort of arbitrary decision as to what the file should be called.
   ' It will either offer no name and force the user to select a name
   ' (As we all know, this is like giving a two-year-old a loaded gun)
   ' or it will simply name the file after the web page
   ' it's calling (eg. MyFileServer.aspx). This will save without any problem
   ' but when the user tries to open the file, their system
   ' will tell them it doesn't know what to do with a *.aspx file
   ' and you're going to slowly go insane over the next
   ' six months with support calls, trying to explain to users how to
   ' change the file extension from *.aspx to *.pdf on their system
   Response.AddHeader("Content-disposition", _
              "attachment; filename=" & strFileName)

   ' Next, we need to tell the browser what type of content
   ' we're serving. If we don't add this header, the user's browser
   ' will assume it is standard html and try to render your
   ' bytes as text. The page won't crash,
   ' but the user is going to see an unholy
   ' mess on their web page, with bundles of little blocks
   ' and funny faces that are absolutely meaningless to them.
   ' I'm using a standard application/octet-stream content type.
   ' It's better to find out exactly what MIME type your particular
   ' file extension is defined under, as this should produce
   ' better browser behaviour.
   ' However, for our purposes, this will work just fine
   ' This header tells the browser that it is serving
   ' an application file as a byte stream.
   ' The browser will know immediately
   ' that it shouldn't serve this file as text and will open
   ' the File Download box instead,
   ' in which it offers the user the ability to save
   ' the file. It will also use the Content-disposition header
   ' (see above) to auto-populate
   ' the Save File dialog box with the file's name
   Response.ContentType = "application/octet-stream"

   ' Now our headers are added, we can serve the content.
   ' To do this, we use the BinaryWrite() method of the server object
   ' This successfully streams our external file to the user,
   ' despite the fact that the file doesn't exist
   ' anywhere inside the web application
   Response.BinaryWrite(bytBytes)

   ' Call Response.End() so that no more
   ' content goes through to the client.
   ' The file has been downloaded,
   ' but if this method is not called, the page
   ' will continue downloading any remaining
   ' html/ text content and mess up the resulting stream.
   ' This method call ensures that the downloaded
   ' file doesn't end up corrupted with unwanted data   
   Response.End()
Else

   ' The validation function returned false.
   ' In other words, we don't want to allow this user access.
   ' Simply serve the user normal text/html
   ' informing them that they don't have access to the file
   Response.Write("Sorry. You don't have access" & _ 
     " to this file. Please return to the login page and try again.")
End If

Points of Interest

XP has some new security settings that may cause problems on the file download feature in browsers. You'll probably need some instructions for people with XP systems to follow. This is a short-term thing - and it's something they'll experience on any file download over the web. As users become accustomed to XP's settings and defaults, they should begin placing fewer support calls.

As a rule, I post the following under the FAQs of any site that uses the above code:

  • Q. I can't download documents from the site. When I click the Download button, the screen pops up, then immediately disappears. Why won't the files download?
  • A. You're probably using Windows XP, which has default settings that prevent you from downloading documents automatically. The only way around this problem is to disable XP's default browser settings and allow it to automatically download documents:
    • In your IE browser, click Tools | Internet Options
    • Go to the Security tab, then click the Custom Level button.
    • Under the Downloads section of the list, look for Automatic prompting for file downloads and click Enable.
    • Click OK.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
South Africa South Africa
Sean Young is the lead developer for a company based in Johannesburg, South Africa. His experience includes development of web applications using ASP, Java, and PHP. He also has experience in C++ and has developed several applications in this language, including a fully-fledged mail server.

His more recent projects include a web portal that allows users with no HTML knowledge to design and manage content on their own web sites. The application allows them to simply copy content from any office document, such as MS Word, or Excel and paste it into their web page.

The portal offers many features, including secure secure file download, and a custom form design interface that allows users to design their own forms on the fly. The form information is mailed to a specified recipient each time it is submitted.

Check out the author's Homepage to see this product in action!

Comments and Discussions

 
QuestionQuestion Pin
Chris Niedbala22-Aug-11 5:57
Chris Niedbala22-Aug-11 5:57 
Generalgreat Pin
Nitin S18-May-10 1:17
professionalNitin S18-May-10 1:17 
GeneralSave download count Pin
gogetsome31-Aug-09 5:13
gogetsome31-Aug-09 5:13 
Generalgreat article!!!! Pin
isaias20-Mar-08 21:32
isaias20-Mar-08 21:32 
Generalit Work for me Pin
dotNET.MC26-Feb-08 23:30
dotNET.MC26-Feb-08 23:30 
Generalthanks [modified] Pin
M.S. Babaei15-Nov-07 18:24
M.S. Babaei15-Nov-07 18:24 
QuestionHow to download Video file like mpeg Pin
Tejas Patel21-Oct-07 1:56
Tejas Patel21-Oct-07 1:56 
GeneralASP.NET is not authorized to access the requested resource Pin
Yabsley12-Sep-07 11:07
Yabsley12-Sep-07 11:07 
GeneralCannot open file Pin
Singh Saab24-Aug-07 6:12
Singh Saab24-Aug-07 6:12 
GeneralASP version Pin
wickyguru23-May-07 14:59
wickyguru23-May-07 14:59 
GeneralRe: ASP version Pin
paulclift24-Mar-09 4:38
paulclift24-Mar-09 4:38 
wickyguru wrote:
Is there a ASP version of the Secure File Download page application??




Long time after you've asked this, but I needed to use something like this in an ASP app we have running so I wrote this using the aspx version as a guideline.

	if CanDownload() then
	   strFileName  = Request.QueryString("filename"))
           strUPLOADdir = "c:\home\default\mydir\"

	   Response.AddHeader "Content-disposition", "attachment; filename=" & strFileName
	   Response.ContentType = "application/octet-stream"

  	   adTypeBinary = 1
  
           Set BinaryStream = CreateObject("ADODB.Stream")
  
           BinaryStream.Type = adTypeBinary
  
           BinaryStream.Open
  
           BinaryStream.LoadFromFile strUPLOADdir & strFileName
	   
	   Response.BinaryWrite BinaryStream.Read
	   
	   Response.End
	else
           Response.Write "Sorry you do not have access to this file, please return to the homepage and login."
	end if
%>

QuestionMultiple downloads problem Pin
Petrus Theron17-Jan-07 12:17
Petrus Theron17-Jan-07 12:17 
GeneralGreat but.... Pin
Filippo Macchi17-Jan-07 3:46
Filippo Macchi17-Jan-07 3:46 
Generalasp.net account access denied Pin
tdalsimer16-Jan-07 8:13
tdalsimer16-Jan-07 8:13 
GeneralRe: asp.net account access denied Pin
tdalsimer16-Jan-07 11:01
tdalsimer16-Jan-07 11:01 
Questionquick question Pin
rahul_l1015-Jan-07 16:04
rahul_l1015-Jan-07 16:04 
AnswerQuick answer Pin
jermanis15-Feb-07 6:26
jermanis15-Feb-07 6:26 
GeneralPerformance of this method Pin
aronitin3-Nov-06 22:41
aronitin3-Nov-06 22:41 
QuestionSSL, anyone? Pin
jay_dubal13-Oct-06 1:36
jay_dubal13-Oct-06 1:36 
AnswerRe: SSL, anyone? Pin
jay_dubal26-Jun-07 3:09
jay_dubal26-Jun-07 3:09 
GeneralRe: SSL, anyone? Pin
mahabir27-Oct-08 2:09
mahabir27-Oct-08 2:09 
QuestionWhy does my brower close upon download only with .zip file type? Pin
Jeff Cecchini24-Sep-06 11:43
Jeff Cecchini24-Sep-06 11:43 
GeneralHello Fellow Saffa Programmer Pin
GaryWoodfine 20-Sep-06 21:50
professionalGaryWoodfine 20-Sep-06 21:50 
Generalno window.document Pin
koolbones30-Aug-06 13:08
koolbones30-Aug-06 13:08 
GeneralClose the open browser window. Pin
Paddy Boyd13-Jul-06 3:09
Paddy Boyd13-Jul-06 3:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.