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.

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.

Two-Level authentication on IIS 7.0 using Forms Authentication and Windows Authentication

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%\system32\inetsrv\appcmd unlock config /section:anonymousAuthentication
> %windir%\system32\inetsrv\appcmd unlock config /section:windowsAuthentication

2. Register the forms authentication wrapper configuration section in your web.config:

<!-- FormsAuthsModule configuration section -->

<configSections>

  <section name="formsAuthenticationWrapper"  

           type="Mvolo.Modules.FormsAuthConfigurationSection" />

</configSections>

3. Replace the built-in Forms Authentication module with the wrapper:

<system.webServer>

  <!-- Replace the built-in FormsAuthenticationModule with the FormsAuthModule wrapper -->

  <modules>

    <remove name="FormsAuthentication" />

    <add name="FormsAuthentication" type="Mvolo.Modules.FormsAuthModule" />

  </modules>

</system.webServer>

4. Set the required settings for the gateway page:

<!-- Disable Forms Authentication for this URL -->

<location path="login.aspx">

  <!-- Disable Forms Authentication -->

  <formsAuthenticationWrapper enabled="false" />

  <system.webServer>

    <security>

      <!-- Enable IIS Windows authentication for the login page -->

      <authentication>

        <windowsAuthentication enabled="true" />

        <anonymousAuthentication enabled="false" />

      </authentication>

    </security>

  </system.webServer>

</location>

That should do it.

Some caveats:

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

Downloads:

1)      Sample application and FormsAuthModule wrapper v1.0.

2)      Source code for FormsAuthModule wrapper v1.0.

NOTE: Released under Microsoft Permissive License, and supported exclusively through this blog.

Thanks,

Mike

Published 11 February 08 02:28 by Mike Volodarsky

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# IIS 7.0 Server-Side : Breaking Changes for ASP.NET 2.0 applications running in Integrated mode on IIS 7.0 said on February 11, 2008 3:16 PM:

PingBack from http://mvolo.com/blogs/serverside/archive/2007/12/08/IIS-7.0-Breaking-Changes-ASP.NET-2.0-applications-Integrated-mode.aspx

# MVolo's Blog said on February 11, 2008 3:52 PM:

The integration of IIS and ASP.NET authentication stages in Integrated mode applications brings a lot

# Claudiov said on February 13, 2008 4:37 AM:
Hi Mike, I have followed your instructions but I receive HTTP 503, the service is unavailable, I'm I missing something?...
# Mike Volodarsky said on February 13, 2008 12:49 PM:

Hi Claudiov,

Please follow http://mvolo.com/blogs/serverside/archive/2006/10/19/Where-did-my-IIS7-server-go_3F00_-Troubleshooting-_2200_service-unavailable_2200_-errors.aspx and see whether you are getting an error message in the event log.

Thanks,

Mike

# Steve said on March 14, 2008 7:55 AM:
will this approach work with web services? Is it possilbe to use one url which allows integrated authentication + user name password to be passed via soap header properties. The if the caller is not a windows user, use passed in credentials?
# Mike Volodarsky said on March 14, 2008 2:03 PM:

Hi Steve,

You wouldnt use this exact approach, but something similar.  If you can extract the credentials in AuthenticateRequest, you can run after the WindowsAuthenticationModule and authenticate as that user. Otherwise, let the request go forward and be rejected with the NTLM / Negotiate challenge to authenticate with Windows credentials.

If you cannot extract credentials until later when WCF has processed the request/SOAP payload, then just authenticate as special "interim" user in AuthenticateRequest to avoid the request being rejected, then in your web service either authenticate with the SOAP credentials or reject the request with 401 to allow Windows authentication to take place.

Just a note: this information is intended for WCF web services hosted in IIS 7.0 running in Integrated mode.

Thanks,

Mike

# AndrewHa said on May 21, 2008 7:30 PM:

Mike what if you wanted to add to this module for x509cert authentication and SecureID. What happens if your internal users don't have passwords and you don't want to distribute another ID. So your users that may use windows auth when they are logged onto your network are hitting the same site from the internet and have the same certificate they would use when they log onto their workstations. Or some of your users have secureID cards.

# Mike Volodarsky said on May 23, 2008 1:21 PM:

Hi Andrew,

Theoretically, you would configure the the required authentication (cert auth or secureId) for the gateway page, and flow their authenticated identity to the forms ticket the same way I do it here using Windows Auth.

The way you determine the identity in the gateway is completely up to you, so it should support any authentication protocol you'd like to use. As long as you then take that identity and issue a forms auth ticket to represent it.

Keep in mind though that Forms Authentication is a ticket-based scheme, which has inherent security limitations.  Using it to represent a stronger authentication scheme (like x509) is essentially downgrading the security of that scheme - if someone manages to exploit the forms auth ticket.

For more info on ticket security, search "client ticket security" in my old article, http://msdn.microsoft.com/en-us/magazine/cc163702.aspx.

Thanks,

Mike

# Benny_NET said on July 22, 2008 3:48 AM:

[原文:http://mvolo.com/blogs/serverside/archive/2007/12/08/IIS-7.0-Breaking-Changes-ASP.NET-2.0-applic...

# Neeraj Tomar said on August 13, 2008 1:53 PM:
Hi Mike, My project requires windows Authentication and if user do not provide correct credentials or don't have valid credentials, then to display login page and use ADAM (Form authentication). I tried your sample application. Challenge response dialog appears but if i cancel it then HTTP Error 401.2 appears. I need to display login page instead. Let me know how can i override 401.2 Unauthorized error page with my login page. Your help is highly appreciated. Regards, Neeraj Tomar
# Chaos_Crafter said on September 19, 2008 12:42 AM:
What if you wanted the reverse of this. You have a web page that may be internally or externally accessed. You'd like the internal users to be automatically recognised by their window id, with no data entry at all. You want external users to be directed to a forms login page. Presumably you can set it to windows login, and then recognise that there is no user auth, but how do you surpress the username/password box if you can't detect a windows user?
# Chuang said on October 6, 2008 12:10 AM:
Hi Mike. There is this button called "Log on To" in Active Directory where an admin can specify which machines a user can log on to. My situation is that the admin sets all the users to be able to log on only to their own desktop PCs. It seems that Forms authentication doesn't work in this case unless I'm accessing the web application from my own PC. Is there any way around this without needing the admin to allow the server as one of the computers the user can log on to?
# Basel said on October 26, 2008 7:21 AM:
hi Mike. I was working on web application on .net 2.0 , deployed into win server 2003 and IIS 6.0 , and after i moved it to windows server 2008 and IIS 7.0 , the form based authentication didn't redirect correctly like the way you describe in the article above, so i decide to change the Application Pool from integrated to Classic , even through it didn't work, so are there away other than using a wrapper module you,ve create.
# fuzzlog said on December 17, 2008 3:38 PM:
Is the "Login.aspx" page stated in the web config file mandatory? I created a new page (LoginNext.aspx) and replaced "Login.aspx" with that in the web config file. Now when I direct IE to "Login.aspx" the code module does nothing. Meaning that it doesn't redirect to "LoginNext.aspx" (I was under the impression the module redirected to whatever page was specified in the within the "location" tag. The reason I'm doing this is because we have many existing sites that need to be converted to this hybrid login scenario. If someone is attempting to login from within the network, they should be redirected to the requested page, or at least a default "authenticated" page, if not, they should be shown the login page. Since most of our users have our various login pages already saved as favorites, I wanted apply this so that it was seamless to them (thus leaving the existing "Login.aspx" under forms Authentications so that it would redirect to "LoginNext.aspx" and be handled accordingly). Any suggestions? Thanks
# Mike Volodarsky said on December 17, 2008 5:10 PM:

fuzzlog,

You can change the login page url in the <forms> configuration element.

Thanks,

Mike

# fuzzlog said on December 17, 2008 6:35 PM:
Mike,

Thanks for the response. In your code (web.config), the "Login.aspx" file was only mentioned in the "path" attribute of the "location" tag that contained the "" tag. Why wouldn't just replacing the new file name in that "path" attribute work accordingly?

Below are the changes I've done to the web config file. The result after those changes is that all pages act as if Authentication had been set to "None". I'm using IIS 6.0, integrated authentication plus anonymous (to prevent the credentials request popup). This setup works just fine with your code unmodified.


Thanks again for any input.
# Mike Volodarsky said on December 17, 2008 6:48 PM:

fuzzlog,

You'll need to change both the location path and set the loginUrl in the <forms> element to the new login url.  See http://msdn.microsoft.com/en-us/library/1d3t3c61.aspx for the latter.

Thanks,

Mike

# fuzzlog said on December 17, 2008 6:51 PM:
Darn,

some tags were stripped. Anyhow, what I did was to add the "LoginNext.aspx" path to the "forms" tags as well as replace "Login.aspx" with "LoginNext.aspx" in the "location" tag.

This produces the "Authentication mode='None'" behavior described in post above.
# fuzzlog said on December 17, 2008 8:06 PM:
Mike,

The problem I was having was due to the following, "FormsAuthentication.SetAuthCookie(wi.Name, true);". Setting the second parameter to "true" saved a cookie in the my system. This authentication cookie persisted even after I made the changes to the web.config, so when I ran it again, I was already authenticated and would always go directly to any page a chose.

After I cleared the cookies in IE and changed the parameter to "false" the code works correctly.

Thanks for your willingness to share you knowledge.
# Nick Watt said on January 12, 2009 5:54 AM:

Mike,

Many thanks for sharing your knowledge.  You won't believe how much time you have saved me.  I developed our intrant on server 2k3 with iis6 for the school I work in which takes advantage of student and staff AD logins, but also uses forms authentication for their parents to login externally (we didn't want to create AD logins for the parents because that would just be silly to manage!).

We have recently just bought a brand new server for the intranet and as we are in the process of upgrading our servers to 2k8 the intranet server was subsequently installed with this version.  This article has helped me tremendously in getting the application migrated to the new server quickly.

Thanks again.

Nick (nwatt@hotmail.com)

# Ron Klose said on March 20, 2009 5:04 PM:
I tried to follow the example. I had some success. The authentication types it switched between were windows integrated, and http-auth (not sure what the current term of the firefox/ie popup authentication challenge). Is there some configuration I have to adjust to get it to switch between windows integrated and forms. Thanks
# David Steadman said on April 5, 2009 2:17 PM:
I am getting an error when I am trying to add the location the system.webserver complains it is not a valid child element of location. Any help would be great...
# Blog de l'équipe support IIS France said on April 10, 2009 9:30 AM:

L'authentification par formulaire – également appelée authentification par cookie – est aujourd'*** énormément

# William Lin said on April 30, 2009 7:23 PM:
Hi Mike. Thanks for providing this solution. We have implemented your solution and it works. But, my question is, Why not enable both forms and windows authentication in IIS7. Then in the location element for login.aspx only have Thanks, Will
# Brad said on May 15, 2009 2:01 PM:
Mike, This sounds very close to what we are wanting to implement. We have multiple domains and want to connect to our web applications using forms authentication. We would like to use Active Directory as the data store for the forms authentication and we need our web applications to impersonate the domain user that logged on through forms authentication. The catch is the users may or may not be logged on to the domain at the time they connect to our web application. Is there a way to use forms authentication to authorize a user and "convert" to windows authentication once inside the application?
# mcm said on June 1, 2009 9:12 PM:

Here's my scenario, when a page needs to make changes to the file system, instead of giving access to the IIS user I use impersonate to get the page to run under a different account that does have write access. This prevents things like the folder getting recreated loosing the permission changes given to the IIS user.

Would there be a better way of achieving that or will your solution be the only way.

Leave a Comment

(required) 
(optional)
(required) 
Enter the code you see below


About Mike Volodarsky

For the past 5 years, I was the core Program Manager for Microsoft ASP.NET 2.0 and IIS 7.0 products. I drove the design and development of the IIS 7.0 web server core, the IIS FastCGI support, the AppCmd command line tool, the ASP.NET Integrated pipeline, and other special projects around server security, performance, and scalability. Now, I am working on my own on cutting edge web server tech on top of the Microsoft IIS platform, and continue blogging about it here.

About me



For the past 5 years, I was the core server Program Manager for the IIS 7.0 and ASP.NET 2.0 products at Microsoft.
Now, I work on advanced web server tech using IIS 7.0, .NET, and Windows Server 2008 and write about it in this blog.

View Michael Volodarsky's profile on LinkedIn

Writings



TechNet Magazine
>Top 10 Performance Improvements in IIS 7.0

MSDN Magazine
>IIS 7.0: Build Web Server Solutions with End-To-End Extensibility
>IIS 7.0: Enhance Your Apps with the Integrated ASP.NET Pipeline
>IIS 7.0: Explore The Web Server For Windows Vista And Beyond
>Design and Deploy Secure Web Apps with ASP.NET 2.0 and IIS 6.0
>Fast, Scalable, and Secure Session State Management for Your Web Applications


Tools and Modules

LeechGuard
IconHandler 2.0
DirectoryListing
HttpRedirection
IIS Auth for Wordpress
iisschema.exe
PortCheck.exe v2.0

Popular Posts

- ASP.NET 2.0 Breaking Changes on IIS 7.0
- Develop IIS7 modules and handlers with .NET
- Troubleshoot IIS7 errors like a pro
- Troubleshooting 503 / "service unavailable" errors
- Troubleshooting "server not found" errors
- Create IIS7 sites, applications, and virtual directories
- Run Ruby on Rails with IIS FastCGI
- VS Debugging of ASP.NET applications on Windows Vista
- Stop hot-linking with IIS and ASP.NET

Tags

Search

Go

This Blog

Archives

Good IIS Blogs

Disclaimer

These postings are provided as is with no warranties, and confer no rights. The views expressed in this blog are entirely my own.

Syndication