Home arrow PHP arrow Page 2 - User identification using cookies in PHP/MySQL
PHP

User identification using cookies in PHP/MySQL


What is a cookie and how do we use them in PHP? If you can not answer this question, you should definitely have a read through this article.

Author Info:
By: Vladmir Krstulja
Rating: 5 stars5 stars5 stars5 stars5 stars / 79
March 28, 2003
TABLE OF CONTENTS:
  1. · User identification using cookies in PHP/MySQL
  2. · The Article
  3. · Common Pitfalls and Conclusion

print this article
SEARCH DEVARTICLES

User identification using cookies in PHP/MySQL - The Article
(Page 2 of 3 )

Setup of Cookie Data

Up to now we know what a cookie can and should contain. In order to strenghten the security, domain address and relevant subdirectories must always be set, along with expiration time.

Before we actually deal with the data our cookie should carry, we must define user data first. Let’s assume that our users have following fields in the database:

  • ID
  • Username
  • Password
  • Logcode

along with any other data your users list must contain.

Let us set up the MySQL table with a following command:

>CREATE TABLE my_users (
>id INT UNSIGNED NOT NULL AUTO_INCREMENT,
>username VARCHAR(20),
>password CHAR(32),
>logcode VARCHAR(32),
>PRIMARY KEY (id),
>INDEX (username),
>INDEX (password));

Certain fields require explanation:

password will be a md5 hash of user’s password, so it is 32 chars fixed-length

logcode is a md5 hash that identifies whether the user is logged in or not.

Now, let us explain what data will be transmited to cookies, and in what manner.

  1. When the user logs in, he is identifed by his username/password combination (thus the indices).
  2. Upon actual login he will receive a randomly created, md5 hashed code called logcode
  3. The logcode will be written in my_users table, for that particular user
  4. User’s ID and LOGCODE will be set in a cookie, on user’s computer

When the user is accessing any protected area, a small function should read the cookie and compare if ID exists in the table, and if does, compare to see if LOGCODEs are equal. If they are not, the user is not allowed to access the page, if they are, a new LOGCODE is generated, stored in the table, and stored on user’s computer in the cookie.

Therefore, upon each successful access, the user receives new LOGCODE, which actually updates his logged status, and strenghtens the security. We will see later why.

Now, let us see what should actually happen in our scripts.

login.php and Logging of Users

A small form should input user’s username and password, both passed to login.php via POST protocol. The login.php does the following:

<?php
// Check if the script received required values
if (isset($_POST['username']) && isset($_POST['password'])) {
// trim and read username and password from the form
$username= ltrim(rtrim(addslashes($_POST['username'])));
$password= ltrim(rtrim(addslashes($_POST['password'])));
 // generate a MD5 hash of the password
$mdpass= md5($password);
// now, detect user (get his ID) by username/password_md5 combination
$res= mysql_query("SELECT id FROM my_users WHERE username='$username' AND password='$mdpass'") or die(“Could not select user ID.”);
// just to ensure there is only one user by that username/password combination
// in the database, we check if only one row was returned
if (mysql_num_rows($res)==1) {
$user_obj= mysql_fetch_object($res);
$user_id= $user_obj->id;
  // now generate a random 8 char long string, and hash it with MD5
$logcode= md5(func_generate_string());
  // now update user’s information in the database
$res= mysql_query("UPDATE my_users SET logcode='$logcode' WHERE id=$user_id") or die(“Could not update database.”);
// now, let us setup the identification information that will be passed to user’s computer via a cookie
  // we will store user’s ID and LOGCODE in ID:LOGCODE form so that we can later extract it using explode() function
$newval= "$user_id:$logcode";
  // store the cookie
             setcookie("cookiename", $newval, time() + 300, "/subdirectory/", ".somedomain.com");
  // redirect to some user welcome area
header("Location: http://www.somedomain.com/subdirectory/welcome.php");
exit;
} else {
 // report invalid user or invalid username/password combination
}
} else {
 // report invalid login
}
?>

Now, it must be mentioned that cookies can only be set in HTTP headers, thatis before any output has been generated for browsing. Our die() functions will abort before the cookie is set, automatically switching to HTML output mode, setcookie will never be reached in that case. Lastly, we redirect to a welcome area. This is important, because set cookies are not visible until next reload of some page, under that domain and subdirectory. Also, subdirectories are not needed. You can have your script in domain’s root, in which case you omit the directory value. See PHP documentation for setcookie() function explanation.

Note that this script requires an open connection to MySQL database, so you must open it before abovementioned code, and close the connection after.
 
The Random String Generating Function

As seen in previous caption, we have generated a 8 char long random string that we additionally hashed with MD5 algorithm. First, here is the code of func_generate_string()

<?php
function func_generate_string() {
$auto_string= chr(mt_rand(ord('A'), ord('Z')));
for ($i= 0; $i<8; $i++) {
$ltr= mt_rand(1, 3);
if ($ltr==1) $auto_password .= chr(mt_rand(ord('A'), ord('Z')));
if ($ltr==2) $auto_password .= chr(mt_rand(ord('a'), ord('z')));
if ($ltr==3) $auto_password .= chr(mt_rand(ord('0'), ord('9')));
}
return $auto_string;
}
?>

What this function actually does is that it sets the first character to random uppercase letter (A-Z), and then it sets the following characters to either uppercase (A-Z; if $ltr=1), lowercase (a-z; if $ltr=2) or a number (0-9; if $ltr=3). Using mt_rand() function we strenghten the random distribution. The for(;;) loop can be modified to output any number of characters.

This is a neat function that can be used in automatic password generation too, or any other situation that requires a random string.

detectuser.php and User Identification

The next script actually identifies the user, via values in cookies. It is important to note that this script must be required with require() function in all your pages that require logged users. Also, this script will setup a global $global_user_id variable that stores user’s id in my_users table. You can use its value to manipulate user’s data or monitor what the user is doing. Also, require detectuser.php at the beginning of your scripts to ensure eventual header redirections before anything is output. An example for this would be if the user is not identified, but has requested a protected page, the page will redirect to a login page, instead of showing its contents.

someprotectedpage.php

<?php
 $legal_require_php= 1234;
require (‘detectuser.php’);
 ...
?>

detectuser.php

<?php
 //see if detectuser.php has been required, not URL’d.
if ($legal_require_php!=1234) exit;
 // setup global variable $global_user_id, set it to 0, which means no user as auto_increment IDs in MySQL begin with 1
 $global_user_id= 0;
 // now, check if user’s computer has the cookie set
if (isset($_COOKIE['cookiename'])) {
$cookieval= $_COOKIE['cookiename'];
//now parse the ID:LOGCODE value in cooke via explode() function
$cookieparsed= explode (":", $cookieval);
// $cookie_uid will hold user’s id
// $cookie_code will hold user’s last reported logcode
$cookie_uid= $cookieparsed[0];
$cookie_code= $cookieparsed[1];
  // ensure that ID from cookie is a numeric value
if (is_numeric($cookie_uid)) {
//now, find the user via his ID
$res= mysql_query("SELECT logcode FROM my_users WHERE id=$cookie_uid");
// no die() this time, we will redirect if error occurs
if ($res) {
 // now see if user’s id exists in database
   if (mysql_num_rows($res,0) {
  $logcode_in_base= mysql_result($res, 0);
  // now compare LOGCODES in cookie against the one in database
  if ($logcode_in_base == $cookie_code) {
   // if valid, generate new logcode and update database
   $newcode= md5(func_generate_string());
   $res= mysql_query(“UPDATE my_users SET logcode=’$newcode’ WHERE id=$cookie_uid”);
   // setup new cookie (replace the old one)
   $newval= “$cookie_uid:$newcode”;
   setcookie("cookiename", $newval, time() + 300, "/subdirectory/", ".somedomain.com");
   // finally, setup global var to reflect user’s id
   $global_user_id= $cookie_uid;
  } else {
   // redirect if logcodes are not equal
  }
 } else {
  // redirect if user ID does not exist in database
 }
 } else {
  // redirect in case of database error
 }
} else {
 // redirect if user ID in cookie not numeric
}
 }
?>

First, let us explain the $legal_require_php variable. In order to ensure that our script is actually required(), not called via URL, we must setup this variable to some arbitrary value in all CALLER scripts. In all REQUIRED scripts we check if that variable is set and contains chosen value.

Next, a note on redirection. We redirect from ALL secure pages (all pages that require() detectuser.php) in following cases:

  • cookie is not set – meaning, user is not logged in, or cookie expired
  • logcode from cookie is not the same as logcode in database – meaning breach attempt
  • value ID in cookie is not numeric – someone tampered with the cookie, or the cookie is corrupt
  • user does not exist in database – cookie corrupted or has been tampered with
  • database error

You can setup redirection or error message notification in mentioned cases. That is up to you.

Also note that on each access to detectuser.php (meaning on each reload or click within secure pages) the user receives a new logcode. This renews user identification and somewhat disables someone else to steal the cookie (or its value) and sets it on his own computer, pretending to be a logged user. More frequent the clicks (reloads of detectuser.php), more secure he is from being hacked.

Also, we have set up the cookie expiration time to be 5 minutes (300 seconds) from current access time. This means the user will be considered logged in for up to 5 minutes of him being idle. You can modify this number, or request your users to choose this time upon registration. By storing this time (in seconds) in the my_users table, you can, upon each access, read it and set it to cookie expiration time like this:

$expire= time() + $user_chosen_time_in_seconds;
setcookie("cookiename", $newval, $expire, "/subdirectory/", ".somedomain.com");

The logout.php and Cookie Cleanup

When the user wants to logout, all that has to be done (in a separate script, if wished) is to store an empty cookie with SAME NAME and host/directory parameters, but with expiration time set in past.

In order for the script to know who is logged out, pass the $global_user_id value to it, preferably via POST protocol. This means you should have a LOGOUT button in a small form, somewhere on your secure pages. Pass the user id via userid variable, as hidden value in your form.

<?php
 // ensure the userid is passed
if (isset($_POST['userid'])) {
$userid= $_POST['userid'];           
  // ensure the value is numeric
if (is_numeric($userid)) {
   // update database
$res= mysql_query("UPDATE my_users SET logcode='none' WHERE id=$userid");
setcookie("cookiename", "empty", time() - 3600, "/directory/", ".somedomain.com");
}
}
 // redirect to a logoff (thanks for using our service) page
header("Location:
http://somedomain.com/logoffpage.php
");
exit;
?>

As you can see, we have set the cookie with the same name and parameters, but with empty value string, and expiration time somewhere in the past (one hour before now).

If someone misuses this script and somehow passes some value to this script, all he can do is to logout the user with ID he passed.

Again, setting cookies with same name and host/directory parameters REPLACES old cookies with same parameters on local machines.


blog comments powered by Disqus
PHP ARTICLES

- Removing Singletons in PHP
- Singletons in PHP
- Implement Facebook Javascript SDK with PHP
- Making Usage Statistics in PHP
- Installing PHP under Windows: Further Config...
- File Version Management in PHP
- Statistical View of Data in a Clustered Bar ...
- Creating a Multi-File Upload Script in PHP
- Executing Microsoft SQL Server Stored Proced...
- Code 10x More Efficiently Using Data Access ...
- A Few Tips for Speeding Up PHP Code
- The Modular Web Page
- Quick E-Commerce with PHP and PayPal
- Regression Testing With JMeter
- Building an Iterator with PHP

Watch our Tech Videos 
Dev Articles Forums 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Write For Us 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Contact Us 
Site Map 
Privacy Policy 
Support 

Developer Shed Affiliates

 




© 2003-2017 by Developer Shed. All rights reserved. DS Cluster - Follow our Sitemap
Popular Web Development Topics
All Web Development Tutorials