68

Assuming you already have a website that implements all of the standard login stuff, what is the correct and most secure way to allow users to automatically be logged in for a certain time period (let's say 30 days)? This time period should be strictly enforced; ie, I don't think a valid solution would be giving a cookie an expiration date and hoping the user's browser deletes it in a timely manner.

Details of the existing login system:

  • A user must enter a user name and password
  • The database stores usernames as well as strong_hash_function(password + user_specific_random_salt)
  • Login forms are loaded and submitted over SSL, as are all subsequent pages

There are many examples out there and many with obvious security flaws. Let's use PHP as the target language but the concepts should be applicable to any language.

2

6 Answers 6

34

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.

8
  • 6
    See Amazon for an example of the second part!
    – ase
    Commented Nov 19, 2010 at 11:54
  • 1
    @adamse, nice, I usually refer to LinkedIn. Amazon even better :)
    – AviD
    Commented Nov 19, 2010 at 12:11
  • Only the hash of the nonce should be stored in the database.
    – Jacco
    Commented Nov 30, 2012 at 0:44
  • 1
    I meant to say that, because the nonce is a password equivalent, instead of the nonce, only the hash of the nonce should be stored in the database. Offcourse you also need to store things like expiration date and/or userId, etc.
    – Jacco
    Commented Nov 30, 2012 at 13:23
  • 2
    My answer is incomplete and has a nontrivial flaw, in some situations - please see @Scott's answer, security.stackexchange.com/a/109439/33 .
    – AviD
    Commented Jan 18, 2016 at 23:39
30

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.

21
  • 1
    Thanks @scott - it took me a while to grok the importance of the timing attack here, but now I see you are correct! Also agree on the other points for the most part, but I'm not sure that's the way I intended it anyway :-). But yeah, the timing attack does make a load of sense, and all the difference!
    – AviD
    Commented Jan 18, 2016 at 23:36
  • 2
    I don't think it would help much. You're relying on the security of a random value for challenge-response authentication here. All a user agent check will do is make people get logged out every time their browser auto-updates, which would make them adverse to auto-updates, and keep them insecure. Commented Nov 12, 2018 at 18:49
  • 1
    9 random bytes is sufficient for this use case, so there's no need to balloon it up to 64 hex characters. So, yes, but there is no security reduction here. 12 characters in the base64 (or base64url) alphabet is fine. Commented Nov 19, 2018 at 23:34
  • 1
    Can you elaborate more on the timing attack? I don't think $Lookup is necessary. Let's say the cookie looks like rememberme=WBWgm2oMFxiR and in the database, the cryptographic hash of the cookie's value is stored. I understand that database lookups with two dissimilar values will take less time to fail than two similar values. Let's say an attacker guessed a cookie value that its hash almost matches a row in the database. Modifying their guess in anyway will result in a totally different hash according to en.wikipedia.org/wiki/Cryptographic_hash_function
    – No_name
    Commented Jun 23, 2020 at 9:09
  • 1
    What benefit does splitting the token in to two parts have over checking the entire token hash in the database AND THEN performing a redundant timing safe hash comparison on two securely generated hashes created from random_bytes and throwing away the result? Commented Jul 23, 2020 at 16:21
5

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.

4

You don't have to depend on the browser to delete the cookie, you have the site check the expiration date on the cookie.

In fact, for this to be secure, I'd say that's probably what you're going to have to do anyway, because you would want to set the expiration date as a part of the cookie value and encrypt it rather than rely on the plain-text expiration property of the cookie itself.

And you would really want to either mark the cookie as secure, and use a secondary cookie for the duration of an individual session, or protect the entire session (not just the login process) with SSL to prevent the cookie from being stolen off the wire and used by an unauthorized party.

1

This article describes an approach that defends against cookie theft and guessing of session IDs:

  • A "remember me" cookie consists of the user ID, a token (big random number) and a sequence token (another big random number).
  • When a user presents the cookie, the database is searched for these three pieces of information.
    • If found, the sequence token is regenerated, stored in the database and sent to the user
    • If only the username and the token are found, it is presumed that someone else has stolen the cookie because the sequence token was already used. The system can then warn the user and/or delete all stored tokens from this user from the database.

As an additional security measure it is advised that critical actions like changing the login data or paying for things request the password, if the user was only authenticated with the "remember me" cookie.

The database table that contains the user id and tokens also contains the expiration date of the tokens. It could also also contain the user agent and IP address so an attacker has to replicate these as well. All tokens should only be stored as hashes because they basically work like passwords and would enable an attacker, who obtained to database, to login.

I have created a sample implementation for PHP.

1
  • That sample implementation doesn't use a CSPRNG. :( Commented Jan 1, 2016 at 8:02
0

A common approach would be to use a strong encryption algorithm to store a cookie containing the following information on the user's computer:

AES([Expiry date] + [random salt] + [Username] + [Password])

That would allow the user to invalidate the cookie (if it was compromised) by simply changing their password. Encrypting it allows the site operator to verify that it has not been tampered with (extending the expiry date, for example), as well as protecting the clear text password from compromise. Including the password means that an attacker who gains access to the AES key can still not "mint" tokens to authenticate as arbitrary users.

The downside is then that the site has to protect an AES key.

An alternative approach would simply be to add two fields to your user database, for a cookie value (long/strong/randomly generated value), and an associated expiry date. Any user presenting that cookie would be authenticated, up to the expiry date. One would just need to make sure on the server side that operations such as changing a password removes any "remember me" cookies stored in the database.

Advantages of this approach are that there is no encryption key to manage (or worry about rolling over), and that there is no possibility of the user's password being compromised. Disadvantages are that you need additional storage for your users.

On the further plus side, this approach allows you to provide different "remember me" tokens on different devices (a la Google Account manager), simply by storing the tokens in a separate table and allowing more than one entry per user. This then makes it possible to track where each is used from, and invalidate them individually.

7
  • 3
    This answer is not correct. Encrypting does not allow a site operator to verify that it has not been tampered with. This is a fundamental and basic confusion about the distinction between confidentiality vs authenticity/integrity.
    – D.W.
    Commented Jan 8, 2011 at 1:58
  • 3
    -1 You are proposing to transfer the end users password over the wire and store it on his harddisk? No, don't do that, it is absolutely wrong even though you use AES encryption. If an attacker can obtain a cookie, you're giving him unlimited attempts at obtaining the end users password in plaintext, i.e. getting of the most security-critical pieces of information. I assume you mean a hash of the password inside the AES (which isn't clear from your writing), and even that is not ideal.
    – user2122
    Commented Apr 25, 2011 at 21:17
  • @D.W. Encrypting DOES allow a site operator to verify that it has not been tampered with. If the decryption results in garbage, it has been tampered with. If it results in valid data, it has not been tampered with. Commented Aug 16, 2011 at 18:42
  • @Jesper There is an insignificant difference between storing an AES encrypted version of the password, and a hashed version of the password. In both cases, the attacker can attempt to brute force a random AES key. Adding the additional step of then brute forcing the hashed password does not really add all that much, given how easy passwords are to brute force these days. At least a suitably long AES key should keep them busy for a while. Commented Aug 16, 2011 at 18:46
  • 2
    @Rogan, you are wrong. Using cryptography properly is more subtle than you may realize.
    – D.W.
    Commented Aug 16, 2011 at 18:54

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .