Search code examples
asp.net-mvc-3c#-4.0iis-7.5windows-authentication

IIS 7.5 Windows + Anonymous Authentication works intermittently


I have an MVC 3 site in which some controllers/action require Windows authentication, other do not, i.e. are anonymous.

The site runs fine for a while, I can access both the anonymous actions and authenticated ones (without prompting, credentials are passed automatically in Chrome/IE/FireFox) but then the authentication just stops working, I start getting prompted for credentials that are never accepted.

I have to either restart the whole server, or change the site physical path to another app, make an authenticated request, which works, when back to the original site. The situation is then rinse and repeat, but I can find no pattern to it. If I do nothing, the authentication stays broken.

I have IIS 7.5 configured as follows:

App Pool

  • .NET Framework Version - v4.0
  • Managed Pipeline Mode - Integrated
  • Application Pool Identity - ApplicationPoolIdentity

Site

  • Anonymous Authentication - Enabled
  • Anonymous user identity - IUSR [I believe this is the default]
  • ASP.NET Impersonation - Disabled
  • Forms Authentication - Disabled
  • Windows Authentication - Enabled
  • Windows Authentication Extended Protection - Off
  • Windows Authentication Enable Kernel-mode authentication - On
  • Windows Authentication Providers - Negotiate, NTLM

Controllers

// Anonymous Controller
    public class HomeController : Controller
    {
       public ActionResult Index()
       {
          return this.View();
       }
    }

// Authenticated Controller
    [Authorize]
    public class AnotherController : Controller
    {
       public ActionResult Index()
       {
          var viewModel = // create view model;
          return this.View(viewModel);
       }
    }

At all times, authentication working or not a GET to /home/index returns 200. Exactly as expected. When authentication is working a GET request to /another/index looks like this:

> GET /another/index
  Response: 401
  Response Headers: WWW-Authenticate: NTLM, WWW-Authenticate:Negotiate


> GET /another/index
  Request Header: Authorization: Negotiate TlRMIVNDUAACAAAAl4II4gAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw==
  Response: 401
  Response Header: WWW-Authenticate: Negotiate TlRMTVNTUAACAAAACAAEADgAAAAVgoni2YSlwIHmCL4AAAAAAAAAAIgAiAA8AAAABgGxHQAAAA9GAFIAAgAEAEYAUgABABQARgBSAEkATgBUAFIAQQBOAEUAVAAEABQARgBSAC5ATgBEAFMALgBjAG8AbQADACoARgBSAKkATgBUAFIAQQBUAEUAVAAuAGCAcgAuAG4AZACzAC4AYvBvAG0ABQAOAE4ARABTAC6AYwBvAG0ABwAIAOST3lPK980BAAAAAA==

 > GET /another/index
   Request Headers: Authorization: Negotiate TlRMTVNTUAACAAAACAAEADgAAAAVgoni2YSlwIHmCL4AAAAAAAAAAIgAiAA8AAAABgGxHQAAAA9GAFIAAgAEAEYAUgABABQARgBSAEkATgBUAFIAQQBOAEUAVAAEABQARgBSAC5ATgBEAFMALgBjAG8AbQADACoARgBSAKkATgBUAFIAQQBUAEUAVAAuAGCAcgAuAG4AZACzAC4AYvBvAG0ABQAOAE4ARABTAC6AYwBvAG0ABwAIAOST3lPK980BAAAAAA==
   Response: 200

When authentication breaks a GET request to /another/index looks like this:

> GET /another/index
  Response: 401
  Response Headers: WWW-Authenticate: NTLM, WWW-Authenticate:Negotiate


> GET /another/index
  Request Header: Authorization: Negotiate TlRMIVNDUAACAAAAl4II4gAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw==
  Response: 401
  Response Headers: WWW-Authenticate: NTLM, WWW-Authenticate:Negotiate

At this point I am prompted for credentials, which I enter, the same request, response is resent:

> GET /another/index
      Request Header: Authorization: Negotiate TlRMIVNDUAACAAAAl4II4gAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw==
      Response: 401
      Response Headers: WWW-Authenticate: NTLM, WWW-Authenticate:Negotiate

Does anyone know if I have something misconfigured (I'm fairly sure I don't or I wouldn't expect it to work at all), why the authentication breaks or how I can stop it?

Many thanks all.


Solution

  • So it turns out this was as a result of the bad usage of the httpErrors section of the web.config

    <httpErrors errorMode="Custom">
          <remove statusCode="401" subStatusCode="-1"/>
          <error statusCode="401" path="/Unauthorized" responseMode="ExecuteURL"/>
          <remove statusCode="403" subStatusCode="-1"/>
          <error statusCode="403" path="/Unauthorized" responseMode="ExecuteURL"/>
          <remove statusCode="404" subStatusCode="-1"/>
          <error statusCode="404" path="/NotFound" responseMode="ExecuteURL"/>
          <remove statusCode="500" subStatusCode="-1"/>
          <error statusCode="500" path="/ServerError" responseMode="ExecuteURL"/>
        </httpErrors>
    

    This resulted in the NTLM requirement to resubmit the credentials not being sent from the server when authentication was required. i.e. What should happen Request, Challenge (please resubmit with credentials) Response. What actually happened Request, Response. The Challenge was never sent.

    The correct approach is to remove this section completely and in my errors controller actions use the extension method TrySkipIisCustomErrors.