Tuesday, June 5, 2012

Directory Security On Webservice with Windows Authentication


Situation/Requirement: There is one folder on the root of a webservice application which contains some htmland pdf files. These files should be able to browse only for the authenticated users.

Normal Solution: The normal solution for this is to put a config file(Web.config) in that directory with these configurations:
<configuration>
 <system.web>
  <authorization>
   <deny users="?"/>
  </authorization>
 </system.web>
</configuration>
or placing these configuration on the normal Web.config file and adding the location tag which points to the appropriate directory.  For this to work, the authentication mode should be Windows or Forms. But this works only for the file types such as .aspx, .asmx, .ashx and not for the types such as htmlpdf, txt, png etc.
As mentioned in MSDN, the reason for this is:
By default, Internet Information Services (IIS) passes requests for only certain file types to ASP.NET to service. Files with file-name extensions such as .aspx, asmx, and .ashx are already mapped to the ASP.NET ISAPI extension (Aspnet_isapi.dll).
and the solution is:
To have IIS pass other file-name extensions to ASP.NET, you must register the extensions in IIS.
My Issue: My authentication mode was windows and this solution on MSDN was solved my problem to block the unauthorized users, but I was unable to provide the access for autherized users too.

My Solution: I have created a separate httphandler for the required file types, so that I can check for the proper session for authentication.
public class HttpHandler : IHttpHandler, IRequiresSessionState
{
    public HttpHandler(){}

    public void ProcessRequest(HttpContext context)
    {
        HttpRequest Request = context.Request;
        HttpResponse Response = context.Response;

        if(SessionHandler.Current.UserID == 0)
            Response.Redirect("~/Default.aspx");
        else
        {
            try
            {
                if (Request.Path.EndsWith(".pdf"))
                {
                    WebClient client = new WebClient();
                    Byte[] buffer = client.DownloadData(HttpContext.Current.Server.MapPath(Request.Path));
                    Response.ContentType = "application/pdf";
                    Response.AddHeader("content-length", buffer.Length.ToString());
                    Response.BinaryWrite(buffer);
                }
                else
                {
                    using (StreamReader reader = new StreamReader(HttpContext.Current.Server.MapPath(Request.Path)))
                    {
                        Response.Write(reader.ReadToEnd());
                    }
                }
            }
            catch
            {
                Response.Redirect("~/Default.aspx");
            }
        }
    }

    public bool IsReusable
    {
        // To enable pooling, return true here.
        // This keeps the handler in memory.
        get { return false; }
    }
}
Also, to make the session handling easier I have created a class called SessionHandler:
public class SessionHandler
{
    public SessionHandler()
    {
        UserID = 0;
    }

    // Gets the current session.
    public static SessionHandler Current
    {
        get
        {
            SessionHandler session =
              (SessionHandler)HttpContext.Current.Session["UserID"];
            if (session == null)
            {
                session = new SessionHandler();
                HttpContext.Current.Session["UserID"] = session;
            }
            return session;
        }
    }

    public int UserID { get; set; }

}
Finally, I added the required file extensions on the httpHandlers section of the Web.config file.
<add verb="*" path="*.html" type="HttpHandler"/>
<add verb="*" path="*.txt" type="HttpHandler"/>
<add verb="*" path="*.pdf" type="HttpHandler"/>
Now on the Login/Authentication service, I can set the session like this:
SessionHandler.Current.UserID = objResult.Id;
and on the log-out service, I can just abandon the session.

Conclusion: On any of the case we need to map the proper file extensions on IIS, otherwise IIS will not pass the page request to ASP.NET.

No comments:

Post a Comment