Secure Persistent ASP.NET Forms Authentication

While the ASP.NET Forms Authentication system is a great system for authentication, it has one significant shortcoming for a lot of sites. You can either only restrict it to always pass the authentication cookies in a secure manner or always pass them even if the connection is not secure. There is no intermediate method of authentication available to you. This means that if you are operating a web store you have a problem.

Normally, a web store wants the customer identified as soon as they come to the site, and throughout the shopping experience. However, when the user goes to edit their account or checkout, you want to switch them to a secure mode. In order to be secure, the cookie used to authenticate them for checkout must be restricted to SSL connections. This means that to maintain their login, you would have to remain in SSL from the moment they sign in forward, which adds a lot of unnecessary server load. Plus, it can cause headaches with external content you might want to include on your page that isn’t encrypted.

The solution is to modify the forms authentication system to use a pair of cookies. One is valid only to identify you but not access secure functions, doesn’t require SSL to be transmitted, and is persistent across sessions. The other is a full authentication and requires SSL to be transmitted.

The basic method this system uses is adding two additional HttpModules to your web.config file, PartialAuthenticationModule and PartialAuthorizationModule.

The PartialAuthenticationModule works by adding to the existing FormsAuthenticationModule. The FormsAuthenticationModule is used as normal to process authentication for the secure authentication cookie (requireSSL should be set to true for the forms module in web.config). The PartialAuthenticationModule kicks in after the FormsAuthenticationModule. If there is no user on the current request (i.e. not a secure request, so client didn’t transmit the cookie) then the user is added to the context from the secondary insecure cookie instead. The user that is added will have the “Partial” AuthenticationType instead of “Forms”. In case the login has changed, it will also clear the old secondary cookie if the username doesn’t match with the secure cookie.

The PartialAuthorizationModule processes later in the request life cycle. It uses the section of the section of your web.config files to identify security requirements on each folder. You just add a web.config file to each subfolder as required. This section supports two key fields. requiresLogin is either true or false, and specifies if the folder requires a secure login using the primary FormsAuthentication cookie. It verifies this by checking the AuthenticationType on the logged in user. The second field is requireSSL, which can be None, Optional, or Required. This will automatically redirect the request to or from https based on how you want the folder to be handled. This allows you to use normal app-relative URLs throughout your application without worrying about switching to and from https. Normally, for most folders you would use requiresLogin=”false” and requireSSL=”None”, and for secure folders you would use requiresLogin=”true” and requireSSL=”Required”.

I’ve also implemented a PartialAuthentication static class that is very similar to the FormsAuthentication static class. It actually uses a lot of the methods from the FormsAuthentication class to help it with a lot of the processing. In particular, you should switch any login or logoff code to use the methods in the PartialAuthentication class instead of FormsAuthentication. This will create or remove both of the necessary cookies. To sign off a user from the secure section but still leave the persistent insecure cookie, use the FormsAuthentication.SignOff method instead.

As far as security is concerned, the partial authentication cookie is generated using a FormsAuthenticationTicket just like with FormsAuthentication. It only difference is that the name of the cookie is prepended to the username in the ticket, and on decryption that string is validated. This prevents a client from editing their cookie file to set the secure cookie to have the same value as the insecure cookie. The cookies are encrypted using the same machine authentication keys as the forms authentication tickets, so they should work on web farms in the same manner.

Please note that this library is designed for .NET 3.5 and Visual Studio 2008, though it should be easily
convertible back to .NET 2.0 if you change the project settings.

UPDATE: Please see updated version here

Odd WSHttpBinding Scenario

I was having lots of trouble recently with a WCF WSHttpBinding scenario on a server. It had been working, and the only thing I had changed recently was to the base address of some of the bindings. On top of that, everything was working in my test environment. When I did trace logging on the server, I wasn’t seeing any messages at all other than stating that listening had been started on the correct base addresses. On the client side, I was receiving the following message during the negotiation for the secure session (I’m using message encryption via certificates):

An error occurred while receiving the HTTP response to http://xx.xx.xx.xx:yy/AdvWebClient/CapacityManager. This could be due to the service endpoint binding not using the HTTP protocol. This could also be due to an HTTP request context being aborted by the server (possibly due to the service shutting down). See server logs for more details.


I replaced the ip and port numbers for security purposes.

I worked on this for almost a day, restarting the windows service, changing configuration, staring at trace logs, etc. Finally, as a last ditch effort, I rebooted the server and everything started working. Apparently, there was some kind of hang up in the http.sys that was preventing it from processing the traffic. This is despite the listeners opening and closing (I watched using netstat) when I started and stopped the service.

Hopefully this helps anyone else who encounters this problem. If anyone knows of a way to reset the http.sys system without rebooting the entire computer, please let me know. Thanks.

Fix To GroupedUpdatePanel

After further testing, I discovered a bug in my GroupedUpdatePanel. It didn’t always function correctly when the postback was initiated by a child control with ChildrenAsTriggers set to true. Any UpdatePanels declared after the initiated panel would update, but those before would not. I’ve corrected this, though it’s now a bit more complicated. There is an processing module which needs to be added to your web.config file in the section. Here’s the link to the corrected file.

GroupedUpdatePanel.zip

Grouping Update Panels

Without a doubt, AJAX is one of the most useful web development tools out there. Through AJAX, we finally have a method for developing web pages with robust user interfaces.

The most common and simplest method of AJAX development on ASP.NET is to use the UpdatePanel. While the UpdatePanel creates a lot of overhead, it is very simple to implement. However, sometimes you will have multiple UpdatePanels on a page that relate to each other. In order to reduce your overhead, you will probably want to set thier UpdateMode to Conditional. With the Conditional UpdateMode enabled, they will only update under three conditions (note that nested UpdatePanels are a bit more complicated):

  1. A child control of the UpdatePanel performs a postback, and ChildrenAsTriggers is true
  2. The Update method is explicitly called.
  3. A manually defined trigger is fired.

However, this doesn’t help with firing the other UpdatePanels on the page that are related. While you can update the other panels manually using the Update method or triggers, that can potentially be a lot of work and difficult to maintain.

In order to address this, I’ve created a simple GroupedUpdatePanel. It has a GroupName property which is used to group multiple GroupedUpdatePanels together. If any one UpdatePanel in a group is updated as part of an asynchronous postback, they will all be updated.

There are two important restrictions to note. First, you must set UpdateMode to Conditional. If any one GroupedUpdatePanel in a group is set to Always, they will all update every time. This is a result of the limitations of inheriting from the UpdatePanel class. Second, all of the UpdatePanels must be created and on the page at the time the PreRender event occurs. I’ve seen some reports on the web of certain situations where this is not the case, though I haven’t tested for any of it.

You can download the source for the control here. Feel free to use it in your own applications.

UPDATE: Please see updated version here

Linking Stylesheets From Content Pages

Personally, I find stylesheets to be the best way to apply formating to web pages. Skins certainly have their uses, but they tend to pad out the HTML with lots of redundant information that can be avoided by referencing a stylesheet, thus reducing download times. Stylesheets can be easily added to themes, but sometimes you don’t want to reference all of your stylesheets on every page, which is what happens if you put stylesheets into a theme.

When using master and content pages, this can be a bit tricky. The common solution isn’t all that difficult, you just have to add a content section inside the header of your master page.

<% Master Language=”VB” AutoEventWireup=”false” CodeBehind=”SystemMaster.Master.vb” Inherits=”MyWebApp.SystemMaster” %>

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Strict//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd”>

<html xmlns=”http://www.w3.org/1999/xhtml>
<head runat=”server”>
<title&gt;Untitled Page</title>
<asp:ContentPlaceHolder ID=”HeadEntries” runat=”server”></asp:ContentPlaceHolder>
</head>
<body>
<asp:ContentPlaceHolder ID=”Body” runat=”server”></asp:ContentPlaceHolder>
</body>
</html>

Then on your content page:

<%@ Page Language=”vb” AutoEventWireup=”false” MasterPageFile=”~/SystemMaster.Master” CodeBehind=”Page.aspx.vb” Inherits=”MyWebApp.Page”
title=”Untitled Page” %>

<asp:Content ID=”HeadEntries” ContentPlaceHolderID=”HeadEntries” runat=”server”>
<link rel=”stylesheet” type=”text/css” href=”styles/styles.css” />
</asp:Content>

<asp:Content ID=”Body” ContentPlaceHolderID=”Body” runat=”server”>
Insert Body Here…
</asp:Content>

Now this works great for most scenarios, but what if you’re using URL rewriting in order to dynamically generate your pages based on the URL that was requested? In that case, the relative path to the stylesheet won’t work. If you know that your web application will always be the root application on the server, you can use a path that starts with /, but that isn’t always the case. What you really need to do is use an application relative path, starting with a ~/.

Normally you can change your link tag to include runat=”server” and it will process application relative paths in the href attribute. However, this doesn’t work on content pages for some reason. Therefore, I’ve created the class below to help.

Imports System
Imports System.ComponentModel
Imports System.Text
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls

<DefaultProperty(“href”), ToolboxData(“&lt;{0}:SmartLink runat=””server””&gt;&lt;/{0}:SmartLink&gt;”)> _
Public Class SmartLink
Inherits WebControl

<Bindable(True), Category(“Behavior”), DefaultValue(“”), Localizable(True)> _
Property href() As String
Get
Dim s As String = CStr(ViewState(“href”))
If s Is Nothing Then
Return String.Empty
Else
Return s
End If
End Get

Set(ByVal Value As String)
ViewState(“href”) = Value
End Set
End Property

<Bindable(True), Category(“Behavior”), DefaultValue(“”), Localizable(True)> _
Property rel() As String
Get
Dim s As String = CStr(ViewState(“rel”))
If s Is Nothing Then
Return String.Empty
Else
Return s
End If
End Get

Set(ByVal Value As String)
ViewState(“rel”) = Value
End Set
End Property

<Bindable(True), Category(“Behavior”), DefaultValue(“”), Localizable(True)> _
Property type() As String
Get
Dim s As String = CStr(ViewState(“type”))
If s Is Nothing Then
Return String.Empty
Else
Return s
End If
End Get

Set(ByVal Value As String)
ViewState(“type”) = Value
End Set
End Property

Public Overrides Sub RenderBeginTag(ByVal writer As System.Web.UI.HtmlTextWriter)
If rel &lt;&gt; String.Empty Then
writer.AddAttribute(HtmlTextWriterAttribute.Rel, rel)
End If
If type &lt;&gt; String.Empty Then
writer.AddAttribute(HtmlTextWriterAttribute.Type, type)
End If
If href &lt;&gt; String.Empty Then
writer.AddAttribute(HtmlTextWriterAttribute.Href, ResolveClientUrl(href))
End If
writer.RenderBeginTag(HtmlTextWriterTag.Link)
End Sub

End Class


Now you replace the link tag on the content page with this:

<cc1:SmartLink runat=”server” rel=”stylesheet” type=”text/css” href=”~/styles/style.css” />

That’s it, now you have an application relative stylesheet link on your content page that works with URL rewriting.