HTTP 418 (I’m a teapot): Finally, a “Legitimate” Use

Ever since IETFA published RFC 2324 (Hyper Text Coffee Pot Control Protocol, HTCPC/1.0), web developers all over have wondered how can I find a legitimate use of HTTP response code 418 (I’m a teapot). Well… at least I’ve been wondering that. And as it would happen, the moment I stopped looking for a way, HTTP 418 found me.

I was faced with a fairly unique problem: get the end-users web browser to prompt for credentials when our application’s authentication is configured to use forms and cookies (FormsAuthentication). In a language much “closer” to the webserver (like Perl), this would be easy: simply send a 401 code. Frameworks like ASP.NET, on the other hand, work very hard to make sure we developers never have to worry about things like HTTP Status codes.

So why would anyone want to have this type of double-authentication? Well, in my case, the Build Schedules module allows users to set-up a URL-triggered build and, for obvious security reasons, configure triggers to require authentication with a name/password. Because FormsAuthentication is already configured, this means that accessing the handler would redirect to the login page… which is not a very useful feature.

Not Authorized to Not Authorize

ASP.NET will gladly allow you to set any status code. This means that, in theory, implementing double-authentication should be easy.

if (!authorized)
{
    context.Response.StatusCode = 401;
    context.Response.Write("Not authorized");
    context.Response.AddHeader("WWW-Authenticate", "Basic realm=\"BuildMaster URL Trigger\"");
    context.ApplicationInstance.CompleteRequest();
    return;
}

Of course, that didn’t quite turn out like expected: I was immediately redirected to the login page. As it turned out, the culprit was the FormsAuthenticationModule. Firing up reflector, I found that it was coded to listen to the Application.EndRequest event, look for a 401 status code, and when found, redirect to the login page.

if (context.Response.StatusCode == 401)
{
    string rawUrl = context.Request.RawUrl;
    if ((rawUrl.IndexOf("?ReturnUrl=", StringComparison.Ordinal) == -1)
        && (rawUrl.IndexOf("&ReturnUrl=", StringComparison.Ordinal) == -1))
    {
        ...snip...
        if (!string.IsNullOrEmpty(FormsAuthentication.LoginUrl))
            strUrl = AuthenticationConfig.GetCompleteLoginUrl(context, FormsAuthentication.LoginUrl);
        ...snip...
        str3 = strUrl + "?ReturnUrl=" + HttpUtility.UrlEncode(rawUrl, context.Request.ContentEncoding);
        ...snip...
        context.Response.Redirect(str3, false);  // Changes the status code to 302 Found...
     }
}

This code presented a bit of a problem. Short of disabling FormsAuthentication (which would obviously create a whole host of other problems), there was no way to avoid it.

I’m a Teapot

If the FormsAuthenticationModule can “listen” for 401 responses and change them to 302, why couldn’t I do the same? My code would simply run after the FormsAuthentication code and change it from some status back to 401. Of course, I’d have to pick another response code to “listen” to… and it’d need a status code that no other code could possibly be using.

It was a relatively easy change. First things first, I modified the status-code setting code:

if (!authorized)
{
    // No, "I'm a teapot" is not what we really want, but the BuildMaster security module will
    // replace it with a 401 (which we actually want)
    context.Response.StatusCode = 418;
    context.Response.Write("Not authorized");
    context.Response.AddHeader("WWW-Authenticate", "Basic realm=\"BuildMaster URL Trigger\"");
    context.ApplicationInstance.CompleteRequest();
    return;
}

After that, I just needed to add something to listen to EndRequest. Since we already had a HttpModule for security, I added the code in there.

context.EndRequest += (s,e) =>
{
    HttpApplication app = (HttpApplication)s;
    if (app.Context.Response.StatusCode == 418)
        app.Context.Response.StatusCode = 401;
}

So while HTTP 418 is never actually sent to the browser, it is being used.

About these ads

2 Responses to HTTP 418 (I’m a teapot): Finally, a “Legitimate” Use

  1. Pingback: DotNetShoutout

  2. Pingback: insider coding

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: