HOWTO: Protect non-.NET content

This is a frequently asked question - how to implement custom authentication on IIS. I am going to ramble a bit about the whole subject because this is a little pet peeve of mine...


Hi David,

I am an avid lurker on your Blog and our company posts some of your HOWTO's on out internal web. Thank you for all the good info.

We are running many websites in a server farm. All of the webs feature a mix of classic ASP and .NET content in C#. We are currently using C++ ISAPI filters to protect classic ASP content from miscreants. Authentication is done either by the ISAPI filters or by the .NET content. Credentials are checked by the ISAPI filters after authentication. We are migrating to IIS 6.0 on Windows Server 2003 but are not completely converted from IIS 5.0 on Windows 2000 Server.

Our problem is that the management of the ISAPI filters is taking too many resources. This is mainly due to the several versions of the filters (which were written in-house) and their configuration options. These options mostly have to do with DB connection strings and passthru mechanisms for content such as images, stylesheets and script files.

I would like to know if it is possible to protect clasic ASP and other non-.NET content soley with an IIS and/or .NET mechanism. Particularly IISWebFile, IISWebDirectory, IISWebVirtualDir, etc. in conjunction with web.config and machine.config settings. Can you help shed some light on this issue? Can you tell us of a way to protect web content without requiring ISAPI filters?


Thanks for using my posts - it is exactly what I am hoping to do - provide digested information and insights that help you do something new/better with less time/money/resources. 🙂 But first, I have to take care of a little pet peeve of mine when it comes to this subject...


By far the easiest and most secure mechanism to protect any sort of content on IIS is to use its built-in authentication protocols such as Basic/NTLM/Kerberos/Digest/Passport and built-in authorization model using NT ACLs on the NTFS FileSystem. I believe that it offers a good combination of simplicity to configure and use, secure, well-tested, and well-integrated. Active Directory adds scalable user identity management and intra-machine trust, which allows security concepts like constrained delegation to enable secure "forwarding" of user credentials to access remote resources. It is probably the most complete and commonly available security system which integrates across multiple servers seamlessly and secures both the user on the client and remote servers involved, and you get it when you choose Windows.

Unfortunately, most users do not understand security other than "I want it to be cheap, simple, and secure". Combined with the problem that Active Directory is easily misunderstood and hard to configure perfectly (security can be like that - usually, there are a million wrong ways to do something and only one secure, correct way), users often end up abandoning Active Directory for one bad reason or another. Instead, they choose to implement their own little simple, custom authentication/authorization solution that they understand and fit it into their environment... and get off to the wrong foot. Why? Because more likely than not, these solutions are either insecure in some fundamental way(s) from a security perspective (the user just does not know it... yet... since they rarely have the security/cryptography training), and even if the solution is secure, it basically ends up being a private implementation of the exact same publicly designed protocol that IIS already supports.

So, I question why anyone should ever re-invent the wheel when it comes to authentication/authorization, especially when your solution is usually less verified, less hardened, and probably built with less domain knowledege?


... ok, now that I have gotten that off of my chest, let me answer your specific question.

Short Answer

If you are looking for a custom authentication solution which works with IIS5 and IIS6, applies to static files, ASP, and ASP.Net content, and does not involve ISAPI Filters, then there is no solution. The reason is simple:

  • IIS only supports three extensibility mechanisms - CGI, ISAPI Extension, and ISAPI Filter (we changed this in IIS7 by adding a new API which allows both native and managed code to be used, but that is not the point of discussion here...)
  • Prior to IIS6 only ISAPI Filter can "filter" a request without owning it
  • Custom authentication/authorization is a request processing phase, not response generation - thus it requires the ability to "filter" a request without owning it

What this means is that:

  • With an ISAPI Filter, custom authentication is always possible on any IIS version
  • It is architecturally impossible to apply ASP.Net Forms authentication on ASP content on IIS5 because ASP.Net ends up owning generating the response for ASP content, which does not work (ASP.DLL should process ASP pages).
  • It is architecturally possible on IIS6 to apply ASP.Net 2.0 Forms authentication on ASP content due to new IIS functionality allowing ISAPI Extension to "filter" a request without owning it, and ASP.Net 2.0 added support for this new functionality. To be clear, this does not work with ASP.Net 2.0 on IIS5 or ASP.Net 1.1 or IIS6 - they are both missing the necessary support.


Ok, I am going to be brutally honest here. Please do not take it personally - I am just looking at the data and openly musing about it:

  • I do not think your problem has anything to do with ISAPI Filter management costing too much resources
  • I do not think that excluding solutions based on technology makes sense here

Why? Well, I think your real problem has to do with poorly designed ISAPI Filters that do not interact and integrate with each other cohesively to provide the comprehensive custom authentication/authorization story that you need. It is not surprising given my pet-peeve rant earlier - most people in this world should not be writing such systems. It is usually a one-off hack of some sort, and hacks beget hacks, etc... which gets ugly and complicated.

Also, deciding on solutions based on implementation technology makes no sense to me. Implementation technology does not build security; knowledgeable people do. If you cannot build the security system with ISAPI Filter, why would you be able to do it correctly with .Net or even re-leverage the built-in IIS mechanism? In other words, how does .Net or avoiding ISAPI Filter improve your situation? If you can give me the answer to that, I can probably tell you how to do it correctly with ISAPI Filter... and if you say that you have more experienced .Net developers than C++, then I will tell you that you are still doomed -- what you need are developers that understand security theory, and understanding of API is not a valid constraint.

In other words, I want to help you solve your fundamental problem. I do not believe your problem is caused by ISAPI Filters but rather by poor understanding and design, and IIS/.Net is not a solution to poor design. Learning how to build with better design (or buying implementations of better design) are the ways to solve your problem. I really do not understand what you desire in your ideal system nor what you want to do, so I cannot give any more concrete advice.

I can tell you that for a solution that has to cross over between IIS5 and IIS6 and has to protect static, ASP, and ASP.Net content, an ISAPI Filter is the only way. On IIS6 with ASP.Net 2.0, it is possible to impose ASP.Net Forms Authentication on any content type, but that requires an understanding of how IIS and ASP.Net interact when it comes to security. I have planned blog entries on this topic (rather, I need to finish up the series that I started a couple months ago...)

Once again, thanks for the encouragement.


Comments (27)

  1. Joe Chung says:

    You say this about Active Directory:

    "Active Directory is easily misunderstood and hard to configure perfectly."

    And then you get onto a soapbox and complain about people not using Active Directory, wanting to do it "quickly, fastly, and cheaply," and either doing it wrong or doing it right but in a completely site-specific way.

    Maybe the problem is that Active Directory is easily misunderstood and hard to configure perfectly?

  2. David Wang says:

    Actually, I think that configuring security correctly requires a high level of sophistication, and Active Directory belongs in that bunch (in relation to Kerberos).

    Some people would argue that success is defined by when the system makes the right thing easy and the wrong thing hard. So, if the right thing is hard, that is a failure of the system.

    I do not believe that approach applies to security because I do not believe security can be made simple. Thus I believe in learning the background concepts and applying it to the system at hand. Not give up and do your own thing.

    Thus, I disagree with the users that try to re-invent the wheel because they do not want to learn basic security to implement it correctly.


  3. SharpDog says:

    Hi David,

    The reason we do not want to use ISAPI filters involves procedural and responsibility/authority issues within our corporate management structure and does not involve any technical issues. Non development IT personnel within our company are responsible for installing and configuring ISAP filters on the several hundreds of websites within our company. This is because installing new versions of ISAPI filters on production web servers requires an IIS restart or frequently a reboot of the server. Also, our desired authentication mode, anaonymous aith Integrated windows authentication is very hard to configure with Microsoft’s FrontPage. FrontPage, for some reason is much simpler to configure with Basic authentication which we would prefer not to use. However, this extra configuration often results in excessive configuration time and rebooting of production web servers. This leads to production downtime. Developers and other content-management personnel have authority and responsibility over ASP and ASP.NET content including configuration files and binaries. It is for these reasons that we prefer a pure .NET solution that does not involve ISAPI. BTW, we are trying to remove FrontPage as part of our publishing process but we are not there yet.

  4. SharpDog says:

    Hi David,

    It’s me again. A good solution for our issues would not have to support any version of IIS prior to IIS 6.0. This is because we already have a plan in place to move all of our servers to an IIS 6.0 / Windows 2003 server environment and this is progressing well.

  5. David Wang says:

    SharpDog – Ah, I see. The problem is not with ISAPI Filters but rather with the particular implementations you have been saddled with and its effects of process/procedures and responsibility/authority.

    For example, the "pure .NET solution" of ASP.NET is basically just a specific ISAPI Filter/Extension implementation which happens to use the CLR as an extensibility model and uses Windows FileChange Notification to implement a distributed configuration system.

    Besides, FrontPage is not a useful IIS Configuration mechanism; as a product that came from Unix years ago, it has a very different audience and assumptions about server configuration…

    This is why I focus more on the design and less of the technology used – because it is possible to build what you need with ISAPI and has already been done. 🙂

    Since you are going ASP.NET 2.0 and IIS6, then you should consider configure ASPNET_ISAPI.DLL as a wildcard application mapping so that it filters all requests and allow you to apply ASP.Net behaviors onto requests handled by IIS. The thing you need to watch out is that wildcard application mapping execution happens AFTER IIS has done authentication, authorization, URL/Resource Mapping, etc so you can have duplicate functionality of IIS and ASP.Net running on the same request. You will have to reconcile it one way or another, depending on your needs.


  6. SharpDog says:

    Thanx for the great answer David, I didn’t think of that. Just one more question: How does ASP.NET tell IIS that it did not handle the request so it can be passed along to the next handler? Do I do this with a .NET IHttpModule? What do you reccommend?

  7. David Wang says:

    SharpDog – Actually, ASP.NET 2.0 does this automatically with its DefaultHttpHandler, so it should just work by default.

    The wildcard application mapping in IIS6 tells it to route all requests to ASPNET_ISAPI.DLL. The DefaultHttpHandler in ASP.Net tells it to re-route all non-handled requests back to IIS6.

    This functionality is ASP.NET 2.0 and IIS6 specific and is the closest you can get to IIS/ASP.Net integration prior to IIS7, where we make it integrated in a perfectly extensible and performant way.


  8. David Wang says:

    I picked up a good tip from Steve who has a great idea – publicly available ISAPI modules with source and encouraging community/user participation. I have not done a review of the open source code yet; I will likely do so when I get a chance.

    I think it would be nifty if the project goes on something like SourceForge – who says that Open Source has to be on Linux?


    —– >8 ———- >8 ———- >8 ———- >8 ———- >8 —–

    Hi David,

    I recently released mod_auth for IIS ( free and as opensource and I’m searching for people that can help me make the filter known. I saw that you have an article about how to protect not .net content and I was wondering if you can add my site to your article in exchange I can add a link to your site from a thanks to site (coming this week)

    Thanks for your help

  9. Mark says:

    In your response to SharpDog on September 06, 2005 5:42 AM, you mentioned something that interests me. Specifically you suggest we consider using the “ASPNET_ISAPI.DLL as a wildcard application mapping so that it filters all requests and allow you to apply ASP.Net behaviors onto requests handled by IIS”

    I am looking for more information about the request processing sequence when static content is requested while the ASPNET_ISAPI.DLL is configured as a wildcard application mapping. I don’t quite see how this would fit into the “HOWTO: IIS6 Request Processing Basics” document you created.

    More specifically, for protecting ASP.Net content I have developed an HttpModule that works quite nicely. If I were to use the ‘ASPNET_ISAPI.DLL’ as a wildcard application mapping, would it then be possible for me to use the same or similar HttpModule to protect static files and ASP? Given that I am using ASP.NET 2.0 and IIS6, I would assume that I would just need to place an appropriate Web.config file in the same directory as the content to be protected?

    Would there be any reason not to do this? I think I’m missing part of the puzzle.


  10. David Wang says:

    Mark – Yup, in the scenario where you are only running ASP.Net 2.0 on IIS6, it is remarkably easy to filter requests with ASP.Net modules/handlers and have them executed by IIS.

    The other blog entry Mark references is:

    How to configure:

    1. At the applicable metabase node, configure wildcard application mapping for ASP.Net ISAPI, making sure to UNCHECK the "Verify that file exists" checkbox.

    2. Make sure that the ASP.Net Handler that handles the request eventually calls HSE_REQ_EXEC_URL underneath the covers (System.Web.DefaultHttpHandler would do this).

    What is going on is this:

    1. The request for /MyASPPage.asp is processed by IIS6 according to my referenced blog entry.

    2. The ASP.Net ISAPI DLL configured as wildcard application mapping gets first crack at handling the request

    3. ASP.Net runs through its pipeline and executes HttpModules, and eventually an HttpHandler "handles" the request. This is where your Forms auth would kick in to protect "something" which happens to be an ASP page.

    4. Since you want to "Filter" the request, you obviously want the HttpHandler which handles the request to eventually call HSE_REQ_EXEC_URL to pass the request back to IIS. System.Web.DefaultHttpHandler does this by default to pass all non-ASP.Net handled requests back to IIS.

    5. Assuming Forms auth succeeded earlier and System.Web.DefaultHttpHandler basically told IIS "I’m not handling the request", IIS proceeds to continue processing /MyASPPage.asp and eventually chooses the .asp ScriptMap and invokes ASP.DLL ISAPI to handle generating the response.

    Voila. You now have ASP.Net HttpModule and HttpHandler filtering an otherwise non-ASP.Net content.

    Of course, the downside to all of this is that you now inject managed code execution into EVERY SINGLE REQUEST, including the static file.


  11. Mark says:

    David – First off, thank you very much.

    But now I have a follow up question along the same train of thought. Given that I now have the ‘ASPNET_ISAPI.DLL’ installed as a wildcard application mapping, and an HttpModule configured to execute on requests for classic ASP content… Would there be any way to pass on bits of information from the HttpModule to the ASP code that will eventually be processed by the ‘asp.dll’?

    It would be really easy if I were able to inject a few custom headers into the request itself. But the collection of headers is read-only when accessed via the HttpModule, and obviously classic ASP code would not have access to the .Net Session or request context.

    I’m beginning to think that this is basically impossible outside of an ISAPI filter/extension. Is there some other .Net technology besides HttpModules I should be looking into?

  12. Mark says:

    I believe I have found the solution to my problem:

    I ran across a blog over at: ‘’:

    Under a blog topic of “Protecting non-ASP.NET resources with ASP.NET 2.0”


    It says:

    “If you need more control, you can derive from DefaultHttpHandler and customize its behaviour. If you override the OverrideExecuteUrlPath method, you can modify the request path that gets handed back to IIS. You can also add new HTTP header to the request by adding entries to the ExecuteUrlHeader collection. This enables you e.g. to protect a classic ASP application with ASP.NET forms authentication and you can pass the user and role information via headers from ASP.NET to ASP.

    Warning: Be sure to validate and authenticate the headers you pass with ExecuteUrlHeader, e.g. by adding a MAC using a secret that is shared between ASP and ASP.NET (HMACSHA1 would be a possibility).”

  13. Mark says:

    I’ve got everything working pretty well, but now I need to add some custom headers into the request. So I made a custom httpHandler that derives from the DefaultHttpHandler and overrides the OverrideExecuteUrlPath() method. This made me want to ask two more questions.

    Since ASP.Net 2.0 does not contain a exhaustive list of httpHandlers in it’s machine.config, how do I go about replacing the DefaultHttpHandler with my new custom DefaultHttpHandler? I think it would be a bit scary to simply add verb=”*” path=”*” line pointing at my custom handler. Scary because how would any of the other extensions get passed off to the correct handler? For example how would “*.aspx” get passed off to the PageHandlerFactory?

    The other question that comes to mind is how does the DefaultHttpHandler eventually call HSE_REQ_EXEC_URL? I assume that it is actually calling some more abstract managed code method. Something like ‘context.Server.Transfer()’. Is that how it actually ends up calling HSE_REQ_EXEC_URL?

  14. Mark says:

    I feel lame, the list of httpHandlers has been moved into a global web.config file that resides in the same directory as the machine.config. The system.web.DefaultHttpHandler is listed there, and obviously could be replaced with my custom handler.

    But I’m still not sure how to force HSE_REQ_EXEC_URL to be called from a custom handler.

  15. Telanas says:

    I’m trying to filter non-ASP.Net content in much the same way. But I would like to add a custom header before the request is sent back to IIS. This way the ColdFusion application that will eventually be executed could read the injected header that was created by the HttpHandler.

    I’ve gone round and round trying to get a custom handler (that is derived from DefaultHttpHandler) to inject custom headers into a request before it is passed back to IIS.  Could you please post a small example that adds headers using the ExecuteUrlHeader collection.

    I’m not sure if I should be making a calls to base.ProcessRequest() and then adding the headers to the base.ExecuteUrlHeaders collection or implementing something on my own.

    Even a tiny pseudo code example would do wonders for my understanding. Please help!

    Thanks in advance!

  16. Murat says:

    Hi David,

    I’m writing a DLL that includes ISAPI Filter & ISAPI Extension export functions together.

    My ISAPI Filter does not use RAW_DATA notifications. I’m using PREPROC_HEADERS to handle "GET" requests. And using HttpExtensionProc function to handle "POST" requests (HSE_REQ_EXEC_URL).

    While reading your "SHORT ANSWER" I noticed this sentence:

    "… ISAPI Filter (we changed this in IIS7, but that is not the point of discussion here…)"

    My question is what you changed in IIS7? Because if you removed ISAPI Filter’s i’m not gonna use it in my dll to make my dll compatible with IIS6 & IIS7.

  17. David.Wang says:

    Murat – what you are doing is fine for IIS6 and IIS7 – ISAPI Extension and ISAPI Filter are staying.

    What I meant to say (and I have updated it to clarify) was that IIS7 uses a completely new extensibility API for both native and managed code (hence "changed" from ISAPI, the original way to extend IIS).

    However, CGI, ISAPI Filter, and ISAPI Extension each require a compatibility module to function on IIS7. They are now third class citizens.

    You may find the new API to be easier to do things than ISAPI, so you may want to write a new native module for IIS7 and beyond, and leave the ISAPI version for IIS6.


  18. Murat says:


    I got two questions more:

    1- Which one is faster you think; Handling "GET" requests in an ISAPI filter or ISAPI extension?

    Processing speed is so important as my dlls are intercepting all incoming IIS requests.

    Currently I’m using an ISAPI Filter to handle "GET" requests and I  got an extension dll that handles "POST" requests (Executes them using "HSE_REQ_EXEC_URL" with dwExecUrlFlags = HSE_EXEC_URL_IGNORE_CURRENT_INTERCEPTOR). Dequeing a request not seems logical to me 🙁

    2- What makes faster "HSE_REQ_EXEC_UNICODE_URL" than "HSE_REQ_EXEC_URL"?

    I got the following information from MSDN site.

    HSE_REQ_EXEC_URL: Only executes URLs formatted in the codepage of the IIS server. It does not support international applications, and is slower than the Unicode version.

    HSE_REQ_EXEC_UNICODE_URL: Only executes URLs formatted in Unicode characters. It fully supports retrieving any Unicode resource on a file system like NTFS.

    Additionally: You are hurting me by calling filters and extensions as third class citizens. 🙂 I miss win 3.1…

  19. David.Wang says:

    Murat – It all depends on what you are trying to do.

    I have no idea why you have to use ISAPI Filter to handle GET and ISAPI Extension to handle POST. Can you please explain what you are trying to accomplish.

    HSE_REQ_EXEC_UNICODE_URL is faster than HSE_REQ_EXEC_URL because there is no internal conversion to Unicode. This is of course implementation-specific to IIS6 and may not hold true in future IIS versions.

    Hey, don’t shoot the messenger. 🙂 I am only telling you the brutal truth, that CGI and ISAPI are really a third-class extensibility mechanism on IIS7 for compatibility reasons.


  20. Murat says:


    My dlls intercept all incoming requests and deny if requests include malicious code. I mean "I’m filtering all requests". But filters cannot reach entity body. So I used an extension dll to handle POST.

    I used filter to handle GET because I think it’s faster! Because I don’t need to execute the request! But the same request is being executed by the extension dll :(. There is no way to specify HTTP Verbs at the definition of wildcard application maps. So which one is faster way? Handling "GET" in the ISAPI filter or handling it in ISAPI extension?

    PS: I will start coding a new native module for IIS7 when I complete IIS6 version.



  21. Steddom says:

    Would the method discussed here be considered a supported configuration of IIS6? By that I mean is configuring the ASP.NET 2.0 aspnet_isapi.dll as a Wildcard application map in IIS6, be considered supported by Microsoft?

    The reason I ask, is because I started a tech support call with Microsoft yesterday, inquiring about some odd behavior I was seeing in a custom HttpHandler that derives from the DefaultHttpHandler. The response from tech support was basically “We cant help you, because configuring the ASP.NET 2.0 DLL as a Wildcard application map is not supported.”

    I am a little confused, because the solution discussed here is so simple and elegant. But I can’t consider it a solution if there is no support for code that would eventually be executed further on down the line.

    It was my understanding that potential call to HSE_REQ_EXEC_URL was specifically coded into the ASP.NET 2.0 aspnet_isapi.dll so that it could be used as a Wildcard application map in IIS6. Is this not the case?

    Thanks for any insight you can give,


  22. Gert Conradie says:

    I assign *.asp to be handled by ASP.Net 2/aspnet_isapi.dll on IIS6

    I run an httpModule where I do url rewrite to a central custom ASP.Net "asp" viewer page.

    This ASP.Net page then request the asp page again (with extra querystring parameter to tell the httpModule NOT to rewrite the url again) via an HttpWebRequest. (I do set correct Credentials and proxy on the HttpWebRequest)

    I use the System.Web.DefaultHttpHandler (set in the application web.config) to handle "path="*.asp"" requests.

    The idea is then to strip the response HTML and place it in our new UI framework (materpages etc).

    All work well but I get a "The underlying connection was closed: The connection was closed unexpectedly." – ie the DefaultHttpHandler was unable to run/process the *.asp page.

    When I use System.Web.HttpForbiddenHandler in the web.config to handle *.asp  

    – I get: "The remote server returned an error: (403) Forbidden " which is correct and prove that my code/httpmodule/httphandler settings work.

    Any comments? Somewhere there must be something else that I must set?

    Thanks, gert

  23. David.Wang says:

    Gert – Please tell me what you are trying to accomplish.

    Your configuration is intentionally causing an infinite loop whenever you try to access .asp resources.

    It is not possible for ASP.Net 2.0 code to filter the response data that is not generated by ASP.Net.

    Only with IIS7 can you do what you want, in the manner you describe.


  24. Tayyab says:

    Hi David!

    I am trying to pass user Roles info from to classic asp by using CustomHandler class as Stefan Schackow explained in his book. Her is my code

    public override string OverrideExecuteUrlPath()


               HttpContext htpCon = HttpContext.Current;

               StringBuilder userRoles = new StringBuilder();

               RolePrincipal rp = (RolePrincipal)htpCon.User;

               string rolesHeader = "";

               if ((rp != null) && (rp.GetRoles().Length > 0))


                   foreach (string role in rp.GetRoles())


                       userRoles.Append(role + ",");

                       rolesHeader = userRoles.ToString(0, userRoles.Length – 1);





                   rolesHeader = string.Empty;


                               this.ExecuteUrlHeaders.Add("Roles", rolesHeader);

               return null;


    My problem is that "this.ExecuteUrlHeaders.Add("Roles", rolesHeader)" line generates error. The ExecuteUrlHeader is always =null. he error is Object reference not set to an instance of an object. Please help me out here and let me know how can I make it run. Thaks

    I am using .net 3.5 and windows 2003


  25. David.Wang says:

    Tayyab – I suggest contacting standard ASP.Net support for your question. Forums at would be a good start.


  26. pete says:

    Hi David,

    Thank you for all the helpful information. I was thinking the mapping described on this page may solve my issue. I have a module that works perfectly on other applications like OWA 2007, SharePoint, etc. However when I try to implement it with OWA 2003 IIS 6 there are problems. The machine has ASP.NET 2 installed and it’s selectable from IIS. Is it possible to do this wildcard mapping with outlook web access 2003? When I add the wildcard mapping it seems to break the application. I noticed it changes the ASP.NET setting when I add that mapping, so I was thinking that might be causing the problem. Also, would I need to implement anything specific in the module for this scenario to hand the request back to IIS?

    Any guidance you can provide is appreciated.



Skip to main content