Skip navigation

Guest Authentication with HTTP Modules

Create a Reusable HTTP Module to Authorize Guest Accounts

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.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish