Example: Adding Users to Roles
Microsoft offers a couple of knowledge base articles on its Web site explaining how to add role-based security to your ASP.NET application. The Microsoft examples show how to add code to the global.asax file to make this happen. In retrospect, this technique surprises me, because I think that global.asax was ported from the old ASP days to make it familiar. It's advantageous to do the same thing in an HttpModule because you can add any number of event handlers to application events through any number of HttpModules.The point of our custom HttpModule is to enable the pages in our application to simply call User.IsInRole("somerole") to determine if a user is in a role. Our custom HttpModule also enables us to use a LoginView control in our pages to display content templates based on the roles of the user. You could of course specify users' roles in web.config, but isn't it a lot easier to get that data from a database?Listing 8.8 gets a logged in user's roles and assigns them to the user's Principal object. We've left the actual data access out of the example to keep it simple, so imagine that we have a class called DataFetcherClass with a static method called Getroles() that takes the user's Name as a parameter and returns an ArrayList of role names.
Listing 8.8. An HttpModule to determine user roles
C#[View full width]
VB.NET
using System;
using System.Collections;
using System.ComponentModel;
using System.Web;
using System.Web.SessionState;
using System.Web.Security;
using System.Configuration;
using System.Security.Principal;
public class UserHttpModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.AuthenticateRequest += new EventHandler(Application_AuthenticateRequest);
}
public void Dispose()
{
}
private void Application_AuthenticateRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
HttpContext context = application.Context;
if (context.Request.IsAuthenticated)
{
// check for a cached lookup first
if (context.Cache["uid" + context.User.Identity.Name] == null)
{
// create a new identity, based on the login name
GenericIdentity identity = new GenericIdentity(context.User.Identity.Name);
// get the roles from the database
ArrayList listRoles = DataFetcherClass.GetRoles(context.User.Identity.Name);
string[] roles = new string[listRoles.Count];
for (int i=0; i<listRoles.Count; i++) roles[i] = listRoles[i].ToString();
// put the identity and roles in a new principal
GenericPrincipal principal = new GenericPrincipal(identity,roles);
// cache it
context.Cache.Insert("uid" + context.User.Identity.Name, principal, null, DateTime.Now.AddSeconds(60), new TimeSpan(0));
// assign the new principal to the user
context.User = principal;
}
else
{
// get the user's new Principal object from cache
context.User = (GenericPrincipal)context.Cache["uid" + context.User.Identity.Name];
}
}
}
}
web.config
Imports System
Imports System.Collections
Imports System.ComponentModel
Imports System.Web
Imports System.Web.SessionState
Imports System.Web.Security
Imports System.Configuration
Imports System.Security.Principal
Imports PopForums.Data
Public Class UserHttpModule
Implements IHttpModule
Public Sub Init(application As HttpApplication)
AddHandler application.AuthenticateRequest, AddressOf _
Application_AuthenticateRequest
End Sub
Public Sub Dispose()
End Sub
Private Sub Application_AuthenticateRequest(sender As Object, _
e As EventArgs)
Dim application As HttpApplication = CType(sender, _
HttpApplication)
Dim context As HttpContext = application.Context
If context.Request.IsAuthenticated Then
' check for a cached lookup first
If context.Cache(("uid" + context.User.Identity.Name)) _
Is Nothing Then
' create a new identity, based on the login name
Dim identity As New GenericIdentity(context.User.Identity.Name)
' get the roles from the database
Dim listRoles As ArrayList = _
DataFetcherClass.GetRoles(context.User.Identity.Name)
Dim roles(listRoles.Count) As String
Dim i As Integer
For i = 0 To listRoles.Count - 1
roles(i) = listRoles(i).ToString()
Next i
' put the identity and roles in a new principal
Dim principal As New GenericPrincipal(identity, roles)
' cache it
context.Cache.Insert("uid" + context.User.Identity.Name, _
principal, Nothing, DateTime.Now.AddSeconds(60), New TimeSpan(0))
' assign the new principal to the user
context.User = principal
Else
' get the user's new Principal object from cache
context.User = CType(context.Cache(("uid" + _
context.User.Identity.Name)), GenericPrincipal)
End If
End If
End Sub
End Class
Our HttpModule is added to the processing stream just like an HttpHandler, via entries in web.config. The class must be compiled to an assembly and placed in the /bin folder.Listing 7.6. In this case, we're going to add a new event handler that we're calling Application_AuthenticateRequest (we can call it anything) to the application's AuthenticateRequest event. Note that the event handler method has the signature you're used to seeing now, passing in object and EventArgs parameters. We could add any number of event handlers to application events here.The Dispose() method doesn't do anything in our case, but because it's part of the IHttpModule interface, we must define it here.Now we can fill out our actual event handler to do something. We start by defining an HttpApplication object. We need a reference to the application to interact with it, and this is where that well-defined method signature for event handlers comes in handy. The object we call sender is actually the application object running the show, but because event handlers have a generic object in the method signature, we need to cast sender into an HttpApplication object.For our purposes, we need a reference to the current HttpContext of the request and response, including the user's Principal object and the Cache object. This is no problem now that we have a reference to the application. We'll create an HttpContext object that references the application object's Context property.Now that we have the HttpContext, we have access to many of the objects we're used to having in pages. We first check the Cache to see if the user's Principal object is stored in memory, using a key that combines the string "uid" and the user's name. If it's not there, we'll get the roles from the database and build a new Principal from scratch. That part of the code is fairly well commented, so I'll let you decipher the rest on your own.Why does this work? Recall Figure 7.1, which outlines some of the more critical events in the application, page, and control life cycles. You'll see that the application's AuthenticateRequest event happens fairly early in the sequence and that the forthcoming Page object isn't even a twinkle in the server's eye at that point. By the time all that code fires, we can use User.IsInRole("somerole") or a LoginView control to determine the user's role because it was established earlier.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<httpModules>
<add name="UserHttpModule" type="UserHttpModule, MyDll" />
</httpModules>
</system.web>
</configuration>
Although this is a very practical example of an HttpModule in action, you technically wouldn't need it in ASP.NET v2.0. This version of the framework has a built-in role manager, which we'll explore in Chapter 11, "Membership and Security." However, if you're using v1.x of ASP.NET, this module solves a very common problem and is easy to implement.
For an additional HttpModule example, check out Listing 17.6 in Chapter 17. In a discussion about threading, you can see how easy it is to execute code on a regular interval without being tied to the request/response life cycle. |
Figure 8.3. The role of HttpHandlers and HttpModules in the page lifecycle.
