Click here to Skip to main content
15,867,594 members
Articles / Web Development / ASP.NET
Article

Extending ASP.NET 2.0 security

Rate me:
Please Sign up or sign in to vote.
2.84/5 (17 votes)
23 May 20063 min read 193.5K   66   35
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 Handler and your existing Web.sitemap.

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.

XML
<?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 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.

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

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

C#
using System;
using System.Web;
using System.Web.Security;

namespace Joel.Net {

    /// <summary>Security Http Module</summary>
    public class SecurityHttpModule : IHttpModule {

        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 three 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 (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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
Joel Thoms has been tinkering around with computers and programming ever since he got his first C= 64 at the age of 5. He has been programming professionally since 1996.

He mostly works with ASP.NET, C# and VB.NET, but is also fluent with ASP, VB6 and JavaScript. Has dabbled a little with C++, Perl, Cobol and a few other languages but doesn't have much use for them.

Joel is now the lead developer for Host Collective the company responsible for discountasp.net (one of the leaders in the asp.net hosting industry)

Comments and Discussions

 
QuestionWhat about the visibility of the nodes Pin
foluis13-Oct-11 12:37
foluis13-Oct-11 12:37 
GeneralMore Benefits to This Solution Pin
sisdog7-Feb-09 18:30
sisdog7-Feb-09 18:30 
GeneralThank you Pin
DuraiPrasanna11-Jun-08 8:09
DuraiPrasanna11-Jun-08 8:09 
Generalsource code download Pin
kumarrajt30-Sep-07 21:30
kumarrajt30-Sep-07 21:30 
GeneralVB Conversion Pin
timmyt85111-May-07 13:11
timmyt85111-May-07 13:11 
QuestionGetting naught but syntax and ms JScript errors [modified] Pin
mrpimp21-Feb-07 3:51
mrpimp21-Feb-07 3:51 
AnswerRe: Getting naught but syntax and ms JScript errors Pin
mrpimp21-Feb-07 5:39
mrpimp21-Feb-07 5:39 
QuestionWhat is the use? Pin
juleshop24-Mar-06 3:20
juleshop24-Mar-06 3:20 
AnswerRe: What is the use? Pin
Joel Thoms22-May-06 11:55
Joel Thoms22-May-06 11:55 
GeneralRe: What is the use? Pin
dbaier23-May-06 22:15
dbaier23-May-06 22:15 
GeneralRe: What is the use? [modified] Pin
Joel Thoms24-May-06 12:46
Joel Thoms24-May-06 12:46 
GeneralUse the Web.config to accomplish this Pin
tketter8-Mar-06 8:29
tketter8-Mar-06 8:29 
GeneralRe: Use the Web.config to accomplish this Pin
HurricaneGordon3-Apr-06 20:55
HurricaneGordon3-Apr-06 20:55 
GeneralRe: Use the Web.config to accomplish this [modified] Pin
Joel Thoms22-May-06 11:40
Joel Thoms22-May-06 11:40 
GeneralNo new functionality Pin
Francisco Jose Peredo Noguez7-Dec-05 12:07
Francisco Jose Peredo Noguez7-Dec-05 12:07 
GeneralRe: No new functionality Pin
Joel Thoms22-May-06 11:42
Joel Thoms22-May-06 11:42 
GeneralRe: No new functionality Pin
_henke_2-Dec-06 4:34
_henke_2-Dec-06 4:34 
GeneralWhen SecurityTrimmingEnabled is true, SiteMap.CurrentNode returns null for nodes if user is not authorized Pin
Diego Vega17-Aug-05 10:01
Diego Vega17-Aug-05 10:01 
The CurrentNode method of the default SiteMapProvider calls on internal method ReturnNodeIfAccessible() in order to filter nodes based on roles and the
SecurityTrimmingEnabled property.

So, I think the clause "if (SiteMap.CurrentNode == null) return;" is not safe. You are leaving it in hands of the default ASP.NET security, when "null" is telling you that the access should be denied.

I think it would be better to do it like this:

if (SiteMap.CurrentNode == null) {
if (SiteMap.Provider.SecurityTrimmingEnabled) {
Response.Redirect(FormsAuthentication.LoginUrl);
}
else {
return;
}
}

Or even better, refactor your code to set the allow flag on all conditions and only check in the end if you have to call Response.Redirect.
GeneralRe: When SecurityTrimmingEnabled is true, SiteMap.CurrentNode returns null for nodes if user is not authorized (correction) Pin
Diego Vega17-Aug-05 10:55
Diego Vega17-Aug-05 10:55 
GeneralRe: When SecurityTrimmingEnabled is true, SiteMap.CurrentNode returns null for nodes if user is not authorized Pin
Joel Thoms22-May-06 11:59
Joel Thoms22-May-06 11:59 
GeneralRe: When SecurityTrimmingEnabled is true, SiteMap.CurrentNode returns null for nodes if user is not authorized Pin
Diego Vega24-May-06 17:45
Diego Vega24-May-06 17:45 
GeneralRe: When SecurityTrimmingEnabled is true, SiteMap.CurrentNode returns null for nodes if user is not authorized Pin
Joel Thoms25-May-06 14:23
Joel Thoms25-May-06 14:23 
GeneralInbuilt security sufficient enough to me Pin
5-Aug-05 11:04
suss5-Aug-05 11:04 
Generalrepost: Now proper formatted Pin
dominick.baier19-May-05 2:02
dominick.baier19-May-05 2:02 
GeneralRe: repost: Now proper formatted Pin
Joel Thoms22-May-06 11:47
Joel Thoms22-May-06 11:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.