Troubleshooting a problem with POST-requests in an environment with mixed NTLM and Forms authentication
In most Intranet solutions there’s a desire to make the users of the Intranet be able to use the Intranet without logging in. This can be done in many ways, but this blog post takes one particular solution and takes a closer look at it.
The partner had created a special FormsAuthentication module to replace the standard ASP.NET FormsAuthentication module. Using some reflection it piggybacked on the OnEnter and OnLeave private method implementations in that module to copy the normal FormsAuthentication behavior that redirects the user to a login page if the authentication ticket is not among the user’s cookies.
As a part of the setup of this module, two virtual paths had been created whose requests were captured by a special module. One of those paths was reconfigured using a web.config location element to add a authentication element that specified that that path expects the user to authenticate using Windows authentication.
The two paths were /WindowsLogin (configured with Windows authentication) and /Authenticate (configured using Forms authentication).
When the user was decided to be unauthenticated, the normal behavior of the Forms authentication module is to redirect the user to the login page. In this case it was configured to be /WindowsLogin (see loginUrl in the documentation). When that Url was successfully executed, the server side code would issue a forms authentication ticket with the credentials the user (or web browser) supplied. In the Intranet zone, Internet Explorer attempts an NTLM/Negotiate based authentication automatically, so no login is required. On failure, the normal forms authentication screen would present via a redirect to /Authenticate.
The solution mostly worked, but there were some problems in critical areas of the web site. Notably, postback-requests would occasionally render as non-postbacks. This is particularly bad when that postback contain information the user has spent much time creating. Typical case would be the user creating a new page or editing an existing page. After about one minute from the moment the page was downloaded, the save action would simply reload the page.
After some troubleshooting we concluded that the problem occurred because Internet Explorer believed that all pages on the intranet was protected with NTLM/Negotiate. It basically means that at each new connection made, the client is required to authenticate itself. HTTP Keep-alives allows a web browser to establish a single connection and then reuse it until the connection is closed. The idle timeout of keep alives in this environment was one minute.
It is done by a (simplified below) four step process:
- The browser sends a request for the protected resource, eg / episerver.
- The server responds 401 Unauthorized and provides a "NTLM challenge" to the browser.
- The browser computes a response to the server's given challenge and sends the response along with a new request for the protected resource.
- The server answers confirms the answer to the authentication and if it went well the server responds with 200 OK and process requests in full.
In step 1, the browser omits to send the POST data (the data entered in form fields on the page), as it is certain that the server will reply "401 Unauthenticated" and ignore the POST data so it would be pointless to send it. In step 3, the browser includes the POST data.
Since the server and the browser has different ideas of what resources are protected with NTLM/Negotiate, this creates a conflict. The browser a expects a 401 response to resources protected by NTLM where the connection is not yet authenticated, but the server satisfies itself with the Forms Authentication ticket in the set of cookies sent by the client. This allows the server to act on a POST request without receiving the POST data (as it is omitted in step 1 of NTLM authentication), and then we will get the effect we saw - the "reloaded page" with emptied of form data.
The solution was to add the NTLM / Negotiate protected resources in a virtual directory, eg /AuthenticationSubSystem/WindowsLogin, so that the browser think it's just the resources in that directory that requires NTLM/Negotiate authentication. The other resources are handled by the browser as usual. Previously the NTLM protected resources resided directly in the root which made the browser assume that other resources under the root and its subdirectories are protected with NTLM.