HttpModule to allow a custom error page for 401.2 Access Denied in ASP.NETAs 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:
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.
<add name="CustomAccessDenied" type="FlimFlan.CustomAccessDenied, FlimFlan.CustomAccessDenied"/>
<customErrors mode="On" defaultRedirect="Error.htm">
<error statusCode="401" redirect="AccessDenied.aspx"/>
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.