Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Race Condition for Persistent "Remember Me" Cookies

According to Persistent Login Cookie Best Practice, you should never allow a "remember me" token to be used more than once:

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).

Then, how do you handle the race condition where a user is visiting multiple URLs at your site at the same time? I'm actually having this problem right now.

Let's say two requests are sent from the browser to the server at the same time. The requests contain no session cookies, but the same "remember me" cookie. One of the requests will be handled before the other, and will get a response with an authenticated session cookie and a regenerated "remember me" cookie.

The "remember me" token in the second request is now invalidated and another session ID is generated on the server. This request fails, since the user cannot be authenticated.

I have come up with a few possible solutions, but none of them seem very good. Am I missing something?

like image 262
Christian Davén Avatar asked May 10 '11 10:05

Christian Davén


2 Answers

Old question but I didn't find the answer anywhere. I had the same problem. My solution was to store the old token on the database and use it as a fallback if the main token is not found. But I made sure the old token is only valid for a short time frame, like several seconds after the token changes. Then I only change the token if some time has passed since the previous update, otherwise there will be cases when the token changes several times in a row.

like image 84
vangoz Avatar answered Sep 21 '22 09:09

vangoz


To elaborate on vangoz's answer, I would add that one has to employ some sort of locking mechanism too. Consider this PHP-ish pseudocode here:

if (isFallback($token)) {
  // Log the user in
} else {
  // Usual processing
  // If token has to be updated, save old token as fallback for a few seconds
}

When there are concurrent requests they might both end up in the else branch, one request will cause the update and the other will cause the token to be invalidated. The way I solved this was to use a named lock, named after the $token in question, to wrap the else branch. Also, all concurrent requests but one will fail to acquire the lock, in which case we sleep a bit and retry (upon retrying we will find the token to have become a fallback token).

if (isFallback($token)) {
  // Log the user in
} else {
  $couldLock = lock($token);
  if (!$couldLock) {
    usleep(10000);
    // Retry, possibly a recursive call
  } else {
    // Usual processing
    // If token has to be updated, save old token as fallback for a few seconds
    unlock($token);
  }
}

I hope these considerations can be useful.

like image 40
nicolagi Avatar answered Sep 18 '22 09:09

nicolagi