Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX

By submitting your information, you agree that devx.com may send you DevX offers via email, phone and text message, as well as email offers about other products and services that DevX believes may be of interest to you. DevX will process your information in accordance with the Quinstreet Privacy Policy.


Nine ASP.NET Site Navigation Problem Solutions: Part 2 : Page 2

Discover how to grant or deny users access to pages in your sites, and how to create dynamic site maps using database-driven data.




Application Security Testing: An Integral Part of DevOps

Step 2: Retrieve Roles
The .NET Framework uses RoleProviders to determine which users are in which roles. ASP.NET 2.0 comes with three pre-packaged role providers, or you can extend the RoleProvider class to handle your own authorization as described below. There are three pre-packaged role providers:

  • The WindowsTokenRoleProvider retrieves role information from the groups to which a user belongs, but works only with Windows authentication.
  • The AuthorizationStoreRoleProvider works with Microsoft Authorization Manager (AzMan) and can retrieve role information from an Active Directory or XML file.
  • The SqlRoleProvider retrieves authorization information from either a SQL Server Express database located in your App_Code directory or from tables in your SQL Server that you can create with the aspnet_regsql.exe command.
Implementing these providers is not complex, but is beyond the scope of this article and is already well documented (see the Related Resources in the left column for more information).

A Custom RoleProvider Example
Unfortunately, not all applications can use the built-in role providers. For example, what if you migrate an application from ASP.NET 1.1, or hold authorization information in Oracle or a custom data store?

If you want the benefits of using User.IsInRole() or declarative security, then you probably retrieve data from your data source on a Login page, store the roles in the authentication ticket, and add them as a new GenericPrincipal to HttpContext.Current.User in the Application_AuthenticateRequest event in your Global.asax file. If so, your Login page calls a function that looks like this:

private void SetAuthenticationTicket(string strUserName) { string strRoles = GetCommaDelimitedRolesFromDataSource(strUserName); FormsAuthenticationTicket tkt = new FormsAuthenticationTicket(1, strUserName, DateTime.Now, DateTime.Now.AddMinutes(20), false, strRoles); string cookiestr = FormsAuthentication.Encrypt(tkt); HttpCookie ck = new HttpCookie( FormsAuthentication.FormsCookieName, cookiestr); ck.Path = FormsAuthentication.FormsCookiePath; Response.Cookies.Add(ck); }

To link the above approach into the site map you must create a new class, inherit from RoleProvider, and override the GetRolesForUser() method. In your custom class the overridden GetRolesForUser() method will look nearly identical to the AuthenticateRequest event in your Global.asax file:

public class CustomRoleProvider : RoleProvider { ... public override string[] GetRolesForUser(string username) { // if the user is authenticated if (HttpContext.Current.User != null) { // retrieve the list of roles assigned during log in FormsIdentity id = ( FormsIdentity)HttpContext.Current.User.Identity; FormsAuthenticationTicket tkt = id.Ticket; HttpCookie authcookie = HttpContext.Current.Request.Cookies[ FormsAuthentication.FormsCookieName]; FormsAuthenticationTicket authTicket = (FormsAuthenticationTicket)FormsAuthentication.Decrypt( authcookie.Value); string[] astrRoles = authTicket.UserData.Split(','); return astrRoles; } return null; } }

The full CustomRoleProvider class is available in Listing 1 and in the downloadable code that accompanies this article.

Naturally, you need to tell ASP.NET about the existence of your CustomRoleProvider—and you do that in the Web.config file:

<roleManager enabled="true" defaultProvider="CustomRoleProvider"> <providers> <clear /> <add name="CustomRoleProvider" type="CustomRoleProvider" /> </providers> </roleManager>

Step 3: Configure Authorization Rules
The last step is to configure which roles have access to which pages or directories. The best approach involves using authorization allow and authorization deny statements in the Web.config file. The .NET Framework uses this information to automatically allow or deny all users' ability to access pages. What's great is that the SiteMapProvider will use the same information to decide which siteMapNodes the SiteMapDataSource should provide (assuming you have enabled securityTrimmings). Consequently, without writing a line of actual code, you can make a site's entire site navigation display only the pages to which users have access.

Setting the allow and deny configuration statements is the tricky part. If your authorization rules are simple you can create one directory for each role and allow and deny by directory using configuration settings such as:

<location path="AdminOnlyDir"> <system.Web> <authorization> <allow roles="AdminRole" /> <deny users="?" /> <deny roles="ReadOnlyRole" /> </authorization> </system.Web> </location> </configuration>

The users="?" statement refers to anonymous users. A users="*" statement would refer to all users. Thus, the preceding configuration would deny unauthenticated users, deny users in the ReadOnlyRole, and allow users from the AdminRole.

Now, what if someone created a new role called ClerksRole—and forgot to explicitly deny it access to the AdminOnlyDir? ASP.NET 2.0 assumes that all users have access to all directories and all pages by default. This is the equivalent of putting an <allow users="*" /> at the root of your site, because permissions cascade down directories. The result is that any new roles will have access to the AdminOnlyDir even though they shouldn't.

One could implement a more pessimistic approach to security by setting <deny users="*" /> at the root of the site, and then use allow statements to permit access to individual pages or directories. Here's an example:

<configuration> <system.Web> ... <authorization> <allow roles="AdminRole" /> <deny users="*" /> </authorization> ... </system.Web> <location path="ReadOnlyDir"> <system.Web> <authorization> <allow roles="ReadOnlyRole" /> </authorization> </system.Web> </location> </configuration>

After setting up the Web.config security authorization rules, you may find you would like to display a page that a user does not have access to.
This approach allows the AdminRole access to everything while denying all other roles access to everything unless they have been granted access explicitly. Specifically, the preceding configuration gives the AdminRole access to the AdminOnlyDir while the ReadOnlyRole—and any new roles—won't have access. Remember that order is important and deny statements should always follow allow statements.

Roles Attribute
After setting up the Web.config security authorization rules, you may find you would like to display a page that a user does not have access to. For example, this situation might occur for the default page in a site. Users should see it, but it will require them to login when they try to access it. You can override the security settings in Web.config with the roles attribute of the siteMapNode in the Web.sitemap file:

<?xml version="1.0" encoding="utf-8" ?> <siteMap xmlns= "http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" > <siteMapNode url="~/Default.aspx" title="Home" roles="*"> <siteMapNode url="~/SiteMap.aspx" title="Site Map" > </siteMapNode> </siteMap>

Assuming forms authentication is set up correctly, ASP.NET will redirect users to the login page if they are denied authorization to Default.aspx.

The roles="*" setting comes in handy in two additional circumstances: referencing external resources and increasing performance. You must set the roles attribute to * for pages that access external resources because ASP.NET cannot retrieve their authorization information from the Web.config. The same technique also increases performance because it bypasses the .NET Framework's need to check each page's permissions. This can help reduce the penalty of having more than 150 pages with security trimming enabled.

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



We have made updates to our Privacy Policy to reflect the implementation of the General Data Protection Regulation.
Thanks for your registration, follow us on our social networks to keep up-to-date