ASP.NET Under the Hood
LANGUAGES: VB.NET | C#
ASP.NET VERSIONS: 2.0
Guest Authentication with HTTP Modules
Create a Reusable HTTP Module to Authorize Guest Accounts
By Michele Leroux Bustamante
Welcome back ASP.NET architects and developers! This month we ll explore a practical implementation of the HTTP module. As always, samples are provided in C# and VB.NET (see end of article for download details). Please send your questions and requests to [email protected] let me unravel some mysteries for you!
Q. I have an ASP.NET site that restricts access using forms authentication and uses the SQL membership provider. My problem is that I want to assign a guest account to unauthenticated users so they can access the site with a limited set of features. I know how to use all the login controls to provide role-based access, but I m not sure how to approach authorizing a guest account. Can you provide an example?
A. HTTP modules are one of my favorite extensibility points for ASP.NET. At a glance, the steps for implementing a custom module are quite simple:
- Create a type that implements the IHttpModule interface.
- Subscribe to one or more application events.
- Write code to respond to those events, based on the desired behavior.
- Enable the module in configuration and the runtime takes care of the rest.
The real challenge when implementing a custom HTTP module is in understanding the application events that you can subscribe to, and understanding how you can interact with the ASP.NET runtime during a request to implement the behavior you seek.
In this article, I ll address how you can implement a custom HTTP module to add custom authentication behavior to allow guest access to an application. But first, let s quickly review the fundamentals of the ASP.NET runtime, and where modules fit within the context of a round trip.
HTTP Modules and the ASP.NET Runtime
There are plenty of resources available that describe the ASP.NET runtime and its core objects: HTTP modules, HTTP handlers and handler factories, the application object, and the request context. My intention here is to present a brief overview to provide context to the article, but I ll cite some references at the end of this article for those new to these concepts.
When IIS receives a request that matches one of the configured ASP.NET extensions (i.e., *.aspx, *.asmx, *.axd), it passes the request to the ASP.NET worker process. The worker process runtime (HttpRuntime) allocates a pooled instance of the application object (HttpApplication) to handle the request and creates an HttpContext instance that is the bucket of all important information related to the request, including the request and response streams, the session, the authenticated user, and more. This HttpApplication instance is responsible for finding the correct handler type (IHttpHandler) to process the actual request, but along the way it also loads any modules configured for the application so they can receive application events and interact with the runtime. As illustrated by Figure 1, the HttpApplication instance, modules, and handlers are loaded into the application domain processing the request, and events are fired from the HttpApplication object to any modules that subscribe to each event. If multiple modules subscribe to the same event, they receive the event in the order they are configured in the module stack (see Figure 2).
Figure 1: HTTP modules receive
events from the HttpApplication instance, and can interact with that instance
and other elements available to the HttpContext.
The ASP.NET runtime relies on many predefined modules to handle caching, session state, authentication, authorization, profile, errors, and more. Figure 2 illustrates the order that these default modules are configured when you first install ASP.NET, and where the custom GuestAuthenticationModule from this article will be configured.
Figure 2: Many predefined modules
are loaded into the application domain by default. Only one Authentication
module is loaded, and other grayed items represent optionally configured
modules.
Each module in the stack may subscribe to different application events, based on the functionality they provide and the timing when they must interact with the runtime to produce that functionality.
Timing Is Everything
When creating a custom module, it helps to know what the application events are, and the order in which they are raised during the lifecycle of a request. Figure 3 illustrates a default set of events raised by the HttpApplication instance before and after the designated page handler is executed. You can hook events as early as BeginRequest; immediately prior to the page handler with PreRequestHandlerExecute; immediately after the page handler with PostRequestHandlerExecute; and immediately prior to sending content to the response output stream with PreSendRequestHeaders and PreSendRequestContent. This should tell you that you can intercept events prior to processing the request via its handler, thereby circumventing how the request is processed, or even if the request should be processed. You can also interact with the response stream after the handler has completed its work. The handler is the primary party responsible for processing the incoming request stream and populating the outgoing response stream. For Web form requests (*.aspx) the Page object is the IHttpHandler instance responsible for this activity.
Figure 3: The
GuestAuthenticationModule will intercept AuthenticateRequest to authenticate
callers with a guest account if they are not already authenticated.
Hooking the Right Application Events
It s not about how many application events your module subscribes to, but rather what the module does with those events it intercepts. Let s review the purpose for this GuestAuthenticationModule: If users are not authenticated, log them in under a guest account. This implies letting the runtime try to authenticate the user based on credentials provided, and if the user remains unauthenticated, provide guest credentials to access the site. This makes it possible to provide some minimal set of features to all site visitors, even if they do not have credentials to provide.
There are several events related to authentication and authorization:
- AuthenticateRequest. Raised by the runtime when it is time to authenticate the caller.
- PostAuthenticateRequest. Raised by the runtime after all subscribing modules have had a chance to authenticate.
- AuthorizeRequest. Raised by the runtime when it is time to authorize the caller.
- PostAuthorizeRequest. Raised by the runtime after all subscribing modules have had a chance to authorize the caller.
To achieve the desired result for this GuestAuthenticationModule, we need to authenticate the caller before the runtime decides to redirect calls to the login page, and before the RoleManagerModule gathers role information to attach a RolePrincipal to the HttpContext.User property. If we hook the AuthenticateRequest event, our module will be invoked after the default FormsAuthenticationModule is given an opportunity to authenticate the caller. That s exactly where we want to be.
Implementing GuestAuthenticationModule
The implementation of the GuestAuthenticationModule will hook the AuthenticateRequest event in the Init method of the module, as shown in Figure 4.
Public Class GuestAuthenticationModule
Implements IHttpModule
Public Sub New()
End Sub
Public Sub Dispose() Implements IHttpModule.Dispose
End Sub
Public ReadOnly Property ModuleName() As String
Get
Return "GuestAuthenticationModule"
End Get
End Property
Public Sub Init(ByVal application As HttpApplication) Implements IHttpModule.Init
AddHandler application.AuthenticateRequest, AddressOf Me.Application_AuthenticateRequest
End Sub
Protected Sub Application_AuthenticateRequest(ByVal sender As Object, ByVal e As EventArgs)
If Not System.Web.HttpContext.Current.Request.IsAuthenticated Then
Dim authSection As AuthenticationSection =
HttpContext.Current.GetSection("system.web/authentication")
LogonFormsGuest()
End Sub
...other methods
End Class
Figure 4: GuestAuthenticationModule will hook the AuthenticateRequest event in the Init method of the module.
The guest module AuthenticateRequest handler will check to see if the user is authenticated, and if not, proceed to authenticate a guest account. LogonFormsGuest is the function where it all happens (see Figure 5).
Private Sub LogonFormsGuest()
Dim userName As String =
ConfigurationManager.AppSettings("guestUser")
Dim password As String =
ConfigurationManager.AppSettings("guestPassword")
Dim authenticated As Boolean =
Membership.ValidateUser(userName, password)
If authenticated Then
FormsAuthentication.SetAuthCookie(userName, False)
Dim user As IIdentity = New GenericIdentity(userName)
Dim userRoles() As String = Roles.GetRolesForUser(user)
Dim principal As GenericPrincipal = New
GenericPrincipal(user, userRoles)
HttpContext.Current.User = principal
End If
End Sub
Figure 5: Check to see if the user is authenticated; if not, proceed to authenticate a guest account.
This function performs several important actions. First, it retrieves the configurable guest account from application settings (which, by the way, should be encrypted). This account information is passed to the membership provider to validate the user, which in this case invokes the SqlMembershipProvider. It is important to use the membership provider here because it updates table entries related to the user s login activity, and increments ASP.NET performance counters related to authentication success or failure.
If the user was successfully authenticated against the credential store, the forms authentication ticket is set using the FormsAuthentication utility component. Had the user entered credentials in the login page, an authentication ticket would have been created in the same way. Lastly, this method creates a generic security principal for the guest user account, including its role assignments, and attaches this principal to the User property of the HttpContext instance. A security principal must always be attached to the context for authorization activities to be properly executed.
The DefaultAuthenticationModule (the last module in the stack) always maps the security principal attached to the context to the executing thread so they are aligned. This ensures that all role-based security checks are compared against the correct security principal:
Thread.CurrentPrincipal = HttpContext.Current.User
GuestAuthenticationModule for Windows Accounts
In fact, the code sample also provides functionality for guest authentication for Windows accounts. First, the Application_AuthenticateRequest handler checks if the authentication mode is Forms or Windows, to determine which type of guest account to authenticate (see Figure 6).
Protected Sub Application_AuthenticateRequest(
ByVal sender As Object, ByVal e As EventArgs)
If Not System.Web.HttpContext.Current.Request.IsAuthenticated
Then
Dim authSection As AuthenticationSection =
HttpContext.Current.GetSection("system.web/authentication")
If authSection.Mode = AuthenticationMode.Forms Then
LogonFormsGuest()
ElseIf authSection.Mode = AuthenticationMode.Windows Then
LogonWindowsGuest()
End If
End If
End Sub
Figure 6: Determine which type of guest account to authenticate.
Instead of invoking LogonFormsGuest as discussed earlier, the LogonWindowsGuest method is invoked when Windows authentication is configured (see Figure 7).
Private Sub LogonWindowsGuest()
Dim refToken As IntPtr = 0
Dim ret As Integer
Dim domain As String =
ConfigurationManager.AppSettings("domain")
Dim userName As String =
ConfigurationManager.AppSettings("guestUser")
Dim password As String =
ConfigurationManager.AppSettings("guestPassword")
ret = LogonUser(userName, domain, password,
LogonType.LOGON32_LOGON_NETWORK,
LogonProvider.LOGON32_PROVIDER_DEFAULT, refToken)
If ret <> 0 Then
Dim user As IIdentity = New WindowsIdentity(refToken)
Dim p As WindowsPrincipal = New WindowsPrincipal(user)
HttpContext.Current.User = p
Else
Dim err As Integer = Marshal.GetLastWin32Error()
' TODO: something with error message
End If
End Sub
Figure 7: The LogonWindowsGuest method is invoked when Windows authentication is configured.
This time the Win32 API function, LogonUser, authenticates the user and creates a valid Windows token. This token is wrapped in a WindowsPrincipal, as opposed to a GenericPrincipal as illustrated earlier.
What It All Means
Now that you have a module that will grant guest account access for unauthenticated users, you can limit what your guests can see on each page with role-based security. You can use the LoginView control to specify which page elements should be visible to guests, restrict pages by denying guests, and perform runtime role-based security checks that either allow or prevent guests from accessing functionality. The code sample accompanying this article illustrates these ASP.NET 2.0 features in conjunction with the GuestAuthenticationModule ... so have a look and enjoy!
Additional Resources
http://msdn.microsoft.com/msdnmag/issues/02/05/asp/
http://msdn.microsoft.com/library/en-us/dnpag2/html/PAGExplained0002.asp
http://msdn.microsoft.com/library/en-us/dnpag2/html/PAGExplained0001.asp
http://msdn.microsoft.com/library/en-us/dnpag2/html/PAGHT000025.asp
http://msdn.microsoft.com/msdnmag/issues/05/04/Security/
If you have questions or comments regarding this column, or any other ASP.NET topics, please drop me a line at [email protected]. Thanks for reading!
C# and VB.NET code examples are available for download.
Michele Leroux Bustamante is Chief Architect at IDesign Inc., Microsoft Regional Director for San Diego, Microsoft MVP for XML Web services, and a BEA Technical Director. At IDesign Michele provides training, mentoring, and high-end architecture consulting services, specializing in scalable and secure .NET architecture design, globalization, Web services, and interoperability with Java platforms. She is a board member for the International Association of Software Architects (IASA), a frequent conference presenter, conference chair of SD s Web Services track, and a frequently published author. She is currently writing a book for O Reilly on the Windows Communication Foundation. Reach her at http://www.idesign.net or http://www.dasblonde.net.