Improved Persistent Login Cookie Best Practice
Charles Miller's article, "Persistent Login Cookie Best Practice,"[1] describes a relatively secure approach to implementing the familiar "Remember Me" option for web sites. In this article, I propose an improvement that retains all the benefits of that approach but also makes it possible to detect when a persistent login cookie has been stolen and used by an attacker.
Review
To summarize Miller's design:
- When the user successfully logs in with Remember Me checked, a login cookie is issued in addition to the standard session management cookie.[2]
- The login cookie contains the user's username and a random number (the "token" from here on) from a suitably large space. The username and token are stored as a pair in a database table.
- When a non-logged-in user visits the site and presents a login cookie, the username and token are looked up in the database.
- If the pair is present, the user is considered authenticated. The used token is removed from the database. A new token is generated, stored in database with the username, and issued to the user via a new login cookie.
- If the pair is not present, the login cookie is ignored.
- Users that are only authenticated via this mechanism are not permitted to access certain protected information or functions such as changing a password, viewing personally identifying information, or spending money. To perform those operations, the user must first successfully submit a normal username/password login form.
- Since this approach allows the user to have multiple remembered logins from different browsers or computers, a mechanism is provided for the user to erase all remembered logins in a single operation.
The Problem
Miller correctly describes the many advantages of this approach. One disadvantage, however, is that if an attacker successfully steals a victim's login cookie and uses it before the victim next accesses the site, the cookie will work and the site will issue a new valid login cookie to the attacker (this disadvantage is far from unique to Miller's design). The attacker will be able to continue accessing the site as the victim until the remembered login session expires. When the victim next accesses the site his remembered login will not work (because each token can only be used one time) but he's much more likely to think that "something broke" and just log in again than to realize that his credentials were stolen. Displaying a "last login time" may help the user notice the problem but, frequently, it will go undetected.
One possible solution to this problem is to treat the presentation of an invalid login cookie as evidence of a previously successful attack. The site could then present an impossible to miss security warning and automatically invalidate all of the user's remembered login sessions. This approach would create a denial of service attack: since usernames are easy to come by or guess, an attacker could submit invalid login cookies for every user and thus disable the entire system.
The Solution
My solution to this problem is based on the observation that since each token can only be used once, a remembered login session actually consists of a series of such tokens. When an attacker successfully steals and uses T_0 he is issued T_1, but the victim is still holding T_0. The database no longer has a copy of T_0 and thus cannot differentiate it from an arbitrary invalid token.
However, if the series of tokens is itself given an identity that must be presented along with the current token, the system can notice that the victim is presenting a valid series identifier along with an invalid token. Assuming that the series identifiers are as hard to guess as tokens, the only way a user could present a valid series identifier with an invalid token is if some other user previously presented the same valid series identifier with a valid token. This requires that two different users held the same series and token pair at the same time and therefore indicates that a theft has occurred.
The implementation is no more difficult and requires no more resources than Miller's design. From the summary above, only items 2 and 3 change:
- When the user successfully logs in with Remember Me checked, a login cookie is issued in addition to the standard session management cookie.[2]
- The login cookie contains the user's username, a series identifier, and a token. The series and token are unguessable random numbers from a suitably large space. All three are stored together in a database table.
- When a non-logged-in user visits the site and presents a login cookie, the username, series, and token are looked up in the database.
- If the triplet is present, the user is considered authenticated. The used token is removed from the database. A new token is generated, stored in database with the username and the same series identifier, and a new login cookie containing all three is issued to the user.
- If the username and series are present but the token does not match, a theft is assumed. The user receives a strongly worded warning and all of the user's remembered sessions are deleted.
- If the username and series are not present, the login cookie is ignored.
It is critical that the series identifier be reused for each token in a series. If the series identifier were instead simply another one time use random number, the system could not differentiate between a series/token pair that had been stolen and one that, for example, had simply expired and been erased from the database.
Conclusion
This system has all the advantages of Miller's original approach. Additionally:
- An attacker is only able to use a stolen cookie until the victim next accesses the web site instead of for the full lifetime of the remembered session.
- When the victim next accesses the web site, he will be informed that the theft occurred.
This system is used by the Persistent Login module for the Drupal content management system.
Notes
[1] Miller, Charles. Persistent Login Cookie Best Practice, http://fishbowl.pastiche.org/2004/01/19/persistent_login_cookie_best_practice. January 2004.
[2] For the most secure result, the standard session management cookie should be a "session" cookie that expires as soon as the Web browser is closed. Furthermore, the server should enforce a fairly short maximum lifetime on sessions even if the browser remains open.
Comments
Another improvement would be
Another improvement would be only to issue Persistent Login cookies via SSL with the 'secure' flag set, only accept them via SSL, and to invalidate any series token received via non-SSL. For sites that are not 100% SSL, this would require issuing another non-SSL cookie that says "I have a Persistent Login cookie." When the site received that cookie, it would redirect the user to an SSL landing page that would receive the SSL-only login cookie, validate it, issue a normal session cookie, and redirect back to original URL.
I'll get to this at some point.
How does this resolve the
How does this resolve the use of multiple web browsers and/or multiple computers a person might use with the "remember me" feature? Does this scheme functional at all if I log in from different computers?
Multiple web browsers and/or
Multiple web browsers and/or multiple computers work perfectly. You have a separate remembered login in each environment.
Nice. I'll be implementing
Nice. I'll be implementing something like this, seems like it'll work very well!
I like your idea, it is
I like your idea, it is refinement, I try this under different browser, it work fine always. very great. Thanks jaspan.
That sounds like a
That sounds like a reasonable suggestion. I've linked here from the original article.
Thank you for this article!
Thank you for this article! Your solution works very good. I have one small question, though:
1. When the user logins with "Remember me" checked, the new record in a database is created (with username, series and token).
2. Now, he clicks "Logout".
3. Then, once again, he logins with "Remember me" checked. The new record in a database is created (with username, new series and new token).
So, user can repeat steps 2-3 and create as many new database records as he wants... I'm afraid this is no good ;)
Do you now any solutions to this problem?
Clicking Logout deletes your
Clicking Logout deletes your current PL record from the database, so if the goal is to fill up the database with useless PL records, that won't work. However, the user could simply log in, erase all session cookies, and log in again over and over to achieve the same effect.
If this becomes a problem, it would be a simple matter to limit the number of unique PL records in the database per user.
IP Address Limitations: It
IP Address Limitations:
It seems to me that a further improvement to this practice would include an optional IP address limitation as accomplished with Bugzilla. This would not work for systems with floating IP addresses, however, it further complicates hijacking a user's information enough that most wouldn't bother because the attacker would have to hijack the users information *and* IP address in order to be successful even just temporarily.
IP address restrictions
IP address restrictions would make the system not work with mobile users (e.g. anyone who uses more than one wifi network). That is an unacceptable cost for the minimal security benefit.
Great article about
Great article about persistent login cookie. i try this idea under multiple browsers/computers and this worked perfectly. thanks!
A problem that i have with
A problem that i have with persistent login is when returning to the site in ff i get an error message of page not found but i am logged in. This unknown problem is perplexing though I do think that would be nice to be able to select a default page to login to for the users on the site. Any suggestion about former problem or how to implement that latter would be greatly appreciated.
If you have problems with
If you have problems with the Persistent Login module for Drupal, please submit a bug report at drupal.org.
Hi, problem I implemented
Hi,
problem
I implemented this but it breaks down when a user quickly clicks on a few links on the web page. The server is returning the cookies out of the expected order since each link takes a different amount of time to render. So at some point the browser and server get out of sync and the token is detected as not being valid.
Anyone else have this happen?
It sounds like you are
It sounds like you are issuing a new persistent login cookie for every request. Instead, establish a session (via a separate cookie) and only issue a new PL cookie once per session. Don't use PL as session management, just as re-authentication.
Note that there is no
Note that there is no facility to request a 'persistent cookie' as this provides a minor security risk. It is up to you to decide whether a permanent cookie is acceptable in your application domain.
Solution works perfect.
Solution works perfect. Thank you!
I was wondering if it is
I was wondering if it is possible to leave the username out of the cookie with this approach and thus avoid exposing a valid login name.
There should only be one valid username/token combination in a series at any one time so the username should be unique for a given series provided you can guarantee that two users aren't randomly assigned the same series id (highly unlikely, but possible). The correct username (and token) can therefore be obtained by looking up the database with the series id.
In systems which have limited persistent login functionality and which may not expose the username or user's real name as part of this, it may still be useful for privacy and security to avoid having the username in the cookie.
Or have I overlooked another reason why it needs to be stored in the cookie, apart from guaranteeing uniqueness? Any concerns about the size of the space without the username could presumably be mitigated by expanding the series id space.
One issue that has not been
One issue that has not been covered or discussed is changing passwords. If a user realizes that someone else (aka attacker) is logging into his account, the first thing he will do is change his password. However, if the attacker still has a valid cookie then the password change will not accomplish anything. All cookies should be invalidated for a user when they change their password.
Luke at 10/31/07 12:20:
Luke at 10/31/07 12:20: Offhand, no, I can't think of a reason that including the user id in the PL cookie is necessary. If only a token were used and not a series identifier, leaving out the user id would allow an attacker to submit random guesses at tokens and if any user had that token the attack would succeed. Since my approach uses a series and token pair, the attacker would have to guess both for a single user simultaneously; including the user id provides no extra security in this case. Note, however, that in the Drupal implementation at least, the "user id" that is included in the cookie is the numeric id (uid) assigned to the user, not the user's login name. The uid cannot be used to log in and reveals very little information from a privacy perspective, so I'm not sure how important this issue is. But if your site has usernames without a similar associated user id, leaving the username out of the PL cookie might be a good choice.
Anonymous at 10/31/07 17:13: Yes, when a user changes his or her password, all existing PL series should be destroyed. Thanks for documenting this requirement. The Drupal implementation already does this.
@bjaspan Thanks for the
@bjaspan
Thanks for the response. I was having a go at implementing an improved persistent login implementation in Java for the Acegi Security framework, based on your approach, and it occurred to me that the username probably wasn't needed. I'm always a bit paranoid about these things though and like to get a second opinion :). We will generally be dealing with an actual login name, since the framework doesn't enforce any particular persistence mechanism so leaving the username out will be useful.
Even with the single token (and no series) approach, couldn't the username be safely omitted by expanding the token size to make the new token space as big as the original combined username+token space?
I guess I can't think of a
I guess I can't think of a reason that the PL cookie needs to explicitly include the user name or id. If the series and token values are wide enough to prevent guessing then they are wide enough to prevent guessing; including the user name/id does not make them any more unguessable.
Do you have any suggestions
Do you have any suggestions for the format of the series and token values? I'm trying to figure out how to best address the creation of these fields while maintaining uniqueness and un-guessability/un-hackability (in a LAMPhp site).
So, for example, an md5 hash of username or userid (or anything unique to the user) isn't ideal because hashes of different strings aren't always unique, and I'm designing for many millions of records. We could use PHP's uniqid() function, but I don't think that's guaranteed to be unique across multiple webservers either (also need to design for scalability to multiple servers). We can't use mysql's SELECT UUID() because that's guessable. I could prepend or postpend the UUID() value with a random string. But that seems like overkill.
I was also considering prepending uniqid() with the IP address string. But this site will live in amazon's ec2, where instances often die and get restarted. And I don't know the effect on on the uniqueness of uniqid() if a server gets completely re-instantiated into the same IP address.
I see a lot of intelligent conversation going on in the comments section here. Would love to get some of your thoughts on this.
You mention in your notes
You mention in your notes and on a comment that session should be stored through standard session management cookies and that this technique is really meant for re-authentication, not session management.
But... why not use this to manage sessions too? It would make the sessions much more secure. And pretty much the same work happens on each pageload of a site that makes use of sessions (php sessions, at least). It seems to me that the only significant difference to this approach is that you would be setting a cookie on each pageload. And I think that can be a relatively slow process.
Thoughts?
The series and token values
The series and token values do not really need to be strictly unique; they just need to be unguessable. If two users out of many millions spread over several web servers happen to have the same series or token value, so what? They are still keyed to the user id and must all be presented simultaneously so unless an attacker knows which other user has the same series/token values, the fact that there might be a collision doesn't matter.
Drupal's implementation uses
md5(session_id() . uniqid(mt_rand(), true) . $private_key)where $private_key is a per-install value initialized tomd5(uniqid(mt_rand(), true)) . md5(uniqid(mt_rand(), true)). The connection with session_id() is irrelevant and probably not even helpful from an unguessability standpoint (since the session id is revealed in plaintext), it's just "more stuff" in the hash.Regarding using the PL technique for every page request, sure, you could. It adds an extra select and update query but with proper table design they can be made very fast. I just have an aversion to sites that set cookies on every page dating from the late 90s when I developed and sold the product interMute, the first commercial cookie (and web ad, pop-up, animated image, etc...) blocker. :-)
One more feature request..
One more feature request..
I have installed this module and it works great, thanks much. I guess one more feature that other login systems have with "remember me" is giving the user two options:
1. [ ] Remember me
Meaning remember only my username and fill it in the form but not my password, I want to type that in myself every time, and:
2. [ ] Log me in automatically
Which is only available if Remember me is checked and this is what actually keeps you logged in over multiple sessions.
In fact whenever I have these options I always go with just "Remember me", ie don't make me type my username every time (especially if it's a long one, like email address) but I prefer to supply my password every time, it's a little "scary" otherwise.
It's a great module as is but I think with this extra feature, and the admin option to select either the two options or the "traditional" simple "remember me" would really be a complete throw-away-everything-else Login module.
Yiannis, yes, but using
Yiannis, yes, but using "remember me" feature is quite comfortable :)
So if I am understanding
So if I am understanding this correctly the series is just a random number that is generated a cookie creation time? Or is it an ordered number such as 1,2,3..... that you randomly start at?
Maybe I am just not understanding how the series is supposed to work.
- Josh
The series is a randomly
The series is a randomly chosen number. It is chosen when the user logs in and clicks Remember Me, and is preserved when the user returns and is automatically re-logged in via the series+token.
The token is a randomly chosen number. It is chosen when the user logs in and clicks Remember Me, and is re-chosen every time the user returns and is automatically re-logged in via the series+token.
Yes I notice this as well.
Yes I notice this as well. Sometimes I do not even log in but I am already logged in.
Also one time I could not log in even thought it says I was logged in. :/
Very confusing.
________________________
Discount iPod Software
This is marginally safer!
This is marginally safer!
If you are thinking about security think like an attacker. What would you do to circumvent this system?
I would change or erase the cookie I just stole. How easy is that. I have access to it so I can probably change or erase it.
When the user logs in after I stole and erased his/her cookie he/she will just be prompted to login again and a new serie will be started by the server.
And I, as the attacker, can enjoy my one private serie :o)
So watch out this refinement may make you feel more secure but it isn't.
Thomas
great solution. works
great solution. works perfectly
Kevin
Hi Barry, Thanks a lot for
Hi Barry,
Thanks a lot for this article. I read a lot about cookie authentication (including Charles Miller's post) and adamant now that there should be very real reason to have it as implementation is quite complicated and anyway doesn't eliminate an ability to use stolen cookie. Even one theft can be disastrous for some systems.
___________
Download Software Tools
Good post but the solution
Good post but the solution presented here do not help you avoid attackers to steal and use your cookies. Just minimize the impact until you login next time. And of course provides a simple way to let you know you have been visited by mean people.
This issue will still remains a concern for web development and a final solution to block attackers is needed.
What about encrypting the
What about encrypting the username
Thanks for the really useful article.
If we encrypt the username the attackers (for DoS attacks) won't be able to guess usernames.
And when a non-logged-in user visits the site and presents a login cookie we decrypt the username, search for (username, token) in the database.
If username exists but token does not exist, we can conclude that the login cookie is stolen and we reset user session.
The overhead of this approach is to encrypt&decrypt the username. There is no need to maintain a series ID.
What do you think?
Hi, The Persistent login
Hi,
The Persistent login module only works randomly for my users. They have to login again every few hours or days, with no consistent pattern ("Days to remember the user" is set to 100).
Is this module still maintained? There doesn't seem to be any activity around it in d.o.
If not, are there other modules that keep users logged in? I couldn't find any, which is surprising for such a basic feature.
Thanks,
Vianney Stroebel
Co-org.net - Développement Drupal - Paris.
Awesome, I was planning on
Awesome, I was planning on writing something like this, looks like I don’t need to!
Is it true that using the PL
Is it true that using the PL technique for every page request adds an extra select and update query but with proper table design they can be made faster?
No. My technique requires
No. My technique requires one extra select per *session*, not per page request. And yes, with proper table design, it is a single primary-key select and so is quite efficient.
I'm a little unclear on when
I'm a little unclear on when (or if) the series ever gets reset or changed. You mention that if the cookie is compromised, that one would expect to see a non-matching token from the same series when the real user returns to the site. At this point it would seem that you should force the real user to authenticate with username and password. If they authenticate successfully, then delete all existing cookies for that user and series, and re-issue a new cookie to the user. Does this new cookie start with a new series number? Perhaps that was want you meant all along, but I didn't see it explicitly stated.
If that is true, then there are at least two reasons why a username (or persistant user identifier which never changes) is important:
1) It allows you to associate multiple machines with the same user (these will each have different series).
2) It is the only element which persists when the series value changes (after receiving a cookie with a valid series, but invalid token).
I suppose you could accomplish both of these without username IN the cookie, but I agree that it strengthens the solution against brute-forcing.
-Scott
Reducing the window of
Reducing the window of attack or making a system in which attacks are easy but can later be detected is an exercise in futility. It is possible (and relatively easy) to write a cookie that cannot be stolen and then used somewhere else in the first place. I'm sure users would prefer not to allow others access to their account rather than just to be told that it has happened since their last log in.
For example, the cookie can be securely encrypted even without having an SSL certificate (the server performs both the encryption and decryption, why bother telling the client your public key in the first place) and contextual information can be added inside this to make sure that the cookie can only be used from this context. This could be as strict or vague as you like, as a very simple example it could include the User-agent header (which would mean that you would need to log in separately for different browsers even if they share the cookie). This requires absolutely no data to be stored on the server but could be extended to record further details such as this sequence number on the server if you /really/ wanted to.
The only way that this can be bypassed is to use the cookie in the same context as it was originally set, in which case, the attacker could have just used the same method of logging in as the normal user does (via the normal user's web browser), because you can never actually tell who is on the browser without them entering some security information each time, no matter how strict the context is.
Nice. I'll be implementing
Nice. I'll be implementing something like this, seems like it'll work very well!
If the username is in the
If the username is in the cookie, only logins for that username can be guessed. If the username is not in the cookie one request can be a guess for all users.
So, if you have 2 persistent logins and there are 2^128 posibilities that's very unlikely. If you have a total of 5000 persistent logins from all users, it is a bit more likely.
Its a great solution but it
Its a great solution but it wouldn't function remembering multiple users, i think
Good solutions, and good
Good solutions, and good comments review, I will implement it in my site
--
Chuletas y apuntes
I first read this article a
I first read this article a few weeks ago and I really like it. I implemented the Persistent Login practices on my site currently in development, and it works fine to a point. For some unknown reason (been racking my brain over this), every so often I get the "Your cookie may have been stolen" message. There are three devices I've been using it with (home comp, work comp, mobile comp), but with 3-4 different browsers on a couple of those, that's probably 8 total places the cookie gets stored. However I don't see how that would matter since you have to log in from each one, creating a separate series ID at each login and different token ID at each new remembered session. I'm using the username to look up the series ID and token ID in the PL table, and random 10-digit numbers (for now).
Anyone else have any issues like this?!?!?
@bjaspan: In the footnote you
@bjaspan:
In the footnote you say:
"Furthermore, the server should enforce a fairly short maximum lifetime on sessions even if the browser remains open."
Can you clarify this? It seems like a conflict.
1. The user has a session that will expire, say in a few hours, forcing the user to login again.
2. The user has opted to save a login cookie, providing auto-login.
So, when session expires, the login cookie auto-login bypasses any forced re-authentication. I don't see how shortening the session lifetime helps security.
Thanks.
Post new comment