Here's a funny story.
About six years after I wrote this blog post, detailing an idea I had for managing persistent login cookies, a post that was linked to from all over the place, implemented in a couple of high profile libraries and that still gets me referrers (mostly these days from Stack Overflow. Hi!); after six years of being embarrassed that the products I worked on used a "remember me" mechanism I felt was demonstrably inferior, I finally got around to implementing the algorithm in real code.
It didn't work. It quite spectacularly didn't work. Because of concurrency.
I think there's a lesson here about the difference between theory and practice, and the hubris of a young blogger naming something "Best Practice" that he hadn't even tried yet.
And that concurrency is, and always will be, a bastard.
Persistent login cookies are the cookies that are stored with your browser when you click the "remember me" button on the login form. I would like to be able to say that such cookies are obselete, and we have a better way of handling user logins, but they aren't, and we don't.
The following recipe for persistent cookies requires no crypto more powerful than a good random number generator.
- Cookies are vulnerable. Between common browser cookie-theft vulnerabilities and cross-site scripting attacks, we must accept that cookies are not safe
- Persistent login cookies are on their own sufficient authentication to access a website. They are the equivalent of both a valid username and password rolled into one
- Users reuse passwords. Hence, any login cookie from which you can recover the user's password holds significantly more potential for harm than one from which you can not
- Binding persistent cookies to a particular IP address makes them not particularly persistent in many common cases
- A user may wish to have persistent cookies on multiple web browsers on different machines simultaneously
The cookie should consist of the user's username, followed by a separator character, followed by some large random number (128 bits seems mind-bogglingly large enough to be acceptable). The server keeps a table of number->username associations, which is looked up to verify the validity of the cookie. If the cookie supplies a random number and username that are mapped to each other in the table, the login is accepted.
At any time, a username may be mapped to several such numbers. Also, while incredibly unlikely, it does not matter if two usernames are mapped to the same random number.
A persistent cookie is good for a single login. When authentication is confirmed, the random number used to log in is invalidated and a brand new cookie assigned. Standard session-management handles the credentials for the life of the session, so the newly assigned cookie will not be checked until the next session (at which point it, too, will be invalidated after use).
The server need not make the effort of deliberately trying to avoid re-assigning random numbers that have been used before: the chance of it happening is so low that even if it did, nobody would know to make use of it.
When a user logs out through some deliberate logout function, their current cookie number is also invalidated. The user also has an option somewhere to clear all persistent logins being remembered by the system, just in case.
Periodically, the database is purged of associations older than a certain time-period (three months, perhaps: the size of the table would be far more an issue than any possibilities of collision in a 128 bit random space).
The following user functions must not be reachable through a cookie-based login, but only through the typing of a valid password:
- Changing the user's password
- Changing the user's email address (especially if email-based password recovery is used)
- Any access to the user's address, payment details or financial information
- Any ability to make a purchase
If the login cookie is compromised, the attacker has access to the common functions of the site as that user. This is inevitable whatever the cookie contains. However, the attacker can not:
- Access sensitive user information
- Spend the user's money
- Recover the user's password and try it on other sites
- Prevent the user from receiving notifications from the site of things that may have been done in their name
- Share the stolen login with others
The mutating nature of the cookie also provides a much smaller window of opportunity for an attacker to exploit a stolen cookie, and means the attacker must be far more careful they don't end up with a useless set of credentials.
Update: Barry Jaspan suggests an addition to the protocol that would further reduce the window of opportunity for stolen cookies: if a cookie that has been known to be used before (and thus invalidated) is presented, treat it as evidence of an attack and invalidate all saved logins for that user.