Home | Software | WebLog | Contact | Wish List

Extending Asp.NET 2.0 (beta) security

The current implementation of ASP.NET 2.0's security is great and I have fallen in love with it, but it's still too limited. I will show you how to extend ASP.NET 2.0's security using a custom HTTP Module and your existing Web.sitemap.

Assumptions

This article assumes you are already familiar with ASP.NET 2.0's built in user and role based security. This article also assumes you are familiar with ASP.NET 2.0's Web.sitemap. Familiarity with C# is a bonus, but not required.

I'm also assuming your site is already setup and using forms authentication.

The Problem

ASP.NET 2.0 gives you this great tool to make securing directories easy, and it does a great job.

asp.net 2.0 web administration security tool


You are also given a Web.sitemap file that allows you to restrict access by roles.

<?xml version="1.0" encoding="utf-8" ?> <siteMap> <siteMapNode url="Default.aspx" title="Home" description="This is the default page"> <siteMapNode url="Webform1.aspx" title="Webform1" description=""> <siteMapNode url="Marketing/SecureFile.aspx" title="Secure File 1" description="" roles="Marketing" /> </siteMapNode> <siteMapNode url="" title="Marketing"> <siteMapNode url="SecureFile.aspx" title="Secure File 2" description="" roles="Marketing" /> </siteMapNode> <siteMapNode url="" title="Links"> <siteMapNode url="http://google.com" title="Google" description="Google" roles="" target="_blank" /> <siteMapNode url="http://yahoo.com" title="Yahoo!" description="Yahoo!" roles="" target="_blank" /> <siteMapNode url="http://microsoft.com" title="Microsoft" description="Microsoft" roles="" target="_blank" /> </siteMapNode> </siteMapNode> </siteMap>

Unfortunately only the ASP.NET 2.0 site navigation controls use Web.sitemap roles attribute to determine whether or not they should display the link (assuming, I rolled my own and don't use the built in controls - I'll test later).

If you go directly to the URL of a file in the Web.sitemap that has roles, it will not be restricted by those roles (though, it'd be nice if it did).

The Solution

The solution is to implement your own Http Module and determine the allow / deny security based upon the rolls attribute in the Web.sitemap file. This works in conjunction with ASP.NET 2.0's built in security.

Add the following code to your web.config file inside the <system.web> node. This will allow you to intercept requests to all pages before processing, allowing you to force the Web.sitemap security.

<httpModules> <add name="SecurityHttpModule" type="Joel.Net.SecurityHttpModule" /> </httpModules>

Here's the code that performs all the magic...

using System; using System.Web; using System.Web.Security; namespace Joel.Net { /// <summary>Security Http Module</summary> public class SecurityHttpModule : IHttpModule { #region Properties public bool IsReusable { get { return true; } } #endregion public SecurityHttpModule() { } /// <summary>Initializes a module and prepares it to handle requests.</summary> /// <param name="context">An <see cref="T:System.Web.HttpApplication" /> that provides access to the methods, properties, and events common to all application objects within an ASP.NET application </param> public void Init(System.Web.HttpApplication context) { context.AuthenticateRequest += new EventHandler(this.AuthenticateRequest); } /// <summary>Occurs when a security module has established the identity of the user.</summary> private void AuthenticateRequest(Object sender, EventArgs e) { HttpApplication Application = (HttpApplication) sender; HttpRequest Request = Application.Context.Request; HttpResponse Response = Application.Context.Response; bool allow = false; // Default is not not allow // Exit if we're on login.aspx, not authenticated, or no siteMapNode exists. if (Request.Url.AbsolutePath.ToLower() == FormsAuthentication.LoginUrl.ToLower()) return; if (Application.Context.User == null) Response.Redirect(FormsAuthentication.LoginUrl); if (SiteMap.CurrentNode == null) return; // Check if user is in roles if (SiteMap.CurrentNode.Roles.Count == 0) { allow = true; // No Roles found, so we allow. } else { // Loop through each role and check to see if user is in it. foreach (string role in SiteMap.CurrentNode.Roles) { if (Roles.IsUserInRole(role)) { allow = true; break; } } } // Do we deny? if (allow == false) Response.Redirect(FormsAuthentication.LoginUrl); } /// <summary>Disposes of the resources (other than memory) used by the module that implements <see cref="T:System.Web.IHttpModule" />.</summary> public void Dispose() { } } }

Inside the Init function, we tie an event handler to the AuthenticateRequest event of the Context object. This allows our AuthenticateRequest to be called every time the Context.AuthenticateRequest event is raised.

The first block of code sets up all the objects we'll need, Application, Request, Response in addition to creating an 'allow' boolean value to determine if they pass or fail the authentication in the Web.sitemap.

The following block will exit, or allow access (allowing access only means OUR custom HTTP module permits it, it still has to pass ASP.NET 2.0's security restrictions), under 3 conditions. #1 if we are at the Login page (we can't restrict the login page - how will they log in!?). #2 if the user is not logged in (if they're not logged in, we cannot get their roles; therefore we let ASP.NET 2.0's built in security handle this request). #3 there is no entry in the Web.sitemap for the current URL.

Next we loop through each role in the current siteMapNode and check to see if the user exists in that role. If they are in the role, we set the 'allow' variable to true and break the loop.

Lastly we test our 'allow' variable to see if we should let them pass through or force them to the login page.

Summary

We created an easy way (only approx 55 lines of code) to extend ASP.NET 2.0's security, while still allowing ASP.NET 2.0 to control the security. When our HTTP Module cannot handle a request (ex: user not logged in, page not in Web.sitemap), we simply pass the request off to the ASP.NET 2.0, and let it handle the security.

Resources


post reply to this comment Comment by ha9953 [Dec 04, 2005 @ 6:04 PM]
After implemented this class, when i start up my website its jump direct to login.aspx? I haven't defined any roles yet and i want visitors to start at the default.aspx?
post reply to this comment Comment by jonathan [Apr 17, 2006 @ 10:41 PM]
To answer ha9953 assign roles="*" for all roels... "" might be causing a problem.

About the post... why use an httpmodule, and not the begin request method of global.asa? which is already in place?

Not really connected.... in our case we made a custom site map provider, and extended the IsAutheticated so that in my base page i can do soemthing like SiteMap.Provider.IsAccesibleToUser() and this allows me to make sure the same method that is doing SecurityTrimming is also in cahrge of redirecting to login.
post reply to this comment Comment by jonathan [Apr 17, 2006 @ 10:42 PM]
That is very cool the way comments are added to this site. awsome.
post reply to this comment Comment by Martha [May 25, 2006 @ 6:41 PM]
Can you convert your code into VB?

"Extending Asp.NET 2.0 (beta) security"
post reply to this comment Comment by M Naveen Kumar [Jun 14, 2007 @ 2:52 AM]
Hi Joel,
i went through the ASP.NET 2.0 Security sample what you posted, but i want to know the implementation to where to call in the and how to make it work. Please do send me over the sample how to implement in my application
post reply to this comment Comment by MikeS [Sep 23, 2007 @ 7:57 PM]
What if securityTrimmingEnabled="true" and Roles are defined at the SiteMapNode.ParentNode level.

CurrentNode Roles.Count==0 and CurrentNode ParentNode==null and SiteMapProvider.IsAccessibleToUser==true.

So I am not seeing this code work when roles are specified around a group of menu items and so roles will have to placed on each and every node.
post reply to this comment Comment by Noel [Sep 02, 2008 @ 1:20 AM]
This code will never work -- because when SecurityTrimmingEnabled = true, then SiteMap.CurrentNode will always be NULL.
post reply to this comment Comment by ZK@Web Marketing Blog [May 23, 2009 @ 8:12 PM]
There is no glamour in building secure ASP.NET web applications. The vast majority of developers I’ve met would much rather focus on building new & flashy features that can impress their managers and end-users. Even though security can usually take a backseat during the initial stages of development, it usually comes back to bite you when it’s least expected.
post reply to this comment Comment by seo techniques [Jun 02, 2009 @ 10:33 PM]
The SQL Database side (SqlMembershipProvider) also includes SqlRoleProvider and SqlProfileProvider. The SQL side, together with the (application side) System.Security and System.Web.Security namespaces makes up the bulk of the Membership Role Provider security features in ASP.NET 2.0. As a side note the “Profile” ASP.Net objects are for tracking user options and personalization’s, such as web parts and user web page preferences..
post reply to this comment Comment by Keno umsonst spielen [Jun 02, 2009 @ 10:41 PM]
I modified AuthFilt to autheticate a user via Oblix/LDAP. After authentication, a user could access his inbox on a single server. In front-end and back-end environment for Exchange, a cookie should be passed from front-end to back-end after a successful authencation. Do you have any tips to generate a Exchange-compatible IIS cookie named sessionId. I can use InternetSetCookie or ASP Session object to generate a cookie. But what's in the cookie
post reply to this comment Comment by plumber sydney [Sep 10, 2009 @ 9:18 PM]
I would like to thank you for the efforts you have made in writing this article. I mostly agree with you most of the point I have subscribed the feed and looking forward for the followup subscriptions
post reply to this comment Comment by ppo plans [Jan 30, 2010 @ 1:19 AM]
The ASP.NET 2.0 site navigation system is built on a powerful and flexible architecture that has been designed to be extensible
Leave Your Comment
Name:
Email:  (gravatar enabled)
URL:
Comment:
or Cancel