authenticationthinktecture-ident-modelmessage-handlers

What can cause a handler to be ignored?


We moved to a new server and my Thinktecture IdentityModel stuff broke.

Here's a super simplified repro sample. This works run locally from Visual Studio, but deployed to the server the handler is clearly not handling.

using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using Thinktecture.IdentityModel.Tokens.Http;
namespace WebApplication1
{
  public class WebApiApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
      AreaRegistration.RegisterAllAreas();
      GlobalConfiguration.Configuration.MessageHandlers.Add(
        new AuthenticationHandler(CreateConfiguration()));
      GlobalConfiguration.Configure(WebApiConfig.Register);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
    private AuthenticationConfiguration CreateConfiguration()
    {
      var config = new AuthenticationConfiguration
      {
        EnableSessionToken = true,
        RequireSsl = false,
        SendWwwAuthenticateResponseHeaders = false
      };    
      config.AddBasicAuthentication(
        (username, password) => { return username == password; });    
      return config;
    }
  }
}

The handler is not executing. I have set up remote debugging and this revealed that

This script is the test client

  <script>
    $(document).ready(function () {
      var u = "bilbo";
      var p = "bilbo";
      var btoken = btoa(u + ":" + p);
      $.ajax({
        url: "api/token",
        headers: { Authorization: "Basic " + btoken },
      }).then(function (result) {
        document.write("auth ok");
      }).fail(function (error) {
        document.write("auth fail");
      });
    });
  </script>

It produces a request for api/token decorated with a basic auth header as shown:

GET http://assa.com.au/api/token HTTP/1.1
Accept: */*
Authorization: Basic YmlsYm86YmlsYm8=
X-Requested-With: XMLHttpRequest
Referer: http://assa.com.au/sandpit
Accept-Language: en-AU,en-GB;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko
Host: assa.com.au
Connection: Keep-Alive

This server responds with this 401

HTTP/1.1 401 Unauthorized
Content-Type: text/html
Server: Microsoft-IIS/8.5
WWW-Authenticate: Basic realm="assa.com.au"
X-Powered-By: ASP.NET
Date: Wed, 17 Feb 2016 01:36:27 GMT
Content-Length: 1293

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>401 - Unauthorized: Access is denied due to invalid credentials.</title>
<style type="text/css">
<!--
body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}
fieldset{padding:0 15px 10px 15px;} 
h1{font-size:2.4em;margin:0;color:#FFF;}
h2{font-size:1.7em;margin:0;color:#CC0000;} 
h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;} 
#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;
background-color:#555555;}
#content{margin:0 0 0 2%;position:relative;}
.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}
-->
</style>
</head>
<body>
<div id="header"><h1>Server Error</h1></div>
<div id="content">
 <div class="content-container"><fieldset>
  <h2>401 - Unauthorized: Access is denied due to invalid credentials.</h2>
  <h3>You do not have permission to view this directory or page using the credentials that you supplied.</h3>
 </fieldset></div>
</div>
</body>
</html>

Visual Studio 2013 is showing valid breakpoints in the handler but they are not hit. This is why I believe the handler is not being invoked.

The response specifies a realm, but modifying handler registration to specify realm = "assa.com.au" did not affect the outcome.


Solution

  • The answer lies in the handling of the absence of trailing slashes.

    The test page is requested as assa.com.au/sandpit which does return the right HTML.

    Close inspection of the 401 response reveals that the request is for api/token which is not the correct URL for the token dispenser - it should be sandpit/api/token

    Requesting the test page as assa.com.au/sandpit/ causes the requested URL to become sandpit/api/token and everything comes out in the wash.

    But why is it 401? Shouldn't it be 404 not found? It turns out that the webserver was configured to respond to unauthorised requests by asking the user agent to authenticate, expressed as a 401 auth demand.

    The incorrect URL left things in an unauthorised state, producing a 401 auth demand.