Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Asp Net 2.0 Security Membership And Role Management

.pdf
Скачиваний:
51
Добавлен:
17.08.2013
Размер:
12.33 Mб
Скачать

Chapter 13

The IIdentity reference is needed because any class that implements IPrincipal needs to be able to return the authenticated identity (that is, an IIdentity reference) associated with that principal. You can see from the constructor signature that the RolePrincipal is not hard-coded to any specific implementation of an IIdentity. That is why you can enable the Role Manager feature with any type of authentication mechanism available in ASP.NET. Forms Authentication creates a FormsIdentity, Windows Authentication results in a WindowsIdentity and your own custom authentication mechanisms may use GenericIdentity. For all of these cases, the RolePrincipal is unaware of the underlying authentication implementation that generates an IIdentity reference.

One reason that you might use the less-than-obvious combination of Windows Authentication and Role Manager is that you may not want to clutter your Active Directory with application-specific roles. It may be a somewhat laborious process to register application-specific groups in your directory if the directory is tightly managed by a central IT group. For this reason, storing application-specific roles off to the side using Role Manager can be very convenient. Also, if you develop “quick-hit” web applications that exist for only a few weeks or months, it’s very easy to stuff application roles into a Role Manager database that can be deleted when the application has outlived its usefulness.

With that said (and before the Active Directory team comes after me!) with the introduction of Active Directory Application Mode (aka ADAM), application developers also have the option of storing user- to-group assignments in application-specific ADAM instances. You can take this approach even further using the new Authorization Manager (aka AzMan) feature of Windows Server 2003 by deploying an AzMan policy store in an application-specific ADAM instance. These types of architectural decisions are beyond the scope of this book, but you should look into them especially for intranet web applications where you may be considering using Role Manager to get around operational or administrative hassles of a centrally managed Active Directory.

You now know that all of the ASP.NET-based features key their user records off of a combination of username and an application name usually found in a provider’s configuration. In the case of the RolePrincipal, when you use the constructors the RolePrincipal “knows” how to look up user information from the default provider based on the following information:

The username comes from IIdentity.Name.

For the constructor with just a single IIdentity parameter, the application name is the one used by the default RoleProvider as defined in configuration.

For the constructors that takes an additional providerName parameter, the application name is the one associated with Roles.Providers[providerName].

In this way, the RolePrincipal can take an arbitrary string representation of a username, and it can associate the username with role data maintained by any of the configured providers.

Most of the methods on RolePrincipal that are not directly associated with cookie caching are pretty self-explanatory:

IsInRole — Based on IIdentity.Name and the application name of the associated provider, the RolePrincipal indicates whether or not IIdentity.Name belongs to the specified role. If the RolePrincipal has not previously cached role information for the user, then the associated provider is called to get all of the user’s roles. If this method is called for an IIdentity of an unauthenticated user (that is, IIdentity.IsAuthenticated returns false), then this method

522

Role Manager

always returns false. This is because the Role Manager feature is only intended for use with authenticated users. Because many sites have public and secured pages, the Role Manager feature can silently run without error for unauthenticated users; it’s just that methods like IsInRole will always return false.

GetRoles — The RolePrincipal returns a string array containing all of the roles that IIdentity

.Name belongs to. If the RolePrincipal has not previously cached role information for the user, then the associated provider is called to get all of the user’s roles. As with IsInRole, this method has special behavior for unauthenticated users. For an unauthenticated user, this method always returns an empty string array (that is, string[0] ).

SetDirty — Tells the RolePrincipal object that it should invalidate any internally cached data. As a result, the next call to IsInRole or GetRoles will always result in a round trip to the associated provider.

The noncookie caching properties and their behavior are listed here:

Identity — This property returns the IIdentity that was originally used when the RolePrincipal was constructed.

ProviderName — The name of the provider associated with the RolePrincipal. This will return the name of the default provider (if you used the constructor that only accepts an IIdentity) or it will return the name of one of the providers configured for use with Role Manager. This property is a string parameter because the RolePrincipal is itself serializable. By storing the associated provider as a string name, RoleProviders don’t themselves need to be serializable. Note that if your application does something funky like serializing a principal in one app-domain and deserializing it in another app-domain, you need to make sure that ProviderName is available in the Role Manager’s provider collection for the app-domain where deserialization occurs.

IsRoleListCached — This property returns true if RolePrincipal is currently caching the user’s roles internally. This internal cache is discussed in the next few paragraphs.

Version — Currently, this property will always return 1 for the 2.0 Framework. In future versions if the internal format or the public functionality of the RolePrincipal changes, the Version property will be changed as well.

Both the IsInRole and GetRoles methods rely on the RoleProvider associated with the RolePrincipal to carry out their work. It turns out that the internal implementation of IsInRole results in a call to

RoleProvider.GetRolesForUser as opposed to RoleProvider.IsUserInRole. The reason for this behavior is that even if you have cookie caching turned off, the RolePrincipal still attempts to optimize performance of the IsInRole method.

Immediately after you new() up a RolePrincipal there is an empty internal dictionary that is ready and waiting to cache role information. The first call after object construction to either IsInRole or GetRoles causes the RolePrincipal to get a reference to its associated provider and retrieve a string array of the roles associated with the user. This array is then cached within the principal’s internal role dictionary. Code can verify this is the case because after the role information is cached RolePrincipal

.IsRoleListCached returns true. Now on subsequent calls to IsInRole, RolePrincipal recognizes that this dictionary contains data. So, instead of making a round trip to the provider again, the principal just looks for the requested role inside of the dictionary of cached role information. GetRoles has similar behavior, although in its case the method just returns the internal dictionary as an array because there is no need to search for a specific role.

523

Chapter 13

Of course, at some point you may want to invalidate the cached role information. For example, after 15 minutes have passed, you may want to force the RolePrincipal to “forget” its current role information and refresh it from the provider. When you call the SetDirty method, it flips the value of RolePrincipal

.IsRoleListCached to false. The next time either IsInRole or GetRoles is called, RolePrincipal sees that the role information is now considered stale, and so it queries the provider again for all of the user’s role data. This caching behavior has a few implications that you need to be aware of.

Because GetRolesForUser is called on the provider, users associated with large numbers of roles (that is, hundreds of roles) will find the RolePrincipal to be slow the first time either IsInRole or GetRolesForUser is called. In fact, for websites or other applications that need to support hundreds of roles per user, you should carefully assess the performance of retrieving all of a user’s roles for these methods. To cut down on the number of round trips made to the back-end data store, you may find that you need to implement a custom RoleProvider that internally caches a user’s role information.

The second issue is that the IsInRole method compares the role parameter against values in the internal dictionary with a case-insensitive comparison, using the casing rules for the invariant culture. If you happen to use a back end like SQL Server and you are running a case-insensitive sort order with the Latin collation order, the behavior of the RolePrincipal comparison won’t matter to you. The standard Latin collation order is roughly equivalent to the Framework’s invariant culture. But if you happen to use a non-Latin character set, you may run into issues where the casing rules in the database don’t match the casing rules for RolePrincipal. Remember from Chapter 11, on SqlMembershipProvider, that all of the SQL based ASP.NET providers work in a case-insensitive manner. The SqlRoleProvider also works in a case-insensitive manner. However, even though the providers for Role Manager work in a case-insensitive manner, casing rules are still partially determined by the culture as well.

The casing rules for the invariant culture are not the same as the casing rules for Cryllic (as an example). As a result, you can end up in some edge scenarios where you create role names in your data store that are considered unique because the data store is using culture-specific casing rules. But when you attempt to use RolePrincipal it throws an exception because from a culture-invariant standpoint it thinks two role names are actually the same value. The array of strings returned from RoleProvider.GetRolesForUser could contain two strings that are considered the same value in the invariant culture. When the RolePrincipal attempts to add the strings to its internal dictionary (which is an instance of a HybridDictionary), the dictionary can throw an ArgumentException because it detects duplicate string values.

Another issue can arise where the result of RolePrincipal.IsInRole does not match the result from RoleProvider.IsUserInRole. The classic “Turkish I” problem is an example where a mismatch can occur for role comparisons. In the Turkish character set a capital “I” and a small “i” are actually associated with two completely different characters. Lowering “I” in Turkish will result in a completely different character than the English “i.” This can cause a problem when RolePrincipal.IsInRole is called by URL authorization because if role names from the database differ only on characters like “I,” then RolePrincipal.IsInRole may consider a user to belong to more roles than they really do. For example, from an invariant culture perspective a user may be considered to belong to both “ThIs role” and “This role.” So if you had a URL authorization rule like <add roles=”ThIs role” />, and the role in a database with the Turkish collation was “This role,” the RolePrincipal object would return true from IsInRole. However, the same comparison made using RoleProvider.IsUserInRole against the database would treat these two roles as completely different and unique strings. A role check using the provider directly would succeed only for “This role.” It would fail for the other role because in Turkish the capital “I” is from a different character pair.

524

Role Manager

Now granted that this discussion can be a bit mind-numbing, and when the ASP.NET team attempted to protect against this, the cure was worse than the problem. The main thing to remember is that if you use Role Manager with data stores that aren’t running in the invariant culture (for example, the Latin1_General collation is a close enough approximation in SQL Server), make sure that the role names you choose result in consistent string comparisons in your data store and on servers where you will be calling

RolePrincipal.IsInRole.

At this point, take a look at a simple example of a console application that demonstrates how the internal caching behavior of RolePrincipal works. Just as Membership works in non-ASP.NET environments, Role Manager can be used outside of ASP.NET. The sample console application references System.Web.dll and includes configuration settings in its app.config file to enable the Role Manager feature:

<roleManager enabled=”true” defaultProvider=”roleprincipalcaching”>

<providers>

<add name=”roleprincipalcaching” etc... />

</providers>

</roleManager>

The console application performs some initial setup for the example and then exercises the internal cache logic in RolePrincipal by calling GetRoles after the role assignments have been changed:

using System.Security; using System.Web.Security;

...

static void Main(string[] args)

{

//initial setup code – snipped for brevity...

GenericIdentity gi = new GenericIdentity(“testuser_rp”);

RolePrincipal rp = new RolePrincipal(gi);

string[] currentRoles = rp.GetRoles(); foreach (string r in currentRoles)

Console.WriteLine(r);

//Now change the user’s role assignments Roles.AddUserToRole(“testuser_rp”, “role_2”); Roles.RemoveUserFromRole(“testuser_rp”, “role_3”);

//The RolePrincipal’s roles will not have changed at this point //Note that the sample code never sets Thread.CurrentPrincipal so //the RolePrincipal has not been invalidated at this point currentRoles = rp.GetRoles();

foreach (string r in currentRoles) Console.WriteLine(r);

//Force the RolePrincipal to flush its internal cache rp.SetDirty();

//Now the RolePrincipal will reflect the changes

525

Chapter 13

currentRoles = rp.GetRoles(); foreach (string r in currentRoles)

Console.WriteLine(r);

}

A GenericIdentity is constructed with a username that has already been associated with three roles using the default provider. The first call to GetRoles causes this information to be loaded from the provider:

role_1 role_3 role_5

After dumping out this information, the test application changes the user’s role assignments by adding the user to a new role, as well as removing the user from an existing role. However, because the first call to GetRoles caused the RolePrincipal to cache the role information internally, the next call to GetRoles still uses the cached information.

role_1 role_3 role_5

The RolePrincipal doesn’t reflect the changes to the user’s role assignments at this point. The test application then forces the RolePrincipal instance to flush the cached information with a call to SetDirty. Now when the test application calls GetRoles again, the principal goes back to the provider to reload the role data, and as a result the output reflects the changes that were made.

role_1 role_2 role_5

Keep this behavior in mind if you happen to be working with an administrative application where you change user-to-role assignments. If you Alt-Tab off to another browser window running as the user you just edited, and you are wondering why no changes are showing up, it is probably the caching behavior in RolePrincipal that is preventing your changes from taking effect.

Now that you have an understanding of how the internal cache within RolePrincipal works, you can explore how cookie caching is supported as an additional caching layer. The Role Manager feature has the ability to take the internal role cache within a RolePrincipal and store this information inside of a cookie. The RoleManagerModule is responsible for managing this process, but it is the RolePrincipal that supports the core functionality that makes this all work.

The method that makes this work is the ToEncryptedTicket method on the principal. This method serializes a RolePrincipal instance into a string. Internally, the method first runs RolePrincipal through the binary formatter. Because RolePrincipal implements ISerializable, some custom serialization logic runs at this point to handle the serialization of the principal’s IIdentity. RolePrincipal doesn’t serialize its associated IIdentity when serialization occurs as a result of a call to the ToEncryptedTicket method. Note that if you just write some serialization code using the Framework’s BinaryFormatterdirectly, then the IIdentity reference will be serialized.

526

Role Manager

Because the intent of ToEncryptedTicket is to convert the RolePrincipal into a payload suitable for a cookie, it intentionally skips serializing the IIdentity reference. There is no need for it when reconstituting a RolePrincipal from a cookie because the constructor overloads that accept the stringized RolePrincipal also require an IIdentity reference. As a side note, RolePrincipal in the RTM version of the Framework uses binary serialization because theoretically this should make it easier in future versions to be able to run web farms with different versions of the Framework issuing different serialized versions of RolePrincipal. Both up-level and down-level versions of the Framework should be able to work with a serialized RolePrincipal without blowing up due to deserialization exceptions.

After ToEncryptedTicket gets back a byte array representation of the RolePrincipal, it converts the byte array into a string that can be safely stored in a cookie without triggering ASP.NET request validation. As part of this conversion, RolePrincipal secures the string using the settings from the cookieProtection attribute in the <roleManager /> configuration element. By default, the string is encrypted using AES and signed with HMACSHA1. The algorithms used and the key values used are all determined from the <machineKey /> section. If you want to change any of this information, you can change the configuration attributes on <machineKey /> just as you would for controlling the encryption and signing information for Forms Authentication. Also, as with Forms Authentication, you can change the cookieProtection attribute on <roleManager /> to None, All, Encryption, or Validation.

At this point, the work of ToEncryptedTicket is done; it doesn’t actually validate if the resulting payload is too large for storage in a cookie. Furthermore, there isn’t any functionality inside ToEncryptedTicket specific to ASP.NET. You can literally serialize RolePrincipal into a string, store the string somewhere (on a disk, in a database table, and so on), and then reconstitute the RolePrincipal from the string at a later point in time.

//Serialize the RolePrincipal

string stringRP = rp.ToEncryptedTicket();

//Do some other work here...

//Reconstitute the RolePrincipal

RolePrincipal anotherRP = new RolePrincipal(gi, stringRP); Console.WriteLine(“User is in role_1: “ + anotherRP.IsInRole(“role_1”)); Console.WriteLine(“User is in role_3: “ + anotherRP.IsInRole(“role_3”)); Console.WriteLine(“User is in role_5: “ + anotherRP.IsInRole(“role_5”));

The output from this sample code is:

User is in role_1: True

User is in role_3: True

User is in role_5: True

Using the sample console application from earlier, you can extend it by serializing the RolePrincipal prior to changing the user’s role assignments (remember the user was removed from role 3 and added to role 2). If you add this code to the sample application, after creating a new RolePrincipal using the output from ToEncryptedTicket, the original role information is cached internally by the new

RolePrincipal instance.

What is interesting, though, is if you take the new RolePrincipal and call GetRoles on it:

currentRoles = anotherRP.GetRoles(); foreach (string r in currentRoles) Console.WriteLine(r);

527

Chapter 13

when you dump out the results you will see what might look like a discrepancy:

role_1 role_2 role_5

What happened here? For a second there it looked like the output of ToEncrypedTicket preserved the set of role assignments at the time serialization occurred. The previous code snippet with a series of IsInRole checks definitely confirms this behavior. The reason for this apparent schizophrenia of the RolePrincipal is that the principal handles the internal cache differently when a new RolePrincipal is initialized from the string output of ToEncryptedTicket.

After you call either of the two constructors that have an encryptedTicket parameter (the two constructor overloads are the companions to the two constructor overloads discussed earlier with the one difference being the extra string parameter for the encrypted ticket), RolePrincipal does a few special things with the extra string data:

1.The encryptedTicket parameter is decoded back into a byte array, and that array is then deserialized with the BinaryFormatter.

2.The RolePrincipal makes two sanity checks with the resulting data. It confirms that the username that was previously encoded into the encryptedTicket matches the username on the IIdentity that was passed to the constructor. Then RolePrincipal confirms that the provider name encoded in the encryptedTicket matches the name of the provider associated with the current RolePrincipal instance. Both of these comparisons are case-insensitive ordinal comparisons. If either of these checks fails, the ticket is discarded and the RolePrincipal instance functions as if it were constructed without the encrypted ticket.

3.If the expiration date contained in the deserialized ticket indicates that the information has expired, the ticket is discarded and the RolePrincipal instance functions as if it were constructed without the encrypted ticket.

4.RolePrincipal looks at IssueDate and ExpireDate that were extracted from the encryptedTicket. If you have configured Role Manager to support sliding cookie expirations (that is, the cookieSlidingExpiration configuration attribute on the <roleManager /> configuration element has been set to true), and if more than 50% of the encrypted ticket’s lifetime has passed, the principal resets IssueDate to the current date-time and updates ExpireDate accordingly. As a side effect of this, the state of the principal is considered to have changed so the principal also marks itself for reserialization when RoleManagerModule runs at the end of a page request.

These validations ensure that the string-encoded version of the RolePrincipal is not spuriously used with a different user. It also ensures that whatever machine is responsible for decoding the encrypted string actually has a named RoleProvider matching the one defined within the encryptedTicket parameter. These checks imply a few things you need to do if you want cookie caching to work properly across multiple machines in a web farm.

First, you need to ensure that all of the providers are configured the same way across all of the machines. This means the same provider names need to be present for the encrypted string representation of a principal to work. It also implicitly means that providers with the same name in a web farm should be configured the same way. For example, the RolePrincipal is not going to validate that the application name for a provider called foo” on one machine is actually the same application name as the provider foo that was associated with RolePrincipal when it was originally serialized on a different machine. If

528

Role Manager

for some reason you use the same provider names across a web farm but with different application names, then it is likely you will end up with inconsistent role information depending on what machine servers up any given request.

The second assumption is that if a user is initially authenticated as foo when a RolePrincipal is serialized, then on another machine when a RolePrincipal is being deserialized the same user will be known as foo. Typically, for custom authentication schemes, or for Forms Authentication, the string value of the authenticated username is fixed after login. For example, the string used at login time against a site using Forms Authentication is encoded into the forms authentication cookie, and hence will remain the same for the duration of the login session.

Back to the original problem where the sample code appeared to lose the cached role information passed via encryptedTicket. Assuming that none of the validations just described failed, you have a RolePrincipal with its internal dictionary containing all of the roles from encryptedTicket. When this initialization occurs though, RolePrincipal “remembers” that it was initialized from an encrypted string, and not from a call to RoleProvider.GetRolesForUser. As long as your code just calls IsInRole, RolePrincipal will continue to fulfill this request using the internal dictionary of roles.

However, after you call GetRoles as shown in the earlier code snippet, RolePrincipal decides that the role information from the encrypted string is not sufficiently authoritative to fulfill the request. So instead, the RolePrincipal flushes its internal cache and then calls GetRolesForUser on the provider. After GetRolesForUser is called, the RolePrincipal ends up with the latest role information for the user, which is why in the sample the dump of the user’s roles after the call to GetRolesForUser was different from the results of the successive IsInRole checks. After GetRolesForUser has been called on the provider, the RolePrincipal remembers that this has occurred, and now all subsequent calls to either IsInRole or GetRoles will be served from the principal’s internal cache.

Part of the reason for this discrepancy in behavior is that cookie caching is meant to be used only to speed up calls to IPrincipal.IsInRole. Hence, the reason for storing the encryptedTicket in a cookie is only to fulfill role checks. The general idea behind calling GetRoles is that the caller wants to have a reasonably up-to-date representation of that user’s roles. Even though calling GetRoles more than once results in

the use of cached data, in the normal use of a RolePrincipal on an ASP.NET page request, the page is running for only a few seconds. So, having GetRoles call the provider the first time ensures that for the duration of the page request your code has a very up-to-date array of the user’s roles. The subsequent caching in this case is a minor optimization to ensure that if the page code continues to call GetRoles that the page doesn’t end up thrashing the underlying data store. If your code actually requires different GetRoles calls to return different data, you can always manually force the principal to flush its internal cache through a call to SetDirty.

Aside from the extra constructor overloads and the ToEncryptedTicket method, there are a few properties on RolePrincipal that deal with cookie caching. These are briefly described in the following list:

CachedListChanged — If the principal calls GetRolesForUser on its associated provider, if

SetDirty is called, or if the RolePrincipal renewed the IssueDate and ExpireDate due to sliding expirations, the value of this property is set to true. However, if the principal is initialized from an encrypted ticket, the issue and expiration dates were not refreshed, and only IsInRole is called on the principal, this property returns false. This property is used by the RoleManagerModule to determine whether it needs to reissue the role cache cookie. If the state of the principle’s internal cache initialized from an encrypted ticket has not changed and the date information also has not changed, then the RoleManagerModule can avoid the expensive overhead of reserializing the RolePrincipal and encrypting the results.

529

Chapter 13

IssueDate — Returns the machine local date-time the cached information in an encryptedTicket was originally created. If the RolePrincipal was not initialized from an encryptedTicket, this property always returns the current local date-time. Note that internally this data is stored as a UTC date-time, and the “UTCness” of this value is preserved when a RolePrincipal is serialized by ToEncryptedTicket.

ExpireDate — Returns the machine local date-time that the cached information in an encryptedTicket is no longer considered valid. If the RolePrincipal was not initialized from an encryptedTicket (for example, the first time a RolePrincipal for a user is ever created), this value is set to the current local date-time plus the value of the cookieTimeout configuration attribute on the <roleManager /> configuration element. As with IssueDate, internally this value is maintained as a UTC date-time.

Expired — This property compares the private UTC value of ExpireDate against the current UTC date-time. If ExpireDate is less than the current UTC date-time, then the property returns true. This property is checked when the RolePrincipal is deserialized from an encryptedTicket to determine whether the encrypted information is stale. Note that you can end up in an edge case where the deserialization check succeeds, but then one millisecond later the encrypted information expires. In this case, for the duration of the lifetime of the RolePrincipal, the cached information from the encryptedTicket will still be used. This behavior is OK for a page request, because a page request is normally completed in a few seconds. However, if you are using the string ticket to initialize a RolePrincipal inside an application like a Winforms application, where a RolePrincipal instance may live for a very long time, then you should ensure that you have code in your application that periodically checks the Expired property on the principal and generates a new instance if the current

RolePrincipal is expired.

CookiePath — This property simply returns the value of Roles.CookiePath, which in turn comes from the cookiePath configuration attribute on the <roleManager /> configuration element. At one point, the path information for a RolePrincipal was actually stored in the encryptedTicket. However, the path is no longer stored in the serialized string because you could end up bloating the size of the serialized string for applications that had lengthy URLs. Note that in a web farm environment all machines must be configured to use the same cookiePath for Role Manager. Otherwise, the role cache cookie issued by one web server may never be sent back to other servers in the farm.

In the next section, you will see how the RoleManagerModule works with the RolePrincipal to issue a cookie that contains the encryptedTicket. Keep in mind ahead of time that it’s possible to create an encryptedTicket that is too large for the RoleManagerModule to store in a cookie. Because serializing a RolePrincipal and then encrypting and hashing the result is an expensive operation, you should test the size of the return value from the ToEncryptedTicket method for users with a large number of roles. If the resulting string is longer that 4096 characters, then the RoleManagerModule is never going to issue a roles cookie, and hence you should probably turn of cookie caching.

Because the RolePrincipal uses binary serialization, this adds a few hundred characters of overhead to the size of the role cache cookie. Roughly speaking, there is about an additional 350-character overhead due to using binary serialization as opposed to some type of custom serialization mechanism. This overhead is on top of the bloat caused by encoding the role information for storage in the cookie. For the earlier sample where the user belonged to just three roles, the encryptedTicket was 492 characters long — even though the length of the three role names was just 18 characters. Remember though that this cookie stores not only each role name, but also issue/expiry dates, a version number, the user’s username, the provider name and a few pieces of internal tracking information. As a result, there is

530

Role Manager

always some additional character overhead from storing all of this information. From testing the cookie caching feature with various numbers of roles, the ASP.NET team has been able to successfully store 300 roles (each role name was around seven characters long) in a role cache cookie with a cookie protection setting of “All.”

The RoleManagerModule

The RoleManagerModule is an HttpModule that is responsible for two main tasks:

Early during the request lifecycle, it places a RolePrincipal instance on HttpContext

.Current.User if the Role Manager feature is enabled. This work occurs during the

PostAuthenticateRequest event.

At the end of a request, the module serializes the RolePrincipal into a cookie if cookie caching has been enabled for Role Manager. The module does this during the EndRequest event.

The RoleManagerModule also exposes an extensibility point with the GetRoles event. If you want, you can hook this event and add your own IPrincipal implementation to the context. This event is fired just before the module performs its regular processing during PostAuthenticateRequest.

PostAuthenticateRequest

The RoleManagerModule subscribes to the PostAuthenticateRequest pipeline event because it needs to set up a principal after an authenticated identity has been established but before any authorization occurs. In earlier versions of ASP.NET, doing this was a bit tricky because there were no Post* events. In ASP.NET 2.0 though, there are Post* events for every major pipeline event, and this makes it very easy for functionality like Role Manager to inject itself at precisely the right time during the authentication and authorization process in the HTTP pipeline.

If the Role Manager feature is not enabled, the module immediately exits. This is important because if you look at the default HttpModule configuration in the root web.config, you will see that the RoleManagerModule is always registered.

<httpModules>

<add name=”WindowsAuthentication” .... /> <add name=”FormsAuthentication” ... />

<add name=”RoleManager” type=”System.Web.Security.RoleManagerModule” />

<!---

other modules ---

>

<add name=”UrlAuthorization” ... /> <add name=”FileAuthorization” ... />

<!---

other modules ---

>

</httpModules>

So, the module registration is basically a no-op in the case that the Role Manager feature is disabled. Assuming that the Role Manager feature is enabled though, the first thing the module does is fire the GetRoles event. The event argument for this event can be used by a custom event handler to communicate back to the module as to whether the event handler attached a user principal to the context. The framework’s definition of the event argument is:

531