How to Securely Implement a "Remember Me" Feature?

My answer is incomplete and has a nontrivial flaw, in some situations - please see @Scott's answer instead.


There are two parts to my answer:

First, assuming your threat model does not worry about exposure of client side cookies, you can generate and store a nonce on the server side, hash that with the username and other info (e.g. client ip, computername, timestamp, similar stuff), and send that in the cookie. The nonce should be stored in the database, together with expiry date, and both checked when the cookie comes back.
This should be a "rememberance" cookie only, NOT a session cookie - i.e. when you get that cookie without a session, reissue a new, regular session cookie. Also note that like the session cookie, this should be delivered only over https, and using the secure and httpOnly attributes, and of course have the cookie scoped properly etc.

Second, for users that were remembered, you should (probably, depending on sensitivity) invoke a reauthentication process for certain sensitive operations. That is, sometimes they would need to put their password in again anyway, but seldom, and only for sensitive operations. This is to prevent attacks such as CSRF and shared desktop exposure.


I've written at great length about secure logins and "remember me" checkboxes. The accepted answer isn't wrong, but I'd argue that it's a bit more complicated in one respect than it needs to be, and neglects an area where a little bit more complexity is needed.

generate and store a nonce on the server side, hash that with the username and other info (e.g. client ip, computername, timestamp, similar stuff), and send that in the cookie. The nonce should be stored in the database, together with expiry date, and both checked when the cookie comes back.

So an implementation that doesn't expose any information to the client might look like...

// Storage:
setcookie(
    'rememberme',
    hash_hmac('sha256', $username . $_SERVER['REMOTE_ADDR'], $storedNonce),
    time() + 8640000 // 100 days, for example
);
// Validation:
$valid = hash_equals(
    $_COOKIE['rememberme'],
    hash_hmac('sha256', $username . $_SERVER['REMOTE_ADDR'], $storedNonce)
);

The obvious limitation here is that, if your IP address (or other information) changes, your cookie is useless. Some might see this as a good thing, I see it as needlessly hostile towards usability for Tor users.

You can certainly interpret the above quote to mean something different, like a poor-man's JSON Web token, too. However, HTTP cookies are limited to 4 KiB per domain. Space is at a premium.

Another problem: How much information does your database query leak about your application? (Yes, I'm talking about side-channels.)


Paragon Initiative's Secure "Remember Me" Strategy

Storage:

  1. Generate a random 9 byte string from random_bytes() (PHP 7.0+ or via random_compat), base64 encode it to 12. This will be used for database lookups.
  2. Generate another random string, preferably at least 18 bytes long, and once again base64 encode it (to 24+). This will actually be used for authentication.
  3. Store $lookup and hash('sha256, $validator) in the database; of course associated with a specific user account.
  4. Store $lookup . $validator in the user's HTTP cookie (e.g. rememberme).

Validation (Automatic Login):

  1. Split the cookie into $lookup and $validator.
  2. Perform a database lookup based on $lookup; it's okay if there's a timing side-channel here.
  3. Check hash_equals($row['hashedValdator'], hash('sha256', $validator)).
  4. If step 3 returns TRUE, associate the current session with the appropriate user account.

Security Analysis:

  • What side-channels are mitigated?
    Most significantly: it mitigates the impact of timing information leaks on the string comparison operation used in the database lookup.

    If you implemented a naive random token authentication, an attacker could send a lot of requests until they find a valid authentication token for a user. (They probably wouldn't be able to select which victim they're impersonating.)

  • What if an attacker can leak the long-term authentication database table?
    We stored a SHA256 hash of the $validator in the database, but the plaintext for the hash is stored in the cookie. Since the input is a high entropy value, brute-force searching this is unlikely to yield any results. (This also mitigates the need for e.g. bcrypt.)

This strategy is implemented in Gatekeeper.


That's an interesting question. I'll start off by saying that I'm not sure it can be done without some weakening of the security of the application, but then depending on the site that may be considered worth the trade-off.

So one approach could be

The session state database will need to record the time that a token is set and the duration that it should be remembered so that it can compare the token presented.

When the user logs in to the application they have a check-box which allows them to stay logged in (ideally with a range of time periods for the user to select)

When the session token is set, that time period is used as the cookie expiry. On subsequent visits the cookie will be presented to the application, and the server will need to check whether it is still valid (ie the expiry period for the remember me function hasn't been hit). If the token is still valid, the user is treated as authenticated, if not then the token is cleared and the user is redirected to the login screen.

Problems with this approach. Shared browsers would mean that the unauthorised access is possible.

Also anyone that can get access to the cookie (either through a vulnerability in the site, a browser issue or through malware planted on the machine) will be able to get unauthorised access to the site. One common suggestion for mitigating this is trying to tie the cookie to a source IP address (encrypted or stored on the server of course) which is checked when the user presents the cookie, however this causes problems with large corporate environments where a user may come from a number of IP addresses, and also may be susceptible to spoofing.