Article Index

HttpModule to allow a custom error page for 401.2 Access Denied in ASP.NET

As you know, the customErrors section of web.config allows you to define your own pages to display to the user when an error occurs. It allows for a default page to display when any unhandled error occurs, and it also allows you to specify different pages to display depending on the HTTP status code.

I like to take advantage of ASP.NET's declarative security model by defining the users and roles that are authorized to execute different parts of my website. For example, I can limit access to a page named Protected.aspx by declaring that all users must be a meber of the VerySpecialUsers group. This can be accomplished by adding the following section to my web.config:

  <location path="Protected.aspx">

    <system.web>

      <authorization>

        <allow roles="VerySpecialUsers"/>

        <deny users="*"/>

      </authorization>

    </system.web>

  </location>


I like this model as it allows me to easily modify the access restrictions for a page, and since it uses ASP.NET's Role Provider framework, it is not tied to any implementation about how role membership is determined. By default in a Windows Authentication/NTLM scenario, the roles map to the group memberships of your Windows login account (Administrators, Power Users, Users, etc). But you can very easily create your own Role Provider that retrieves group membership information from your own data store.

When a user does not meet the authorization requirements to visit a certain page, ASP.NET will return a 401.2 Access Denied error. The page looks like the standard yellow screen of death (YSOD) that ASP.NET returns for all errors.The customErrors section of web.config is supposed to let you provide a friendlier response to your users so they never have to see the YSOD. You would think that all you need to do is define a page for the 401 status code in customErrors and all would be good. You would be wrong. You can even try changing the custom error page defined in your IIS settings, and it still won't work.

The newsgroups have countless unanswered posts about this issue. Fortunately, my most recent attempt to solve this problem finally found some answers. The first one I found, and the one I use for my solution, was posted by John "iSpeakGeek" on ASPFree forums. I have since discovered a CodeProject article by George Mamaladze, and even a (somewhat related) Microsoft KB article. Hopefully this post will help improve the search results for other people struggling with this issue.

The key to the solution is to intercept the EndRequest event of the page lifecycle, check for a 401 status code, and then execute your custom page. The Microsoft article suggests intercepting the Error event and checking for an UnauthroizedAccessException, but that didn't work for my scenario. It may only apply when using impersonation, as the article describes. I need to do more testing and will possibly modify my solution to cover more scenarios.

Since I need this functionality on pretty much every website I make, I created an HttpModule to encapsulate the logic. The nice thing about my module approach is that you simply register the module in your web.config, and it will automatically respect the settings you define in your customErrors section. There is no hardcoded dependence on a specific error page or need for a custom configuration section.

<httpModules>

  <add name="CustomAccessDenied" type="FlimFlan.CustomAccessDenied, FlimFlan.CustomAccessDenied"/>

</httpModules>

<customErrors mode="On" defaultRedirect="Error.htm">

  <error statusCode="401" redirect="AccessDenied.aspx"/>

</customErrors>


I've packaged up the full source and a sample website. Use it and change it as you wish. If you don't care about the source, just grab the DLL from the DemoWeb\Bin folder and put it in your own website's Bin folder.

I can't claim it is a general purpose solution yet, as I have not tested it with all possible authentication/authorization settings. If you find a scenario that doesn't work, please let me know and I'll see if I can address it.

Download FlimFlan.CustomAccessDenied

Comments

Server.Transfer("~/Errors/401.htm",true) also works.
CW - December 05, 2007 09:27am
CW - I'm not sure what you are referring to. Are you proposing an alternate solution to the problem I described? I don't see how that line of code fits this scenario.
Joshua Flanagan - December 05, 2007 07:19pm
Hi,

I want my .Net app to be accessible to a group only.For this I entered the following in web.config

<authorization>
<allow roles="Group name" />

<deny users="*" />
</authorization>

and want all users who are not member of this group redirected to customised page NoAccess.htm

<customErrors mode="On" defaultRedirect="Error.aspx" >
<error statusCode="401" redirect="NoAccess.htm" />
</customErrors>


I am using Windows authetication,have added the group in the local users and groups.

When I run the set up it prompts for login credentials which when entered gives error message 401.2 Access is denied

Please help.
anoop - January 31, 2008 08:06am
Hi,

This is great thanks. The only problem I have now is that my error page is in the root, and I have an image in it which is in the images folder. \ I cannot get the image to display.

I have a folder of the root called Dashboards, which is secured.

Root
CustomError.aspx
--Images
--Dashboards

When I look at the path for the image, it has the path Dashboards/images/error.jpg

and so doesnt display?

This is the markup which works fine when you view the page on its own as the page is in the root.

<img src="images/error.jpg">

When your HTTP handler returns my custom error page, the image has the path:

Dshboards/images/error.jpg (the error was raised by a page in the Dashboards folder)

Any thoughts?
Stephen Mackenzie - February 08, 2008 06:00am
to Stephen Mackenzie - you should try path starting from root of app...use "~/images/error.jpg" insted of "images/error.jpg"

to Joshua Flanagan - thank verry much for this solution...works fine for me
martin - June 10, 2008 06:39am
hi. I can run your sample fine. however when I try to run your sample via my own web.config / site it won't run.
web.config
<httpModules>
\ <add name="CustomAccessDenied" type="FlimFlan.CustomAccessDenied, FlimFlan.CustomAccessDenied"/>
<add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</httpModules>

<authentication mode="Windows"/>
<customErrors mode="RemoteOnly" defaultRedirect="Error.htm">
<error statusCode="401" redirect="~/errorpages/denied.aspx"/>
</customErrors>

<location path="Reporting/default.aspx">
<system.web>
<authorization>
\ <!--<allow roles="BUILTIN\Administrators,DomainName\Manager" />-->
<allow roles="Human_resources" />
<deny users="*" />
</authorization>
</system.web>
\ </location>
when visiting hrapps/reporting/default.aspx then page will only redirect to error.htm

dll file was imported from demoweb vis VS2005
any thoughts on this? from what I can see, the setup is fairly similar.I am running ajax if that matters.
mac
mac - July 15, 2008 02:02pm
i think I have discovered the issue.. however unable to determine the fix. when tested localy on the server (win2003) the dll is not getting loaded.

Error
Could not load file or assembly 'FlimFlan.CustomAccessDenied' or one of its dependencies.

I have the following
<httpModules>
<add name="CustomAccessDenied" type="FlimFlan.CustomAccessDenied, FlimFlan.CustomAccessDenied"/>
...other modules
</httpModules>

i registered through and it appears to be loaded in correctly.
mac - July 15, 2008 03:16pm
Mac - I don't think I did any testing with the new AJAX mappings. Try removing (or commenting out) the ScriptModule and see if that resolves the issue. If it does, then we know there is a compatibility issue between my solution and Microsoft's AJAX stuff. If you do discover an issue and figure out how to change my sample to make it work, I'll post the code.
Joshua Flanagan - July 15, 2008 03:22pm
Joshua,

I don't think this solution will work as presented when using Forms authentication. The reason, is because the FormsAuthenticationModule already intercepts the 401 and performs a redirect to the "login page".

Many posts out there are looking for a solution to redirect to an unauthorized page instead of the login page. I think this is the best approach, but would require clearing all of the http modules and redeclaring them.

Let me know what you think.

Will
Will - October 14, 2008 12:45pm