HttpHandler to Authorize File Downloads - C# Code Sample

Its quite a common scenario to have a feature in a web site to check for the credentials of the user before allowing a particular download. For example, there might be some documents which should be visible only to a set of users and not to any others. This document might be anything - a PDF file, a zip file, an exe, a .doc,etc.

The situation gets even more complicated when you have certain documents that can be downloaded by everyone on the Internet and have some documents visible only to logged in users. A HttpHandler fits this scenario perfectly.

Before I get into developing the HttpHandler, a couple of points regarding the design is a must:

  • In case you need to distinguish between allowing the users to download publicly visible files and those that need log-in, its a good idea to have the documents in different folders so that security can be enforced more strictly.
  • For documents that need user's authentication and / or authorization, its advisable to have the document store outside the Web's root to make sure that the folder is not visible to any automated download app.
  • In cases wherein there is a 1-1 map between the users and his / her documents, a unique way to determine the user's file path needs to be put it to tighten security. This can be in the form of a HashedId for the user which maps onto the path in the store or some other URL rewriting technique.

To get on with it, there are three major steps that need to be performed to make sure that the file request is routed through the IIS via the ASPNET pipeline and onto your handler.

  1. Develop your handler - of course this is obvious :-)
  2. Configure your web site to use this handler for the filetype that you want authorization on and the verbs that you'll need to support (GET , PUT, POST, etc)
  3. Configure the IIS to make sure that the request for the particular filetype is routed through the ASPNET ISAPI dll so that your custom handler is it.

Lets take it step by step...and to make it more fun, in the reverse order!

Step 3: Configure IIS

This is quite a straightforward step. All you need to do is open up the properties of the Web Application under consideration.


  1. Under the Virtual Directory tab,
  2. click on the Configuration... button,
  3. in the new window that opens up, under the Mappings tab,
  4. click Add... ,
  5. click Browse and select C:\<windows root> \Microsoft.NET\Framework\<version>\aspnet_isapi.dll (the full path looks something like: C:\WINNT\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll).
  6. Provide the Extension without the dot (i.e just PDF)
  7. Under the Verbs section, you can leave it as is or for better security, provide only GET.
  8. Uncheck the Verify File Exists checkbox.
  9. Click OK, Apply and finally make sure that you have Execute Permissions to Scripts Only.

Step 2: Configure your Web Application

All you need to do is add the below line(s) into the web.config of the application you are developing:


 <system.web>
     < httpHandlers ><br>        <add verb="GET" path="*.pdf"              type=" 
Microsoft.Web.PortalFramework.HttpHandlers.PdfDownloadAuthorizationHandler,<br>             Microsoft.Web.PortalFramework"   /><br>    </httpHandlers > 
</system.web>

The above states that the HTTP verb GET with all files ending in extenion pdf should be routed to the class mentioned in the type. The type if of format "<Namespace.Classname>, <AssemblyName>"


Step 1: Develop your Handler

All you need is for your class to implement IHttpHandler interface in the System.Web namespace and that's its! Instead of saying some blah blah blah about the code, here's the full code:


 using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Web;


namespace Microsoft.Web.PortalFramework.HttpHandlers
{
    /// <summary>
    /// 
    /// </summary>
    public class PdfDownloadAuthorizationHandler: IHttpHandler
    {
        private static string userDocsBaseFolder;
        /// <summary>
        /// Initializes a new instance of the <see cref="DownloadAuthorizationModule"/> class.
        /// </summary>
        public PdfDownloadAuthorizationHandler()
        {
            userDocsBaseFolder = "PersonalDocs";
        }
       

        #region IHttpHandler Members

        /// <summary>
        /// Gets a value indicating whether another request can 
        /// use the <see cref="T:System.Web.IHttpHandler"></see> instance.
        /// </summary>
        /// <value></value>
        /// <returns>true if the <see cref="T:System.Web.IHttpHandler"></see>
        /// instance is reusable; otherwise, false.</returns>
        public bool IsReusable
        {
            get { return false; }
        }

        /// <summary>
        /// Enables processing of HTTP Web requests by a custom HttpHandler
        /// that implements the <see cref="T:System.Web.IHttpHandler"></see> interface.
        /// </summary>
        /// <param name="context">An <see cref="T:System.Web.HttpContext"></see>
        /// object that provides references to the intrinsic server objects 
        /// (for example, Request, Response, Session, and Server) 
        /// used to service HTTP requests.</param>
        public void ProcessRequest(HttpContext context)
        {
            try
            {
                //get the url requested by the user
                string urlRequested = 
                    HttpUtility.UrlDecode(context.Request.Url.AbsolutePath.ToUpper());
                //set the initial flag to false
                bool userPersonalDoc = false;
                //remove all spaces in the url before checking if the url contains the private path
                if (urlRequested.Replace(" ", "").Contains(userDocsBaseFolder))
                {
                    //once we know that its for the personal folders, set the flag
                    userPersonalDoc = true;
                    //we now assume that the cookie is set with the document profile id of the user
                    //and the dynamic link that its generated includes this key
                    //check only for the negative conditions
                    HttpCookie profCookie = context.Request.Cookies["DocId"];
                    if (profCookie != null)
                    {
                        //we need to use some sort of encryption (refer my previous post!)
                        TripleDESCryptoHelper helper = new TripleDESCryptoHelper();
                        string profileId = helper.GetDecryptedValue(profCookie.Value);
                        Guid profileGuid = new Guid(profileId);
                        //check if the docid from the url and the one in the cookie match
                        if (!urlRequested.Contains(profileGuid.ToString("B").ToUpper()))
                        {
                            //no match - spoofed request, send 'em back
                            context.Response.Redirect(context.Request.ApplicationPath,true);
                            
                        }
                    }
                    else
                    {                        
                        context.Response.Redirect(context.Request.ApplicationPath);
                    }
                }
                //if we have reached till here, we are good to go
                this.TransmitRequestedFile(context, urlRequested, userPersonalDoc);
            }
            catch (Exception generalException)
            {
                ExceptionManager.LogException(generalException);
            }


        }


        /// <summary>
        /// Transmits the requested file.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="urlRequested">The URL requested.</param>
        /// <param name="forceDownload">if set to <c>true</c> [force download].</param>
        private void TransmitRequestedFile(HttpContext context,
            string urlRequested, bool forceDownload)
        {
            // as a security considerataion, force the user to download the document
            // if the doc is from the personal folder
            if (forceDownload)
            {
                this.StartForcedDownload(context, urlRequested);
            }
            else {
                //else let the IE handle the filetype
                this.TransmitNormal(context, urlRequested);
            }
        }

        /// <summary>
        /// Transmits the file in 'normal' way.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="urlRequested">The URL requested.</param>
        private void TransmitNormal(HttpContext context, string urlRequested)
        {
            string filePath = HttpContext.Current.Server.MapPath(urlRequested);

            string fileName = System.IO.Path.GetFileName(filePath);

            context.Response.ClearContent();

            context.Response.ClearHeaders();            

            FileInfo pdfInfo = new FileInfo(filePath);

            context.Response.AddHeader("Content-Length", pdfInfo.Length.ToString());

            //assuming that the file is pdf, needs to be changed appropriately
            //context.Response.ContentType = "application/zip";
            context.Response.ContentType = "application/pdf";

            context.Response.TransmitFile(filePath);

            context.Response.End();
        }

        /// <summary>
        /// Starts the forced download.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="urlRequested">The URL requested.</param>
        private void StartForcedDownload(HttpContext context, string urlRequested)
        {
            string filePath = HttpContext.Current.Server.MapPath(urlRequested);

            string fileName = System.IO.Path.GetFileName(filePath);

            context.Response.ClearContent();

            context.Response.ClearHeaders();

            context.Response.AddHeader("Content-Disposition", "inline; filename=" + fileName);

            context.Response.ContentType = "application/pdf";

            context.Response.BufferOutput = false;

            context.Response.TransmitFile(filePath);
                        
            context.Response.End();

        }

        #endregion
    }
}