Managing Forms Authentication Access Rules programmatically

Version Française

Introduction:

Forms Authentication is not one of the default authentication mechanisms used by the IIS 5 and 6 servers and was introduced together with ASP.net applications. Forms authentication is aimed mainly at internet facing ASP.net web-applications. It allows them to be able to authenticate users without having to rely on a corporate directory such as Active Directory.

The way in which forms authentication grants access to different parts of an ASP.net application is based on a system of permissions described in the web-application's web.config file. It is not the purpose of this article to discuss in detail the section of the web.config that is responsible for attributing the permissions or how ASP.net forms works. If you want further reading on these topics, I would recommend:

Rather the article focuses on showing how you can programmatically go about adding or changing these access rules from inside your own application.

Scenario:

Suppose that you built an ASP.net web-application that had the following folder structure:

To continue with the scenario, we will suppose that you have two roles, one called Administrators and one called SiteUsers. All authenticated users for your web-site will be part of the SiteUsers role and only the site Administrators will be part of the Administrators role. We would like to achieve the following:

When the ASP.net application is started up, we would like to verify that:

  • only Administrators have access to the AdminFolder of the application. If not we would like to change this so that the policy is enforced.
  • only SiteUsers and Administrators to have access to the UsersFolder of the application, and if this is not the case, we need to change the access rules to ensure that the scenario is met.
  • finally, that everyone, even non-authenticated users can have access to the PublicFolder.

There are two ways of doing this: one is to code all these rules directly in the web.config section of the application and then deploy it. The second way is to programmatically verify that the rules are in the web.config file and if they are not there, to add them.

Implementation:

The first thing to realize is that an ASP.net web application can have web.config files at multiple locations. Each web.config file describes the settings for the application that apply to the folder where the web.config is located and to the folders underneath. It also may overwrite or inherit settings from web.config files that are above its location in the folder hierarchy. Thus, the web.config file in the AdminFolder will inherit settings described in the web.config found in the PublicFolder.

To implement the scenario above, we must overcome the following obstacles:

  • how to access information that is stored in the web.config file
  • how to get a hold of the section that describes the authorization rules for forms authentication
  • how to distinguish between rules declared at the level of the current web.config and rules inherited from the web.config above.
  • How to store the information that we may need to add back into the web.config.

Accessing the web.config:

To access the web.config, the .Net development team has kindly provided us with a class from which we can instantiate objects that represent a .config file. In the case of our web-application we will use the WebConfigurationManager class with its OpenWebConfiguration(System.String) method. The argument of the method allows us to specify the location of the web.config file we wish to target. For example "/" would tell ASP.net that we would wish to target the web.config file that is at the root of our application. In this case the root of our app is the folder called PublicFolder.

The call to the WebConfigurationManager.OpenWebConfiguration(System.String) will return an object of type System.Configuration.Configuration which represents the web.config that we have just opened.

Configuration config =
WebConfigurationManager.OpenWebConfiguration("/");

The Configuration object allows us to get a hold of any section in the web.config we might need to read or modify. This is simply done via the GetSection(System.String) method. We can thus write the following code:

AuthorizationSection auth =
(AuthorizationSection)config.GetSection("system.web/authorization");

We see that just like everything else in .Net, a section of the web.config is still an object. And we have several types of sections, the one that interests us today being of type AuthorizationSection. Looking at the <Authorizations> tag in web.config, we notice that it contains a series of inclusion and / or exclusion rules that grant or deny access to users and roles to parts of the application. Following the model, the AuthorizationSection object contains a list of AuthorizationRule objects. To inspect the rules that are present at each folder level, all we have to do is iterate through the Rules of the AuthorizationSection. For the public folder the code would look like this:

foreach (AuthorizationRule ar in auth.Rules)
{
//iteration logic will go here...
}

We thus verify that the following two rules exist:

  • We should allow all un-authenticated users access to pages in this folder
  • We should also allow all authenticated users access to pages in this folder.

Should the rules not be found, we would need to add them to the list of rules. We can do this by declaring a new object of type AuthorizationRule and then setting its properties accordingly. The code below shows how this would be done for an access rule allows all un-authenticated users to access the site:

//create the rule - allow access to un-authenticated users
accessRule =
new AuthorizationRule(AuthorizationRuleAction.Allow);
accessRule.Users.Add("?");
auth.Rules.Add(accessRule);

Saving changes to the configuration is simply done by using the Configuration object's Save(System.Configuration.ConfigurationSaveMode)method. When using the ConfigurationSaveMode.Minimal we will only save the properties that differ from the ones that we inherit. This will become more obvious when we deal with the web.config files that are underneath the root one in this scenario:

config.Save(ConfigurationSaveMode.Minimal);

Now that we have ensured that the permissions are correct for the PublicFolder, we can focus on the AdminFolder. Here we want only users that are part of the Admin role to be able to access pages and all the rest (authenticated and non-authenticated users) should not have access. The access rules are stored in a web.config located in the AdminFolder, underneath the PublicFolder. If we look at this config file we will notice that normally only the sections that differ from the web.config that is at the root of the application are present. To recover the <Authorizations> section, we can create another Configuration object and load it providing the appropriate path: "/AdminFolder":

Configuration config =
WebConfigurationManager.OpenWebConfiguration("/AdminFolder/");
AuthorizationSection auth =
(AuthorizationSection)config.GetSection("system.web/authorization");

The inspection of the rules is done just as we have for the previous example – we iterate through all the AuthorizationRule objects that are in the Rules section of the AuthorizationSection we have recovered. We will look to see if we find the following rules:

  • Deny access to all un-authenticated users
  • Deny access to all members of the SiteUsers role
  • Grant access to all members of the AdminUsers role

There is also another obstacle to overcome: we inherit access rules from the web.config that is the PublicFolder (the application's root folder). We need to identify these rules and ignore them since the access rules we are looking for will take precedence over any inherited rules. In general, settings in web.config files that are deeper in the hierarchy will overwrite settings from the application's root web.config. For access rules, deny rules take precedence over all grant rules. Thus if we grant access to un-authenticated users to all pages found in the PublicFolder and its subfolders, the fact that we add a rule denying all non-authenticated users access to the AdminFolder (which is a sub-folder of the PublicFolder) will take precedence.

To identify inherited rules we will inspect the ElementInformation for each of the AccessRules we iterate through. There are two such PropertyInformation objects that are of interest: "users" and "roles". An access rule can grant or deny access either to a specific user or to all the users in a specific role. Thus we can find rules that either apply to users or to roles and we need to get both PropertyInformation objects to be able to decide if we are dealing with the first or the latter of the two cases. For any given rule only one of these objects will contain a value, the other will be null. To decide if the property is inherited, we look at the ValueOrigin member of the PropertyInformation object and compare it to the PropertyValueOrigin.Inherited constant:

private bool IsRuleInherited(AuthorizationRule rule)
{
//to see if an access rule is inherited from the web.config above
//the current one in the hierarchy, we look at two PropertyInformation
//objects - one corresponding to roles and one corresponding to
//users

PropertyInformation usersProperty = rule.ElementInformation.Properties["users"];
PropertyInformation rolesProperty = rule.ElementInformation.Properties["roles"];

//only one of these properties will be non null. If the property
//is equal to PropertyValueOrigin.Inherited, the this access rule
//if not returned in this web.config
if (usersProperty != null)
{
if (usersProperty.ValueOrigin == PropertyValueOrigin.Inherited)
return true;
}

if (rolesProperty != null)
{
if (rolesProperty.ValueOrigin == PropertyValueOrigin.Inherited)
return true;
}

return false;
}

Armed with this new method, we can no update the code that iterates though all the AuthorizationRules at this level of the application, adding the following logic: if a rule is inherited, ignore it since it is not one of the access rules that is of interest to us during the iteration – we can only change inherited rules in the web.config where they are inherited from – in the current web.config the only thing we can do is to overwrite their values.

foreach (AuthorizationRule ar in auth.Rules)
{
if (IsRuleInherited(ar))
{
continue;
}

//check if the rule is one of the rules that are of interest
//...
}

With this algorithm implemented, we can do the same for the web.config file that is found in the UsersFolder. To retrieve this file we will use the WebConfigurationManager.OpenWebConfiguration(System.String) just like before, indicating the location of the config file starting from the root of our website ("/UsersFolder"). We then get a hold of the AuthorizationSection object and inspect its rules one by one ignoring any that are inherited:

Configuration config =
WebConfigurationManager.OpenWebConfiguration("/UsersFolder/");

AuthorizationSection auth =
(AuthorizationSection)config.GetSection("system.web/authorization");

//check all rules in the AuthorizationSection of the web.config
//ignore the rules that are inherited and set the flags below if
//we find any of the rules that are of interest

bool foundAllowSiteUsers = false;
bool foundDenyAnonymous = false;
bool foundAllowAdmins = false;

foreach (AuthorizationRule ar in auth.Rules)
{
if (IsRuleInherited(ar))
{
continue;
}

//check if we are denying access to Un-Authenticated Users
//note that Un-Authenticated users are represented by the
// ? (question mark) sign

if (ar.Users.Contains("?") &&
ar.Action == AuthorizationRuleAction.Deny)
{
foundDenyAnonymous = true;
continue;
}

//check if we are allowing access to the users that
//belong to the Administrators role
if (ar.Roles.Contains("Administrators") &&
ar.Action == AuthorizationRuleAction.Allow)
{
foundAllowAdmins = true;
continue;
}

//check if we are denying access to the users that
//belong to the SiteUsers role
if (ar.Roles.Contains("SiteUsers") &&
ar.Action == AuthorizationRuleAction.Allow)
{
foundAllowSiteUsers = true;
continue;
}
}

We are looking to have the following rules:

  • Deny access to all un-authenticated users
  • Grant access to users from the Administrators role
  • Grant access to users from the SiteUsers role

If these are not present we proceed to add them and save the new configuration:

//looking at the flags decide if we need to update the config
bool updateConfig = false;
AuthorizationRule accessRule;

if (!foundAllowAdmins || !foundDenyAnonymous || !foundAllowSiteUsers)
{
//clear all pre-existing rules.
auth.Rules.Clear();

//create the rule - deny access to users belonging to the SiteUsers role
accessRule =
new AuthorizationRule(AuthorizationRuleAction.Allow);
accessRule.Roles.Add("SiteUsers");
auth.Rules.Add(accessRule);

//create the rule - deny access to Un-Authenticated users
accessRule =
new AuthorizationRule(AuthorizationRuleAction.Deny);
accessRule.Users.Add("?");
auth.Rules.Add(accessRule);

//create the rule - allow access to all users belonging to the Administrators role
accessRule =
new AuthorizationRule(AuthorizationRuleAction.Allow);
accessRule.Roles.Add("Administrators");
auth.Rules.Add(accessRule);

//set the update flag to true
updateConfig = true
}

if (updateConfig)
{
config.Save(ConfigurationSaveMode.Minimal);
}

Conclusion:

This shows you the steps that are needed to ensure that when your application starts up in IIS, you are able to check that the access rules you intended for Forms Authentication are in place and should they not be there, you can add them. A small note when deploying the sample in IIS: you will need to grant write access to the site.

This is needed so that you can save to disk the changes that you have made to the web.config files should you need to make any.

Source Code:

You may use the source code provided below. Just unzip the solution to a local folder on your computer and launch in Visual Studio 2008 or 2005

Download Source Code

Written by: Paul Cociuba - IIS and Internet Dev Team

ManagingFormsAccessRules.zip