Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Secure File Download Using Basic Authentication

0.00/5 (No votes)
19 Mar 2006 1  
Secure file download using Basic Authentication. The interesting part is that we maintain two separate entry points for uploading and downloading a file.

Sample Image - Secure_File_Download.jpg

Introduction

Security has always been a top issue for all kinds of applications, especially Web applications. Web apps are accessible to almost the entire universe, and are open to attack. Most of the web applications provide the file download feature, the real time challenge is not in providing such a feature, but in securing such operations. Recently, I dealt with an application which demands for secure file upload and download, during which I did wide research, so I thought of sharing it with the world to make it worth.

You may find several articles on the internet talking about secure downloads, but this article is a bit different from all those, because it distinguishes between upload and download by providing two different entry points for them.

The following article depicts how secured file download can be achieved using IIS basic authentication.

Background

Before we start, let's refresh our memory and learn some basics.

An ASP.NET application has two separate authentication layers. That is because ASP.NET is not a standalone product. Rather, it is a layer on top of IIS. All requests flow through IIS before they are handed to ASP.NET. As a result, IIS can decide to deny access without the ASP.NET process even knowing that someone requested a particular page. Here is an overview of the steps in the joint IIS and ASP.NET authentication process:

  1. IIS first checks to make sure the incoming request comes from an IP address that is allowed access to the domain. If not, it denies the request.
  2. Next, IIS performs its own user authentication if it is configured to do so. By default, IIS allows anonymous access, so requests are automatically authenticated, but you can change this default on a per � application basis within IIS.
  3. If the request is passed to ASP.NET with an authenticated user, ASP.NET checks to see whether impersonation is enabled. If impersonation is enabled, ASP.NET acts as though it were the authenticated user. If not, ASP.NET acts with its own configured account.
  4. Finally, the identity from step 3 is used to request resources from the operating system. If ASP.NET authentication can obtain all the necessary resources, it grants the user's request, otherwise it is denied. Resources can include much more than just the ASP.NET page itself. You can also use .NET�s code access security features to extend this authorization step to disk files, Registry keys, and other resources.

How it works

In general, any web application contains a single virtual directory and a single Uploads folder, to/from which end users upload/download files. Here, I'm playing a little trick: though upload and download happens to/from the same folder, I'm keeping two different entry points for them. I.e., I create two different virtual directories which point to the same physical folder.

  • Create a virtual directory by name Security (your web application) in which the ASP.NET application resides.
  • Inside this folder, create a folder by name Uploads (your application subfolder, to which you upload the files). The ASP.NET application will upload files to this folder using general file upload practices.
  • Create one more virtual directory by name Downloads, and map it to the Uploads (your application subfolder, to which you upload the files) folder which is residing in the Security (your web application) folder. This virtual directory is simply meant for file downloads, and it has nothing to do with the ASP.NET application or the file upload process.

Keeping the uploads folder as it is, we are creating two entry points for the files, one is through http://localhost/security/uploads, and the other through http://localhost/downloads. Though both the virtual directories are pointing to the same folder, based on their settings, they will behave differently.

Configuration

  • Unzip the attached source code on to disk.
  • Start IIS.
  • Create a new virtual directory by name Security, and map it to the source code.
  • In IIS, select the Security virtual directory (created above). You will find an Uploads folder inside it. Right click on the Uploads folder and select Properties. Remove the Read access from the Uploads folder, and provide only Write access to it.

Sample screenshot

  • Create a new virtual directory named Downloads, and map the Uploads folder (present in the Security folder) to it.
  • Open the downloads virtual directory property window, and grant only Read permissions to it, as shown below:

Sample screenshot

Using the code

Modify the following parameters in the web.config files as per your application needs:

//Application uploads folder virtual path
<add key="UploadPath" value="/Security/Uploads" />

//Entry point for downloads folder, a virtual path
<add key="DownloadURL" value="http://localhost/downloads" />

//Windows user name
<add key="BasicAuthenticationUser" value="administrator" />

//Windows user password
<add key="BasicAuthenticationPWD" value="admin$123" />

Now, let's start learning...................

What is Basic Authentication - when an unauthenticated request comes into the web server, the web server returns an HTTP 401 response, prompting the client for its credentials. The client re-requests the same resource, passing the username and password in a base-64 encoded HTTP header. (The base-64 encoding does not encrypt or protect the credentials; it merely ensures that the characters sent over the wire are in a format that won't conflict with any reserved characters.) Since the credentials are sent over the wire in plain-text, Basic Authentication should only be used when using SSL, since this ensures that the entire contents of the HTTP request are encrypted. However, in our case, we are passing the credentials to the same server through localhost, so passing credentials as clear text would not be a problem.

The .NET Framework provides the WebClient class, which is designed to simplify the HTTP request process. It contains common methods for sending data to and receiving data from a resource identified by a URI. The HttpWebRequest class is used to generate the request, and HttpWebResponse is used to retrieve the response from the server. Typically, HttpWebRequest and HttpWebResponse serve all the purposes, but in the case of Basic Authentication, an extra class comes into picture, i.e., CredentialCache.

Both the WebClient and HttpWebRequest classes make it easy to include authentication information in the request through their Credentials properties. The Credentials property accepts an object that implements ICredentials. The CredentialCache class provides a store for credentials. The intent of the CredentialCache class is to store a set of credentials for the user. When a request is made to a resource, the CredentialCache class can be interrogated and the appropriate credentials can be extracted based on the resource being requested.

The simplest use of this class involves just a few lines of code. The steps we need to perform include:

  1. Creating an instance of the class.
  2. Calling the DownloadData method, passing in the URL (which returns an array of Bytes).
  3. Writing the downloaded data's Byte using Response.BinaryWrite, which in turn prompts the user to download the file.

The following code shows how to use the CredentialCache class and the WebClient's Credentials property to make a request to a URL that is protected via Basic Authentication:

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Net;
using System.IO;
namespace security
{
    /// <SUMMARY>

    /// Summary description for SecureFile.

    /// </SUMMARY>

    public class SecureFile
    {
        public SecureFile()
        {
            //

            // TODO: Add constructor logic here

            //

        }
        public bool UploadFile(HtmlInputFile inputfile)
        {
            try
            {
                string fileName = "";
                string DirPath = "";
            
                if(( inputfile.PostedFile != null ) && 
                   ( inputfile.PostedFile.ContentLength > 0 ))
                {
                    DirPath=HttpContext.Current.Server.MapPath(
                      System.Configuration.ConfigurationSettings.AppSettings[
                      "UploadPath"]);
                    fileName = System.IO.Path.GetFileName(
                                     inputfile.PostedFile.FileName );
                    inputfile.PostedFile.SaveAs( DirPath + "\\" + fileName  );
                }
                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool DownloadFile(string strFile)
        {
            try
            {
                string strDownloadURL=
                  System.Configuration.ConfigurationSettings.AppSettings[
                  "DownloadURL"];
                string strUser=
                  System.Configuration.ConfigurationSettings.AppSettings[
                  "BasicAuthenticationUser"];
                string strPWD=
                  System.Configuration.ConfigurationSettings.AppSettings[
                  "BasicAuthenticationPWD"];
                string strURL=strDownloadURL + "\\" + strFile;
                 //Creating an instance of a WebClient
                WebClient req=new WebClient();

                //Creating an instance of a credential cache, 
                //and passing the username and password to it
                CredentialCache mycache=new CredentialCache();
                mycache.Add(new Uri(strURL),"Basic", 
                            new NetworkCredential(strUser,strPWD));
                req.Credentials=mycache;

                //Creating an instance of a Response object
                HttpResponse response = HttpContext.Current.Response;
                response.Clear();
                response.ClearContent();
                response.ClearHeaders();
                response.Buffer= true;

                //Keep the current page as it is, and writes 
                //the content to an new instance, 
                //which prompts the user to download the file 
                response.AddHeader("Content-Disposition", 
                  "attachment;filename=\"" + strFile + "\"");
                byte[] data=req.DownloadData(strURL);
                response.BinaryWrite(data);
                response.End();
                return true;
            }
            catch(Exception ex)
            {
                if(ex.Message=="The remote server " + 
                               "returned an error: (404) Not Found.")
                    throw new Exception("File not found");
                else if(ex.Message=="The remote server" + 
                        " returned an error: (401) Unauthorized.")
                    throw new Exception("Unauthorized access");

                return false;
            }
        }
    }
}

Enjoy!!! Any feedback would be appreciated.

Using the code

For quick implementation and demonstration purposes, I have used the administrator user, but I strongly recommend you to create a new user which has only Write permissions to the Uploads folder and no other permissions on the server/system.

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