Server-side Sessions Are A Hack
There is no reason for the server to manage sessions. CGI languages used to be so removed from the real web that basic HTTP operations were a mystery to them. Thus, there was no CGI-based HTTP Authorization. This lead to the proliferation of login-by-form and server-side session management by your CGI language. And thousands of innocent URIs being sullied by ugly “PHPSESSIONKEY” variables.
Web browsers are really good at maintaining sessions. So good, in fact, that CGI languages tend use a small part of the browser’s system to manage their system. PHP, for example, takes the PHPSESSIONKEY out of the URL and hides it in a cookie. This cleans up the URIs, but it still leaves all the heavy lifting happening on the server.
Servers are busy. Web browsers have lots of free cycles available to them. Why not just use the browser’s sessions then?
Well, we tend to keep a fair chuck of data in the session variables. It’s very convenient to do so. And sending all of that data is expensive in both time and bandwidth. Not to mention the fact that there more often we put the data on the wire, the better the chance someone is going to do something nasty with it. So we can’t just stuff out session variables into a cookie. But if we look at the data in those session variables, we see that 99% of it is just two things: authentication and preferences. If we use HTTP authentication, we don’t have to hold on to that part of the session variable any more. So all we’re left with is the user’s preferences. And this is just a caching mechanism. We pulled the preferences out of the data store when they logged in and stuffed them into the session variable to avoid having to re-fetch them. Is this ‘optimization’ worth the cost? Is avoiding one SELECT statement worth giving up a stateless server? I’d suggest it isn’t. If you are servicing enough requests per second that the database can’t transparently cache the SELECT for you, you are probably going to be better served by moving the more common preferences into the URL. Because GET parameters are even faster to access that a session variable…
Form authorization + JavaScript allows us to use things like CRAM-SHA1 as authentication mechanisms. CRAM-SHA1 is awesome and secure and wonderful. Yet nobody does it, most sites just fire off a POST with the username and password in plain text. (And, for the record, if you MD5 the password in the browser it counts as plaintext because I don’t need the password, the digest hash of a constant string is a constant.) This is a bad situation. Luckily, HTTP 1.1 offers digest authorization and every remotely modern browser supports it out of the box. Heck, even browsers that don’t have JS have Digest. Digest authentication encrypts the password, prevents replay attacks, and even automatically expires sessions if you want it to.
Still, forms are prettier than the browser’s login/password dialog. Luckily, we can keep them.
The assumption: client and server are separate systems. Server is HTTP-server based, client is housed in a browser (located on the same webserver as the server app) and connects to server via XMLHTTPRequest. (Non-browser clients will be able to connect via normal HTTP libraries.)
Step one: the login form. The onsubmit function of the form takes the username and password and fires off an XMLHttpRequest with those credentials to some arbitrary part of the server that requires authorization (and prefereably incurs little to no processing on the server), let’s say ‘GET / HTTP/1.1′. If the request comes back unauthorized, we just add a ‘login failed’ message to the form and let the user try again. If the request succeeds, we’re in. We can then take the credentials, base64 encode them (not to encrypt them, just to make sure they’re in a clean set of characters) and jam them into a cookie in the browser. JavaScript can read and write cookies, you see.
Now, we set document.location to the welcome page or whatever is suitable. From the user’s perspective there’s no difference between this and the page refresh, server-side session management way of doing things. But on the server life is completely stateless and free. From now on, when the user clicks something that requires a call to the server, you get the cookie from the browser and pass the credentials to XMLHttpRequest. When the user clicks logout, jsut delete the cookie and set document.location to the login page.
The really nifty part is that if you’re careful, you can have your cake and eat it too. JavaScript-enabled browsers get to talk directly to the server and format the data locally. Non-JavaScript-enabled browsers won’t see the onsubmit handler. So we can make the target of the form the authentication part of an old-style server-side session application if we need to support browsers without JavaScript.
The one thing we lose by making the server completely stateless is the ability to track logins and logouts. Every other activity is still log-able in a stateless system. If we need to track sign-in/sign-out, there’s noting stopping us from having a resource on the server that when accessed records a login or logout. For instance, instead of requesting / to test the credentials, POST to /user/login. If the request is authorized, the server can get the user’s information from the login/password tuple and record the login. When the user clicks logout, POST to /user/logout and do the same thing. The server is still stateless and we get a complete activity log.
Because the cookie our javascript is using never actually leaves the user’s computer, we can happily eat up as much of the 4KB size limit as we want without worrying about transmission time. So if there’s any small pices of data we’d like to cache, we’ve got a quick and easy place to do it.
My main motivation for this is that stateless servers are far, far easier to write and the client-side code is going to have to know the state anyway. So why not push the state all the way out to the client. Or, as we used to say: push the processing to the edge of the network. But I’m sure that sort of phrase is ‘Web 2.0′-ish enough…
