“Keep Me Logged In” - the best approach

2018-12-31 09:24发布

My web application uses sessions to store information about the user once they've logged in, and to maintain that information as they travel from page to page within the app. In this specific application, I'm storing the user_id, first_name and last_name of the person.

I'd like to offer a "Keep Me Logged In" option on log in that will put a cookie on the user's machine for two weeks, that will restart their session with the same details when they return to the app.

What is the best approach for doing this? I don't want to store their user_id in the cookie, as it seems like that would make it easy for one user to try and forge the identity of another user.

12条回答
十年一品温如言
2楼-- · 2018-12-31 09:26

I read all the answers and still found it difficult to extract what I was supposed to do. If a picture is worth 1k words I hope this helps others implement a secure persistent storage based on Barry Jaspan's Improved Persistent Login Cookie Best Practice

enter image description here

If you have questions, feedback, or suggestions, I will try to update the diagram to reflect for the newbie trying to implement a secure persistent login.

查看更多
听够珍惜
3楼-- · 2018-12-31 09:27

I asked one angle of this question here, and the answers will lead you to all the token-based timing-out cookie links you need.

Basically, you do not store the userId in the cookie. You store a one-time token (huge string) which the user uses to pick-up their old login session. Then to make it really secure, you ask for a password for heavy operations (like changing the password itself).

查看更多
听够珍惜
4楼-- · 2018-12-31 09:28

Old thread, but still a valid concern. I noticed some good responses about security, and avoiding use of 'security through obscurity', but the actual technical methods given were not sufficient in my eyes. Things I must say before I contribute my method:

  • NEVER store a password in clear text...EVER!
  • NEVER store a user's hashed password in more than one location in your database. Your server backend is always capable of pulling the hashed password from the users table. It's not more efficient to store redundant data in lieu of additional DB transactions, the inverse is true.
  • Your Session ID's should be unique, so no two users could ever share an ID, hence the purpose of an ID (could your Driver's License ID number ever match another persons? No.) This generates a two-piece unique combination, based on 2 unique strings. Your Sessions table should use the ID as the PK. To allow multiple devices to be trusted for auto-signin, use another table for trusted devices which contains the list of all validated devices (see my example below), and is mapped using the username.
  • It serves no purpose to hash known data into a cookie, the cookie can be copied. What we are looking for is a complying user device to provide authentic information that cannot be obtained without an attacker compromising the user's machine (again, see my example). This would mean, however, that a legitimate user who forbids his machine's static information (i.e. MAC address, device hostname, useragent if restricted by browser, etc.) from remaining consistent (or spoofs it in the first place) will not be able to use this feature. But if this is a concern, consider the fact that you are offering auto-signin to users whom identify themselves uniquely, so if they refuse to be known by spoofing their MAC, spoofing their useragent, spoofing/changing their hostname, hiding behind proxies, etc., then they are not identifiable, and should never be authenticated for an automatic service. If you want this, you need to look into smart-card access bundled with client-side software that establishes identity for the device being used.

That all being said, there are two great ways to have auto-signin on your system.

First, the cheap, easy way that puts it all on someone else. If you make your site support logging in with, say, your google+ account, you probably have a streamlined google+ button that will log the user in if they are already signed into google (I did that here to answer this question, as I am always signed into google). If you want the user automatically signed in if they are already signed in with a trusted and supported authenticator, and checked the box to do so, have your client-side scripts perform the code behind the corresponding 'sign-in with' button before loading, just be sure to have the server store a unique ID in an auto-signin table that has the username, session ID, and the authenticator used for the user. Since these sign-in methods use AJAX, you are waiting for a response anyway, and that response is either a validated response or a rejection. If you get a validated response, use it as normal, then continue loading the logged in user as normal. Otherwise, the login failed, but don't tell the user, just continue as not logged in, they will notice. This is to prevent an attacker who stole cookies (or forged them in an attempt to escalate privileges) from learning that the user auto-signs into the site.

This is cheap, and might also be considered dirty by some because it tries to validate your potentially already signed in self with places like Google and Facebook, without even telling you. It should, however, not be used on users who have not asked to auto-signin your site, and this particular method is only for external authentication, like with Google+ or FB.

Because an external authenticator was used to tell the server behind the scenes whether or not a user was validated, an attacker cannot obtain anything other than a unique ID, which is useless on its own. I'll elaborate:

  • User 'joe' visits site for first time, Session ID placed in cookie 'session'.
  • User 'joe' Logs in, escalates privileges, gets new Session ID and renews cookie 'session'.
  • User 'joe' elects to auto-signin using google+, gets a unique ID placed in cookie 'keepmesignedin'.
  • User 'joe' has google keep them signed in, allowing your site to auto-signin the user using google in your backend.
  • Attacker systematically tries unique IDs for 'keepmesignedin' (this is public knowledge handed out to every user), and is not signed into anywhere else; tries unique ID given to 'joe'.
  • Server receives Unique ID for 'joe', pulls match in DB for a google+ account.
  • Server sends Attacker to login page that runs an AJAX request to google to login.
  • Google server receives request, uses its API to see Attacker is not logged in currently.
  • Google sends response that there is no currently signed in user over this connection.
  • Attacker's page receives response, script automatically redirects to login page with a POST value encoded in the url.
  • Login page gets the POST value, sends the cookie for 'keepmesignedin' to an empty value and a valid until date of 1-1-1970 to deter an automatic attempt, causing the Attacker's browser to simply delete the cookie.
  • Attacker is given normal first-time login page.

No matter what, even if an attacker uses an ID that does not exist, the attempt should fail on all attempts except when a validated response is received.

This method can and should be used in conjunction with your internal authenticator for those who sign into your site using an external authenticator.

=========

Now, for your very own authenticator system that can auto-signin users, this is how I do it:

DB has a few tables:

TABLE users:
UID - auto increment, PK
username - varchar(255), unique, indexed, NOT NULL
password_hash - varchar(255), NOT NULL
...

Note that the username is capable of being 255 characters long. I have my server program limit usernames in my system to 32 characters, but external authenticators might have usernames with their @domain.tld be larger than that, so I just support the maximum length of an email address for maximum compatibility.

TABLE sessions:
session_id - varchar(?), PK
session_token - varchar(?), NOT NULL
session_data - MediumText, NOT NULL

Note that there is no user field in this table, because the username, when logged in, is in the session data, and the program does not allow null data. The session_id and the session_token can be generated using random md5 hashes, sha1/128/256 hashes, datetime stamps with random strings added to them then hashed, or whatever you would like, but the entropy of your output should remain as high as tolerable to mitigate brute-force attacks from even getting off the ground, and all hashes generated by your session class should be checked for matches in the sessions table prior to attempting to add them.

TABLE autologin:
UID - auto increment, PK
username - varchar(255), NOT NULL, allow duplicates
hostname - varchar(255), NOT NULL, allow duplicates
mac_address - char(23), NOT NULL, unique
token - varchar(?), NOT NULL, allow duplicates
expires - datetime code

MAC addresses by their nature are supposed to be UNIQUE, therefore it makes sense that each entry has a unique value. Hostnames, on the other hand, could be duplicated on separate networks legitimately. How many people use "Home-PC" as one of their computer names? The username is taken from the session data by the server backend, so manipulating it is impossible. As for the token, the same method to generate session tokens for pages should be used to generate tokens in cookies for the user auto-signin. Lastly, the datetime code is added for when the user would need to revalidate their credentials. Either update this datetime on user login keeping it within a few days, or force it to expire regardless of last login keeping it only for a month or so, whichever your design dictates.

This prevents someone from systematically spoofing the MAC and hostname for a user they know auto-signs in. NEVER have the user keep a cookie with their password, clear text or otherwise. Have the token be regenerated on each page navigation, just as you would the session token. This massively reduces the likelihood that an attacker could obtain a valid token cookie and use it to login. Some people will try to say that an attacker could steal the cookies from the victim and do a session replay attack to login. If an attacker could steal the cookies (which is possible), they would certainly have compromised the entire device, meaning they could just use the device to login anyway, which defeats the purpose of stealing cookies entirely. As long as your site runs over HTTPS (which it should when dealing with passwords, CC numbers, or other login systems), you have afforded all the protection to the user that you can within a browser.

One thing to keep in mind: session data should not expire if you use auto-signin. You can expire the ability to continue the session falsely, but validating into the system should resume the session data if it is persistent data that is expected to continue between sessions. If you want both persistent AND non-persistent session data, use another table for persistent session data with the username as the PK, and have the server retrieve it like it would the normal session data, just use another variable.

Once a login has been achieved in this way, the server should still validate the session. This is where you can code expectations for stolen or compromised systems; patterns and other expected results of logins to session data can often lead to conclusions that a system was hijacked or cookies were forged in order to gain access. This is where your ISS Tech can put rules that would trigger an account lockdown or auto-removal of a user from the auto-signin system, keeping attackers out long enough for the user to determine how the attacker succeeded and how to cut them off.

As a closing note, be sure that any recovery attempt, password changes, or login failures past the threshold result in auto-signin being disabled until the user validates properly and acknowledges this has occurred.

I apologize if anyone was expecting code to be given out in my answer, that's not going to happen here. I will say that I use PHP, jQuery, and AJAX to run my sites, and I NEVER use Windows as a server... ever.

查看更多
有味是清欢
5楼-- · 2018-12-31 09:28

I don't understand the concept of storing encrypted stuff in a cookie when it is the encrypted version of it that you need to do your hacking. If I'm missing something, please comment.

I am thinking about taking this approach to 'Remember Me'. If you can see any issues, please comment.

  1. Create a table to store "Remember Me" data in - separate to the user table so that I can log in from multiple devices.

  2. On successful login (with Remember Me ticked):

    a) Generate a unique random string to be used as the UserID on this machine: bigUserID

    b) Generate a unique random string: bigKey

    c) Store a cookie: bigUserID:bigKey

    d) In the "Remember Me" table, add a record with: UserID, IP Address, bigUserID, bigKey

  3. If trying to access something that requires login:

    a) Check for the cookie and search for bigUserID & bigKey with a matching IP address

    b) If you find it, Log the person in but set a flag in the user table "soft login" so that for any dangerous operations, you can prompt for a full login.

  4. On logout, Mark all the "Remember Me" records for that user as expired.

The only vulnerabilities that I can see is;

  • you could get hold of someone's laptop and spoof their IP address with the cookie.
  • you could spoof a different IP address each time and guess the whole thing - but with two big string to match, that would be...doing a similar calculation to above...I have no idea...huge odds?
查看更多
孤独寂梦人
6楼-- · 2018-12-31 09:32

I would recommend the approach mentioned by Stefan (i.e. follow the guidelines in Improved Persistent Login Cookie Best Practice) and also recommend that you make sure your cookies are HttpOnly cookies so they are not accessible to, potentially malicious, JavaScript.

查看更多
妖精总统
7楼-- · 2018-12-31 09:33

There are two very interesting articles, I found while searching for a perfect solution for the "remember-me"-problem:

查看更多
登录 后发表回答