Introduction
The two most common methods for storing a user’s logged-in or logged-out state are sessions and cookies. Sessions are a construct that stores a set of variables on the server and gives the user’s browser an id as a key to those variables. Cookies are variables stored on the user’s machine that can be set and set and accessed by the server. Though very different in construction, they accomplish the same goal: they allow users to uniquely identify themselves. I prefer using cookies, mostly because I understand them better. An argument can be made that cookies are less secure than sessions since cookies’ values can be read and set by the user, but there are measures that can be taken to make malicious activity very difficult (using encryption).
The basic idea then is to store the logged in / out status of the user in the cookie. If you have many users with their own accounts, you can also store an id or something by which to identify the user in the cookie (be sure to read the ‘Security Concerns’ section below).
Setting / Accessing Cookies
Reading cookies is really easy. Cookies are held in the global $_COOKIE array, and can be accessed by $_COOKIE[’cookie_name’].
Setting cookies requires the use of the PHP function setcookie(). You should read through the PHP documentation for a more complete explanation of all the options available, but I’ll explain the essentials. The setcookie() function should have at least two arguments: $cookie_name and $cookie_value. One big advantage of cookies is that they can be set to persist for however long you want – years if desired. By specifying the third argument (as in the example below) we can set the time the cookie should expire (in seconds from midnight Jan. 1, 1970).
The Decision Tree
The decision tree for logging someone in and out isn’t that complicated, but can be tedious. The following code describes the decision tree for logging a user in / out. This code needs to be on every page that users need to be logged in to view. The functions cookie_is_valid(), make_cookie_value(), and login_is_valid() are where you do the fun stuff individual to your site like encrypt and decrypt your cookies and verify login info.
$cookie_name = "logged_in_status";
if(!cookie_is_valid($_COOKIE[$cookie_name])) { // USER IS NOT LOGGED IN
$valid = login_is_valid($_POST['un'], $_POST['pw']);
if($valid) {
$cookie_value = make_cookie_value($_POST['un'], $_POST['pw']);
setcookie($cookie_name, $cookie_value, time() + 60*60);
header("location: ".$_SERVER['PHP_SELF']); // reload or wherever you want them to go after they log in
} else if(isset($valid)){
echo "<font color='red'>INVALID LOGIN</font><br>";
} else {
echo "You must be logged in to view this page<br>";
}
?>
<form action='<?= $_SERVER["PHP_SELF"] ?>' method='POST'>
Username: <input type='text' name='un' value=''> - it's "correct_username"
<br>Password: <input type='password' name='pw' value=''> - it's "correct_password"
<br><input type='submit' value='Login'>
</form>
<?
} else if($_GET['logOut']){
setcookie($cookie_name, false, time() - 24*60*60);
header("location: ".$_SERVER['PHP_SELF']); // reload or wherever you want them to go after they log out
} else {
setcookie($cookie_name, $_COOKIE[$cookie_name], time() + 60*60);
echo "You are logged in. <a href='?logOut=true'>Log Out</a>";
}
Verifying Login Information
If you have users logging in and creating accounts etc., you probably want to query your database here to determine the validity of the username / password combination. If you’re just protecting an administrator page or something, you can do what I did here and just have the username and password hardcoded into the script. Returning a value of null vs. false is important to the functioning of the decision tree. Basically, if the arguments are null, (the user hasn’t filled out the form yet), we don’t display a “INVALID LOGIN” message as we would if the user did fill out the form and the entries were invalid.
function login_is_valid($un = null, $pw = null) {
if(isset($un) AND isset($pw)) {
if($un == 'correct_username' AND $pw == 'correct_password') return true;
else return false;
} else return null;
}
Security Concerns
Addressing the security concerns mentioned earlier, these functions should be something cool and unique to your site. This is really only a concern if you have multiple users with accounts.
Again, the cookie value should hold the user’s logged in / out status and identify the user that’s logged in. Remember that users can view and set cookies manually (I’ve done it with FireFox – anyone want an article on that?), so we can’t just have cookies called $logged_in_status = true and $user_id = 123 because then anyone could set their cookies manually and effectively log in as whoever they wanted.
The solution is to encrypt the cookie value. Encryption a guilty pleasure of mine, but for this exercise, I’ll just use a simple algorithm that’s been around since Ancient Greece – a simple substitution cipher.
// http://www.thescripts.com/forum/thread5669.html
function doEncDecTranslation($text, $decrypt = false) {
define(A, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz&');
define(B, 'qFpkt79uxTlRWNngCye1isvODmVzJK82I3GLUYSQrjHdoa&f4XEMZ6A05BbhPcw');
//str_shuffle(A);
return $decrypt ? strtr($text, B, A) : strtr($text, A, B);
}
function encrypt($str) {
return doEncDecTranslation($str);
}
function decrypt($str) {
return doEncDecTranslation($str, true);
}
The method goes like this: First each user gets a randomly generated string (variable length between 20 and 30 characters long) when they register, call it $user_key, that is stored in the database along with their username and password. The cookie value is then set to an encrypted concatenation (combination of the strings) of the $user_id and $user_key separated by a delimiter like ‘&’. This gives us the functions:
function make_cookie_value ($un, $pw) {
$sql = "SELECT user_id, user_key FROM tblUserInfo WHERE username='$un' AND password='$pw' LIMIT 1";
list($id, $key) = mysql_fetch_row(mysql_query($sql));
return encrypt($id."&".$key);
}
function cookie_is_valid($cookie_value) {
list($id, $key) = explode("&", decrypt($cookie_value));
if(!alpha_numeric($id) OR !alpha_numeric($key)) return false; // TO PREVENT SQL INJECTION
$sql = "SELECT TRUE FROM tblUserInfo WHERE user_id = '$id' AND user_key='$key' LIMIT 1";
list($valid) = mysql_fetch_row(mysql_query($sql));
return $valid;
}
// http://www.roscripts.com/snippets/show/41
function alpha_numeric($str) {
return (strspn($str, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") == strlen($str));
}
The alpha_numeric test is to guard against SQL injection, but that is for another article.
Even though this uses pretty simple tools, this method is pretty hard to crack. I’d welcome any feedback, though, from anyone who disagrees.