Introduction
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.
This article will show you how to secure individual pages, each with independent permissions, without the need to add redundant data into your web.config.
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.
You are also given a Web.sitemap file that allows you to restrict access by roles.
="1.0" ="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 the 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 roles
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 {
public class SecurityHttpModule : IHttpModule {
public SecurityHttpModule() { }
public void Init(System.Web.HttpApplication context) {
context.AuthenticateRequest += new
EventHandler(this.AuthenticateRequest);
}
private void AuthenticateRequest(Object sender, EventArgs e) {
HttpApplication Application = (HttpApplication)sender;
HttpRequest Request = Application.Context.Request;
HttpResponse Response = Application.Context.Response;
bool allow = false;
if (Request.Url.AbsolutePath.ToLower() ==
FormsAuthentication.LoginUrl.ToLower()) return;
if (Application.Context.User == null)
Response.Redirect(FormsAuthentication.LoginUrl);
if (SiteMap.CurrentNode == null) return;
if (SiteMap.CurrentNode.Roles.Count == 0) {
allow = true;
} else {
foreach (string role in SiteMap.CurrentNode.Roles) {
if (Roles.IsUserInRole(role)) { allow = true; break; }
}
}
if (allow == false)
Response.Redirect(FormsAuthentication.LoginUrl);
}
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 three conditions:
- if we are at the Login page (we can't restrict the login page - how will they log in!?),
- 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),
- 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 (e.g.: user not logged in, page not in Web.sitemap), we simply pass the request off to ASP.NET 2.0 and let it handle the security.
Resources