<< July 2014 | Home | September 2014 >>

418 I'm a teapot

Problem: How to handle session timeouts for AJAX calls made from a plain HTML + jQuery page?

The webapp has a GUI where the user logs in via a form and gets an authentication cookie. A servlet filter handles authentication and accepts a valid cookie, or a valid Authorization (Basic) header. The latter is intended for web service calls made from other applications.

When a non-authenticated request arrives, the filter detects whether it is for a web service or for a GUI resource (HTML or whatever). If it's for a GUI resource the login page is presented. If it's for a web service the response is 401 Unauthorized since non-browser clients are expected to use Basic authentication with specially generated API keys and passwords.

This works fine, except in the case where the cookie has expired and the user clicks on something that generates an AJAX call to the web service API. Since this call is now unauthenticated, the response will be 401 which causes the browser to present the Basic username/password popup. The user's normal login credentials won't work here, and we wouldn't want Basic authentication for the browser anyway.

Changing the response to be the same as for GUI resources won't help, because the AJAX code can't distinguish it from a successful 200 response. Of course a special header or extra cookie could be added, but that's awkward to deal with.

But I found a trick: Use a HTTP fail-code that is not handled transparently by the browser. I picked 418 I'm a teapot which was introduced in RFC 2324 as an April Fools' joke. Then I put this in my jQuery startup code:
 

    $.ajaxSetup({
        statusCode: {
            418: function() {
		     window.location.reload();
                 }
        }
    });

This ensures that all AJAX calls on all web pages will force a page reload whenever status 418 is returned, and this in turn will present the login page to the user.

Credits to this Stackoverflow answer for this elegant solution to a similar problem.

The 418 response is only returned for AJAX calls from the browser. Other clients still get the proper 401 response. In order to distinguish browsers from other clients, I now just check if the request has a User-Agent header that starts with "Mozilla/5.0", which apparently is the case for all modern browsers these days.