Persistent Logins

Published in PHP Architect on 25 May 2005

Remember Me

Have you ever visited a web site and noticed a checkbox that says Remember Me directly underneath the form? This is the most common phrase used to describe this feature to the user, and there are two major implementations:

  1. The user's username is stored in a cookie, so the user only has to provide a password in situations where the user would otherwise be required to provide both a username and password.
  2. An authentication cookie is created that allows the user to completely bypass the next authentication. This usually means that the user is automatically logged in on the next visit and often up to a certain number of future visits.

This second implementation is called a persistent login.

Persistent Login

Because cookies are the only good source of persistence between browser sessions, they provide the foundation of most persistent login implementations. The most common mistake I have observed when auditing PHP applications that attempt to provide this feature is storing both the username and password in a cookie. It's easy to understand the temptation—you simply retain the access credentials and basically save the user the trouble of entering them. Of course, this approach has numerous risks, including the fact that the access credentials are subject to a drastic increase in exposure.

Your persistent login cookies should be temporary. As such, they should not be based on any information that provides permanent access. This primarily means that you should not be basing the cookie on the user's password. The username is far less sensitive, and the username is often public anyway. You can use this in your persistent login cookie to help you identify the user. The challenge, of course, is in authenticating the user with this cookie in such a way that an attacker has a very difficult time reproducing your efforts.

Begin with a simple idea: the authentication token. This is a random string that you associate with a single user, and you can generate a good random string with the following:

  1. $token = md5(uniqid(rand(), TRUE));

Because you need to associate this with a single user, you might be tempted to do so using sessions. While this is a good idea when your purpose is to protect against session hijacking, it doesn't help you persist logins across browser sessions. Therefore, you need to associate this token with the user in the database — typically in the same place that you keep the username and password.

With this idea, you now have something that can be considered a temporary password. This authentication token can be provided by the user to bypass the authentication step, and you should only allow an authentication token to be used once before it is considered expired.

In order for this to be useful, you need to keep it in a cookie. You also need the user to let you know the username associated with the authentication token, so that you can verify it. A good way to accomplish this is with a single cookie that has both:

  1. setcookie('auth', "$username:$token", time() + 60 * 60 * 24 * 7);

This cookie is set to expire in one week, and the value of it is the username and authentication token separated by a colon.

Because the expiry of a cookie depends on the user's computer having an accurate clock, you might consider having it expire in the distant future and keeping up with when you want it to actually expire on the server. A good place for this is in the same data store in which you associate an authentication token with a user.

If you implement this feature, there are some additional rules to follow. One is to never allow an authentication token to be used more than once. If you want a user to be remembered for a period of two weeks, for example, you will simply want to generate a new token after each authentication, and you can then set a new persistent login cookie.

Another good rule to follow is to require that the user provide a password for any sensitive transaction. The persistent login should only grant access to the features of your site that aren't considered to be extremely sensitive. There is simply no substitute for requiring users to verify their password immediately before an important transaction.

Lastly, you want to make sure that a user who logs out is really logged out. This includes deleting the persistent login cookie:

  1. setcookie('auth', 'DELETED', time());

This overwrites the cookie with a useless value and also sets it to expire immediately. Thus, a user whose clock causes this cookie to persist should still be effectively logged out.

Until Next Time…

Hopefully, you now see how you can provide this useful feature to your users without placing them at an unnecessary risk. Persistent logins are very convenient, but without proper guidance, they can create major security vulnerabilities.

If you have any features that you want to add to your PHP applications, but you're concerned about the security implications, feel free to drop me a line, and perhaps I'll discuss the issue in depth in a future Security Corner.

Until next month, be safe.