IIS 7.0 Two-Level Authentication with Forms Authentication and Windows Authentication
One of the key improvements granted by the ASP.NET integration in IIS 7.0 is a unified authentication model. Instead of the two-stage model in previous versions of IIS, where IIS executed its own authentication methods before ASP.NET processing began, in Integrated mode IIS and ASP.NET authentication modules participate in a single authentication process as equals. With this, it becomes very easy to write custom authentication methods using .NET (that previously required ISAPI filters and C++ code), and use these solutions in a way that integrates seamlessly into the IIS security model.
Update: We recently launched a service that significantly helps you understand, troubleshoot, and improve IIS and ASP.NET web applications. If you regularly troubleshoot IIS errors, manage Windows Servers, or tune ASP.NET performance, definitely check out the demo at www.leansentry.com.
Popular example – everyone’s favorite Forms authentication, backed by a Membership credential store and login controls, being used to secure access to your entire Web site including your images, PHP pages, CGI applications, and so on.
The problem: using ASP.NET Forms authentication and IIS Windows authentication in the same application
Unfortunately, one of the limitations of a single-stage authentication model is that it is done in a single stage (imagine that!). Because of this, certain authentication schemes that relied on the two-stageness of the authentication process used by ASP.NET applications in the past no longer work.
Consider the following example:
You have a login.aspx page which allows your users to log in using Forms authentication. But, all of your users also have Windows accounts on the server (or Active Directory). For some reason, you want all users to first log in using their Windows credentials, and then log in using their Membership credentials and Forms authentication. You could do that by enabling Windows authentication and disabling Anonymous authentication in IIS, which would cause the request to be rejected by IIS before it would arrive in ASP.NET, thereby making sure that your users were first authenticated by Windows auth.
This works on IIS 6.0 and on IIS 7.0 in Classic mode. But, in Integrated mode, both Windows and Forms authentication run during the single stage authentication process, which makes it impossible to first authenticate with Windows authentication, and second authenticate with Forms authentication. Additionally, because Forms authentication is enabled for the entire application, there is no way to enable it for a part of your app and not for another – which presents a problem, because Forms authentication’s 302 redirect challenge is incompatible with the 401 “WWW-Authenticate” challenge used by Windows authentication. Forms auth will always convert unauthorized requests to the application to a 302 redirect, thereby breaking Windows authentication.
Here is how to do it …
After posting the list of ASP.NET breaking changes for IIS 7.0, a number of people contacted me asking for a way to accomplish this.
The answer lies in separating the windows authentication and forms authentication transactions into two separate pages – one page will be the gateway page that requires Windows authentication, and the other page (or pages) will require forms authentication. Luckily, this maps well into the Forms Authentication model of having a separate login page which will become our gateway.
Secondly, using a wrapper module, we will disable Forms authentication for the gateway (login) page. This way, our Windows authentication challenge will work correctly.
This works as follows (as shown in the diagram above):
1) Anonymous request to page.aspx (a protected page in your app)
a. Access is denied (anonymous is disabled, or, authorization rule denies anonymous user)
b. Forms authentication issues a 302 redirect to login page
2) Redirected anonymous request to the login page
a. Access is denied (anonymous is disabled)
b. Forms authentication is disabled using our wrapper, so it doesn’t issue a 302 redirect
c. Windows authentication issues a challenge
3) Request with windows credentials to the login page (this may actually be several requests as part of the NTLM/Kerberous handshake)
a. Windows authentication authenticates the request
b. The page either displays a login control for the user to log in using forms, or automatically logs in using forms equivalent of the windows user
c. Issues a 302 redirect back to the original page
4) Forms-authenticated request to page.aspx succeeds
Setting it up
Download the attached application for an example of setting it up. You’ll need to:
1. Unlock the <anonymousAuthentication> and <windowsAuthentication> configuration sections before you can use them in web.config:
> %windir%system32inetsrvappcmd unlock config /section:anonymousAuthentication
> %windir%system32inetsrvappcmd unlock config /section:windowsAuthentication
2. Register the forms authentication wrapper configuration section in your web.config:
<!-- FormsAuthsModule configuration section -->
3. Replace the built-in Forms Authentication module with the wrapper:
<!-- Replace the built-in FormsAuthenticationModule with the FormsAuthModule wrapper -->
4. Set the required settings for the gateway page:
<!-- Disable Forms Authentication for this URL -->
<!-- Disable Forms Authentication -->
<!-- Enable IIS Windows authentication for the login page -->
That should do it.
- The wrapper uses reflection to invoke the real forms authentication module. This means that it must either run in applications in Full trust, or be in the GAC.
- This is for Integrated mode applications on IIS 7.0 only. Previous versions of IIS or Classic mode applications dont require this as they use two-phase authentication.
NOTE: Released under Microsoft Permissive License, and supported exclusively through this blog.