The "remember me" feature on user login forms (long-term persistent authentication) is one of the most common and convenient web authentication features. It allows the user to be constantly authenticated in the application without having to enter username and password in each session. The feature is based on cookies and handling cookie data in PHP. Since we are talking about authentication, security is important and any possible security issues and solutions will be covered in this tutorial.
Persistent login is usually optional and work the following way:
1. Add a "remember me" checkbox to the login form
2. If the checkbox has been checked, create a long-lived cookie containing some unique information about the user
3. Store cookie value in the database
4. Store login information in a PHP session
5. When the user tries to access a page that requires authentication, check session data
6. If session data is missing, check whether the "remember me" cookie exists
7. If the cookie exists, compare its value to the value saved in database and run other security checks
8. If the cookie does not exist or is not valid, the user will not be allowed access and will be redirected to the login page
There are a few things to consider when creating a secure persistent authentication feature.
You may have noticed that many tutorials suggest saving the user id in a cookie. That is not to be done in production environment and is a big vulnerability. A malicious attacker could manually change that cookie value and log in as someone else (e.g. administrator). Instead of that, a random "remember me" token should be created for each user and stored in a cookie and in the database. As the token needs to be truly random (not pseudo-random), Paragon Initiative suggests the following solutions for creating a random number in PHP:
• RandomLib (you can find it on Github)
• Raw bytes read from /dev/urandom
• random_bytes($length) – available in PHP7
• mcrypt_create_iv($length, MCRYPT_DEV_URANDOM)
The random token should be saved in the database in order to prevent maliciously changing the cookie data. However, this enables the attacker to attempt another type of attack.
The remote timing attack exploits the way the strings are compared in the database. The comparison of two strings will fail slightly faster if their first character is different that if only their last character is different. For example, let’s say that we have a following token:
If we change the first character to "U" and try to query the database, the comparison will fail slightly faster than if we change the last character to "m" and try to login with that token.
Although this type of attack is not highly probable, it is better to consider it and protect the login from that attack as well. Instead of directly querying the database, we would store the user id in another cookie and fetch the token from the database by using a query like this:
SELECT token FROM user WHERE id = ?
After that, we would compare the token with hash_equals() PHP function, which makes the string comparison time constant.
If you are interested in reading more about the timing attack in PHP, I recommend this article.
Also known as cookie theft, session hijacking is a type of attack where the attacker steals the session (or in this case, "remember me" cookie) from an authenticated user. To secure the application from these attacks, it is advised to use HTTPS, accept session ids only from a cookie (set session.use_only_cookies to true in your php.ini file) and to implement session fingerprinting (check for client-specific information, such as the browser name and version; don’t use the data than can change between requests, such as IP address). For more details about session hijacking and methods used to prevent it, read this and this.
There are many persistent login examples available online. However, I suggest that you take a look at one of the most popular and community-accepted authentication libraries, such as Laravel framework’s native authentication library or Gatekeeper.