Lightweight, Decentralized Authentication
0
An essential feature of all respectable web frameworks is a reliable user authentication mechanism. Nevertheless, one might be tempted to write his own from scratch, and the reasons are many. It could be that you don’t like the way your favorite framework does the task, or you might feel a need to tweak it a little bit. Maybe you’re too paranoid to let a third party handle it for you! You might not be using a framework in the first place, or you might even be writing your own!
In this post, I’m going to discuss a simple mechanism that has served us well in several projects. It’s straightforward and flexible enough to be used in almost any project, but just like any solution to a software problem, there’s no such thing as a silver bullet; you have to choose the right tool. So, before we go any further, let’s first state precisely what we’re looking for in our sought solution.
- It has to be at least as secure as regular password-based authentication.
- It has to be lightweight, minimizing database I/O as much as possible, so that it can be done for each and every request without adding too much overhead.
- It should require minimum communication between server nodes.
- It shouldn’t bind a user to a specific machine, allowing requests originating from the same user to be transparently handled by different machines.
It all starts off with a simple and familiar step: the user sends his login and password, which are then compared to the stored ones. From that point, things take a slightly different path. Remember that authentication is part of each and every request. Done the usual way, this means an expensive db query at the beginning of each request. Our goal here is to convert this into a lightweight, on-the-fly step. So, how can this be achieved?
Consider this: what if after verifying that the login credentials provided by the user are actually correct, the user is given a little box that can never be opened (well, let’s say very hard to open) except by the one who made it, and which contains a secret message which no one knows, except for the one who wrote it. For each of the following requests, instead of providing the login & password again, the user hands back the box. By looking at the enclosed message, we can instantly determine whether it’s a authentic or fake, depending on whether the contents match the secret we know or not.
Of course, in programmers’ jargon, that box translates to a secret message encrypted at the server using a symmetric key, which will be referred to in the rest of this post as the authentication token, or just as token. Depending on the size of the secret message and the encryption algorithm used, decrypting the message should be much faster than querying the user’s password from the database (from my own experience, the ratio can be something like 1:20 using Triple DES and a secret almost 100 bytes long, but your mileage may vary).
This can be implemented in a myriad of ways, and it’s not tied to any technology in particular. Actually, the concept is used over & over in numerous places so I’m not claiming it’s new. I’m just showing how you can fit it inside your application.
For that purpose, and since the web community is currently rediscovering HTTP with the rise of RESTful web services, I’ve chosen to stick as faithfully as possible to the HTTP protocol throughout my demonstration, giving examples on how the same can be achieved in different scenarios.
Using HTML forms to send login credentials is known to everyone, but HTTP actually has built-in support for several authentication schemes (yes, I’m talking about that annoying modal dialogue that asks for password in some sites that require authentication). HTTP authentication works as follows: whenever a client accesses a URI that resides within a set of restricted pages (called realm in HTTP lingo), the client is challenged by the server. At that moment, the client is supposed to provide a challenge response. Depending on the authentication scheme, the challenge response can be one of several things. In the BASIC scheme, which is what we’re concerned with, the client should provide a base64 encoded string that when decoded should look like this: <username>:<password>
To simplify things, and since we’re assuming that each request has to be authenticated, the client is always assumed to embed a challenge response in each request. If the client fails to do so, the server should reply with 401 Unauthorized. On the other hand, if the client supplies a valid username/password combination, the server composes and sends the authentication token in a response header (a custom header that should preferably include your application name, for example, to avoid name collision). The encrypted token should contain the secret as mentioned before, but should also contain something unique to the user, such as the user ID in the database, or the username. Otherwise, tokens generated for different users will be identical, and that’s certainly not wanted!
On the other side of the wire, when the token is received by the client in the response, it should be extracted & used in the challenge response of each subsequent request, where the client will still use the BASIC scheme, but with the username omitted, so that the decoded challenge response should look like this: <empty string>:<token>
This is so that the server knows whether the client is using a password or a token.
Figure 1: State diagram describing how the client chooses the appropriate authentication method.
Figure 2: Flow chart showing what happens at the server when authenticating a request.
A disadvantage of this method is that there’s no obligation on the client to ever use the token; it can choose to send the password every time, and the server can only play along. However, if you have some control on how the client behaves, this won’t be an issue, but I’m saving that for another post.
Depending on the capabilities of the client (for instance, the subset of the HTTP protocol it supports, or if it’s not using HTTP altogether), you have the freedom to implement this in many different ways. For instance, you can pass back the token in a cookie, JSON object, XML response or whatever format you wish. Note, however, than an encrypted token will most probably have non-ASCII characters, so you might find it necessary to base64 encode it after encryption.
One very useful extension of this whole thing is to make the authentication token assume the role of the user session. If the session is small enough to be piggy backed on each request, then you needn’t keep anything at all about the user session on the server! This makes the server completely stateless, allowing user requests to be served from anywhere. If this is doable in your application, then it’s even better than session caching, because unlike a cache, it doesn’t require any maintenance. In my opinion, this is the most interesting aspect of this approach (and I believe this is the approach endorsed by Rails 2.0, by the way).
One overlooked issue, however, is: what if someone stole a user’s token by sniffing the victim’s packets? After all, it’s still an authentic token, isn’t it? Well, this is not different than password-based authentication. Both are equally vulnerable to that kind of attack. Your best call is to use HTTPS. Alternatively, but not as equally safe, is to add the user’s IP address to the token contents, so that the token is considered valid only if it’s coming from an IP that matches the one inside it. Finally, the key & secret used by the server to create the token should be regularly changed, and in that case, old tokens will cause the client to receive a status code indicating session expiration, and the cycle repeats again starting with password login.
Post a Comment
Archive
Latest Comments
- ibrahim Ahmed Commented on How to Avoid the IE Box Model bug
- Ishola Omoeko Commented on New Versions of Ruby, Rails and Instant Rails
- Fizo Commented on Ruby Fibers Vs Ruby Threads
- Michael Commented on IE6 Background Image Problem
- chrisb Commented on IE6 Background Image Problem

