web/KeyPlusAuthenticator written at 01:29:18; Add Comment
The 'key plus authenticator' pattern in web apps
As part of our account request system, I need to generate an unguessable URL for a particular account request. This is a widely solved problem, because it's a variant on session IDs; you generate a large random number, encode it to ASCII somehow (base64 is popular), and put it in the URL as the identifier of the particular object. When you get an incoming HTTP request you extract that portion of the URL and use it as the key to do a database lookup to find the record you want.
However, there's an issue. Database keys have to be completely unique, but large random numbers are merely almost certainly unique (cue the birthday paradox again). Since I do not enjoy debugging weird database errors that happen once in a blue moon, I decided that I wanted to do database lookups with something that was guaranteed to be unique; in this case, a primary key. But I still wanted the URL to be unguessable.
My solution was to use what I'm calling a 'key plus authenticator' pattern. I embedded in the URL both the account request's primary key (which is unique but easily guessed) and a large random number (encoded to base64) that I call the access hash. When a URL comes in I extract both pieces of information, use the primary key to look up the account request, and then check that the account request has the right access hash.
An attacker can easily guess potentially valid primary keys but they can't get access to anything unless they also get the access hash correct (they can't even tell whether or not the primary key exists; I return an identical 404 error in both cases). With enough bits in the large random number this is just as secure as using the large random number alone. I call this the 'key plus authenticator' pattern because demonstrated knowledge of the access hash has served to authenticate your knowledge of the guessable primary key.
(You may well want to store the authenticators in your database in some hashed form, for the same reason as session IDs. We don't do so in the account request system for a number of reasons, including that I only found out about the session ID issue after I'd written the system.)
* * *