To quickly explain the “Secure” part, it is as secure as it can be without using SSL. I’ll expand on explaining the security more below.
The main purpose is to artificially extend the PHP session beyond its normal lifetime, as you see on most sites using a cookie. However, cookies are inherently insecure as they are stored on insecure medium. I’ll explain how to make this quite a bit more secure. Again, if you REALLY need security, for example if you’re running an e-commerce site, you really should fork over the money for SSL. I only recommend the following method if you’re running some sort of forum or some other sort of non-sensitive information-accessing website.
First off, lets set up the database. You’ll need a table (I call mine userAuthentication) with three columns: userId:int, lastUsed:datetime, and authString:varchar(32). The below will create this table for you:
CREATE TABLE IF NOT EXISTS `userAuthentication` ( `userId` int(11) NOT NULL, `lastUsed` datetime NOT NULL, `authString` varchar(32) NOT NULL ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
The three fields should be pretty self-explanitory, except the authString, which I will explain later.
Next let’s set up the code for logging in. I’m assuming the use on the HTML side of 3 fields: userName, password, and remember. The below is generally how your php login script should look:
session_start();
include_once('userAuth.php'); // We'll show this later
// other includes, dbconnect, etc...
// get variables
$userName = strtolower(mysql_real_escape_string($_REQUEST['userName']));
$password = md5($_REQUEST['password']);
$remember = $_REQUEST['remember'] == '1';
// check if the credentials are correct and get the userId
// if user logged in successfully
if($userIsCorrect)
{
if($remember)
generateAuthString($userId); // this function will exist in userAuth.php
$_SESSION['userId'] = $userId;
// do whatever else you need
}
Before I go on, do note that it is HIGHLY recommended to hash any password you store (I used md5 in my example). That way if your database is compromised somehow, the attacker will never be able to read your users’ passwords as plain text. This doesn’t have anything to do with the rest of this post, but is very good practice.
So the above code is also pretty self-explanatory. Get the credentials, checks the credentials, set the session, then call this mysterious generateAuthString function, which I’ll explain below.
So far everything we’ve done is fairly straightforward, and if you already had a login system set up, you’ve written probably almost no code so far. Now I’ll get into this userAuth.php code file.
Here is the PHP Source (as text file).
There are three functions in this file: checkLogIn, generateAuthString, and clearAuthString.
checkLogIn first checks to see if the PHP session is still active to save time. This code is assuming the security of PHP sessions, so if it still is valid, it just immediately returns true. If, however, the session has expired, it checks if the userAuth cookie is set. If it is not, it returns false as the “remember” cookie doesn’t exist.
If the userAuth cookie does exist, the function takes the cookie, appends the user’s IP address and user agent (browser’s identification string), hashes it all together, and checks it against the authString string in the database. Currently, I don’t check for how long the authString lasts, but it is pretty straightforward to make it expire after a certain time after lastUsed.
After this is all done and everything checks out, the old authStringis cleared and generateAuthString is called to create a new authentication cookie string and the session is set for the user.
generateAuthString just generates the authString for the database using a hash of a random number, the user’s IP, and their user agent, and then sets the user’s cookie to the hash of the same random number. My example sets the cookie expiration to one month in the future.
clearAuthString just clears the user’s authString entry in the database that corresponds to the authString cookie they have. This is to be used when the user logs out.
checkLogIn should be used any time the user’s credentials should be checked (usually the top of every page that requires the user to be logged in).
So now that the code is all explained, I can further discuss why this is secure.
First off, the cookie that the user stores contains absolutely no identification information. All that’s stored is a hash of a random number.
Next, each authString is unique to a particular user, so if one user’s authString is somehow compromised, it doesn’t compromise the entire system.
Each authString is also a hash of the user’s cookie, IP, and user agent, so having the correct cookie alone is not enough to gain access.
Finally, each authString is deleted after it’s use and another generated, so although they may last a long time if not used, they are only able to be used once. I must admit, that I got this “one-use” idea from Blizzard’s Blizzard Authenticator. Obviously I haven’t seen their code for that nor is this really anything like that, but I just thought I’d give them credit nevertheless =P.
So for an attacker to gain access using the authString of a legitimate user, they must:
- Steal the user’s
userAuthcookie. - Know the particular user’s IP address.
- Spoof the particular user’s IP address
- Know the particular user’s exact browser and version (user agent)
- Spoof the particular user’s user agent
- Do all this before the particular user’s PHP session expires and tries to access the site again
Generally, all of that will be much harder than just scanning outgoing packets for the user’s plain-text username and password when they actually do have to type in their username and password. In fact, since the user will most likely have to log in less, they will be sending their credentials in plain-text less, and thus this “remember me” functionality actually increases the security for that user.
To conclude, this is NOT meant to replace SSL, but can add some security to your log in scripts as well as adding some convenience for your users.
