Using Forms Based Authentication in ASP.NET for Static Content (Doc Files, PDF Files etc.)

Scenario:
You want to implement Forms based authentication for ASP.NET web application, and you have certain document/PDF files which need to be protected. It should be such that even if you try to browse that .doc file directly, it should take you to the Login Prompt OR it should simply deny access to the file. In a nutshell, only the folks who have provided the UserName/Password should be able to access the static file.

There are two different workarounds using which I typically do it, based on the requirement. But before we get to the solution we will need to create a small project using which we could do the Forms Based authentication. Lets build that application first and then, we will go to the solutions...

Steps to create a Project with Forms Based authentication.

1. Create a new Project using your VS 2003 IDE and add an aspx page called Login.aspx
2. Switch to the HTML view of the Login.aspx and paste the following inside the <Form> tag

<h3>
   <font face="Verdana">Logon Page</font>
</h3>
<table>
<tr>
  <td>UserName:</td>
  <td><input id="txtUserName" type="text" runat="server" NAME="txtUserName"></td>
  <td><ASP:RequiredFieldValidator ControlToValidate="txtUserName" Display="Static" ErrorMessage="*" runat="server"
ID="vUserName" /></td>
</tr>
<tr>
  <td>Password:</td>
  <td><input id="txtUserPass" type="password" runat="server" NAME="txtUserPass"></td>
  <td><ASP:RequiredFieldValidator ControlToValidate="txtUserPass" Display="Static" ErrorMessage="*" runat="server"
ID="vUserPass" />
</td>
</tr>
</table>
<input type="submit" Value="Logon" runat="server" ID="cmdLogin" NAME="cmdLogin"><p></p>
<asp:Label id="lblMsg" ForeColor="red" Font-Name="Verdana" Font-Size="10" runat="server" />

3. In the web.config change the authentication mode to look like <authentication mode="Forms" />
4. Change the authorization section to look like...

<authorization>
   <deny users="?" />
   <allow users="*" />
</authorization>

5. In IIS, right click on the Virtual Folder and click Properties. Go to the Directory Security tab and click on Edit. You should have Anonymous Access checked there.
6. Go to the code behind page for Login.aspx and paste the following... (on the first line you should add Imports System.Web.Security else your application won't compile due to FormsAuthentication class)

Private Sub cmdLogin_ServerClick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdLogin.ServerClick
  'You should provide your own Authentication here. Refer https://support.microsoft.com/kb/301240 if in doubt
  'I am just hardcoding the values. User name is Rahul and Password is Rahul as well
  If txtUserName.Value = "Rahul" And txtUserPass.Value = "Rahul" Then
      FormsAuthentication.RedirectFromLoginPage(txtUserName.Value, True)
  Else
     Response.Redirect("Login.aspx", True)
  End If
End Sub

7. Create another page called Default.aspx and switch to the HTML view. Once again, add the following inside the Forms Tag...

This is the default page!<BR>
<A href="SecuredFolder/AnotherWordFile.doc" mce_href="SecuredFolder/AnotherWordFile.doc">https://ServerName/FormsAuth/SecuredFolder/AnotherWordFile.doc</A><BR>
<A href="SecuredFolder/MyWordFile.doc" mce_href="SecuredFolder/MyWordFile.doc">https://ServerName/FormsAuth/SecuredFolder/MyWordFile.doc</A><BR>
<asp:Button id="Button1" style="Z-INDEX: 103; LEFT: 17px; POSITION: absolute; TOP: 179px" runat="server" Text="Logout"></asp:Button>

8. Switch back to the Code-behind for Default.aspx and paste the following...

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
   FormsAuthentication.SignOut()
   Response.Redirect("Login.aspx", True)
End Sub

9. Right click on Default.aspx and select "Set as Start page" in your Solution Explorer window.
10. Add a new folder in your Virtual Folder called "SecuredFolder" and create a couple of  word files (with some text for your reference) called MyWordFile.doc and AnotherWordFile.doc.
11. Press CTRL + F5 to run the project, you would expect the "Default.aspx" page to show up, but instead you will see Login.aspx since the Forms Based authentication has kicked in!! Nice, isn't it :o) ?
12. So far so good. In username and Password, type in Rahul (I have hardcoded the values) and click on Logon button. You should be re-directed towards the default.aspx.

Now, the actual problem starts! You would expect the same thing when you try to browse https://ServerName/FormsAuth/SecuredFolder/MyWordFile.doc file directly from your Internet Explorer Address Bar. Right? Unfortunately, this won't work. Even if you put a web.config with Location tag in the same folder this won't work for Static files. Although, if you put any other ASPX file there, that specific WebForm will work!

There are a couple of workarouds for this...

Solution 1:

1. Right click on the Virtual Folder in IIS and go to the Home Directory tab.
2. Click on Configuration and then click on Add button.
3. In the Extension text box, type in .DOC
4. In the executable text box, browse to C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\aspnet_isapi.dll and click Ok thrice.

Now, if you try to browse https://ServerName/FormsAuth/SecuredFolder/MyWordFile.doc you will be redirected to the Login.aspx Page and once you have entered the credentials you will be served MyWordFile.doc file.

BUT, there is a problem to this approach. Open the MyWordFile.doc and modify it. You will notice that this time if you browse it, you will be served a local copy (cached) of the same file. Now, this is a big problem if you are expecting those Word files to change. To get rid of this problem, you will need to add a header for that specific folder SecuredFolder which will tell the browser to NOT cache the file in its Temporary Internet Files.

1) Open IIS console
2) Navigate to SecuredFolder (folder where you have those DOC files). Right click on that Folder and click Properties
3) Click on HTTP Headers tab
4) In the Custom HTTP Headers frame, click on Add button
5) In the Custom Header Name, type "Cache-Control" without quotes
6) In the Custom Header Value, type "no-cache" without quotes
7) Click on Ok twice
8) Click on "Start -> Run" and type iisreset and click Ok

Once you have added it, the problem should be fixed and you should be good to go. Basically we are telling IIS that whenever there is any file with an extension .DOC, let it be handled by aspnet_isapi.dll which will take care of authentication if the file is accessed directly. If you don't map the DOC file to the dll, ASP.NET never gets a chance to kick in, IIS gets the request and it just serves it and hence the issue!

There is another workaround to this problem. Let's have a look into it.

Solution 2:

1. Create a new Project and Follow Step 1-6 from Steps to create a Project with Forms Based authentication.
2. In the Step 7, you will need to paste the following...

<table border="0">
   <tr>
      <td>
         https://ServerName/FormsAuth/SecuredFolder/AnotherWordFile.doc
      </td>
      <td>
         <asp:Button id="SendFile1" runat="server" Text="Send File"></asp:Button>
      </td>
   </tr>
   <tr>
      <td>
         https://ServerName/FormsAuth/SecuredFolder/MyWordFile.doc
      </td>
      <td>
         <asp:Button id="SendFile2" runat="server" Text="Send File"></asp:Button>
      </td>
   </tr>
</table>

3. Switch back to the Code-behind for Default.aspx and paste the following...

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
   FormsAuthentication.SignOut()
   Response.Redirect("Login.aspx", True)
End Sub
Private Sub SendFile1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles SendFile1.Click
   Response.ContentType = "application/msword"
   Response.Clear()
   Response.TransmitFile("SecuredFolder\AnotherWordFile.doc")
   Response.End()
End Sub
Private Sub SendFile2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles SendFile2.Click
   Response.ContentType = "application/msword"
   Response.Clear()
   Response.TransmitFile("SecuredFolder\MyWordFile.doc")
   Response.End()
End Sub

4. As you can see, there is no hyperlink to the document. Instead we are using a button to transmit the file to the client.
5. In my application, it is simply using a crude logic. You may create the content dynamically and do it according to your application architecture.
6. The Files in the SecuredFolder is not protected yet!! To do it, you can simply Uncheck all the checkboxes under the Edit Security tab for the SecuredFolder VD from IIS.
7. All you have to ensure is that you have given Read permission to the ASPNET or NETWORK SERVICE or the account using which the Worker Process is spawned using Windows Explorer.

Here are a few articles I would like to suggest in case you want to take a deeper dive in Forms based authentication...

Troubleshooting Forms Based Authentication
How To: Use Forms Authentication with SQL Server in ASP.NET 1.1
How To: Use Forms Authentication with Active Directory in ASP.NET 1.1
How To: Use Forms Authentication with Active Directory in ASP.NET 2.0
Mixing Forms and Windows Security in ASP.NET

Hope this helps!
Rahul

Share this post : email it! | bookmark it! | digg it! | reddit! | kick it! | live it!