User Tools

Site Tools


web_development:magic_session

Here's a little something I'm working on… not ready for production yet.

The general idea here is to create a mySQL driven drop-in replacement for standard session cookies, allowing the simple pass of the token from site to site, resulting in shared sessions from domain to domain.

Of course, this will be done as securely as possible, but if you notice any security issues, please let me know.

How it Works

By passing the token from page to page, site to site, you can then set $_SESSION['token'], and then run this script. The script will then assess the session's token, and compare it to the database. If the IP address matches the token, the session information is loaded into $_SESSION['magicsession'], which we'll then manipulate back into the original strings and arrays.

The Code

mySQL Query:

CREATE TABLE `magicsession` (  `id` int(11) NOT NULL auto_increment,  `sessiondata` blob,  `expiry` int(11) NOT NULL,  `token` varchar(255) NOT NULL,  `ip` varchar(255) NOT NULL,  PRIMARY KEY  (`id`)) ENGINE=MyISAM DEFAULT CHARSET=latin1;

Source Code:

magic_session.php
<?php
// magic new session, driven by mySQL - by Robbie
 
// user settings
  $debug = 1; // set to 1 for debugging / testing, or 0 for production - 1 means output jargon, 0 means just load magicsession data with no output
	$mySQLserver = 'localhost'; // set to localhost or your server URL/IP according to your setup
	$mySQLdb = 'database'; // the name of your mySQL database
	$mySQLuser = 'username'; // your username for the mySQL database
	$mySQLpass = 'password'; // your password for the mySQL database
	$expirytime = 90; // number of minutes to keep the cookie alive
	$add = array(); // comma-separated string or array names you'd like to add to the cookie
	$remove = array($probablyWillDeprecateThis); // comma-separated string, array or key names you'd like to remove from the cookie
// end of user settings
 
// stuff you can change, but probably want to leave
	$mySQLtable = 'magicsession'; // should be magicsession, but you can change it if you've made it something else
// end of user configurable jargon
 
// notice session_start() -- means this file MUST be included prior to ANY HTML output.
if (!session_id()) session_start();
 
// declare functions
	// obtain the user ip address ... source unknown
	if (!function_exists('GetUserIP')) {
		function GetUserIP() {
 
		    if (isset($_SERVER)) {
 
			if (isset($_SERVER["HTTP_X_FORWARDED_FOR"]))
			    return $_SERVER["HTTP_X_FORWARDED_FOR"];
 
			if (isset($_SERVER["HTTP_CLIENT_IP"]))
			    return $_SERVER["HTTP_CLIENT_IP"];
 
			return $_SERVER["REMOTE_ADDR"];
		    }
 
		    if (getenv('HTTP_X_FORWARDED_FOR'))
			return getenv('HTTP_X_FORWARDED_FOR');
 
		    if (getenv('HTTP_CLIENT_IP'))
			return getenv('HTTP_CLIENT_IP');
 
		    return getenv('REMOTE_ADDR');
		}
	}
	// end of GetUserIP()
 
	// compress/expand functions to turn arrays into unreadable compressed doodles by Robbie ... not a secure thing, just makes saving an entire array to database a lot nicer
	if (!function_exists('compress')) {
		function compress($thisVar) {
		 foreach ($thisVar as $key => $val) {
		  $thisvar2 = ($thisvar2 + strlen($val) + strlen($key));
		 }
		// echo "Size before compression: " . $thisvar2 . "<br />";
		 $thisVar = serialize($thisVar);
		 $thisVar = gzcompress($thisVar, 9);
		 $thisVar = base64_encode($thisVar);
		// echo "Size after compression: " . strlen($thisVar) . "<br />";
		 return $thisVar;
		}
	}
 
	if (!function_exists('expand')) {
		function expand($thisVar) {
		 $thisVar = base64_decode($thisVar);
		 $thisVar = gzuncompress($thisVar);
		 $thisVar = unserialize($thisVar);
		 return $thisVar;
		}
	}
 
// end of function declarations
 
 
// generate some important session variables
	$thisSession['ip'] = GetUserIP();
	$thisSession['useragent'] = $_SERVER['HTTP_USER_AGENT'];
//
 
 
// load database
$conn = @mysql_connect($mySQLserver, $mySQLuser, $mySQLpass);
 
if($conn){
 
	@mysql_select_db($mySQLdb);
 
}else die("Database is currently unavailable. Please try again later.") ;
 
 
// it's safe to purge using date() because it's server-side. If this was JS, you'd be relying on the user's clock... in this case, it's the same clock that set the session in the first place.
$quickdate = date('U');
 if ($purge = mysql_query("DELETE from $mySQLtable WHERE expiry < '$quickdate' ") ) $debugMessage .= "- Purge ghost sessions result: " . $purge . "<br />";
unset($quickdate);
 
 
// set the token id
	if (!$_SESSION['token'] && $_POST['token']) $_SESSION['token'] = $_POST['token']; // allow the token to be passed to the script via POST
	if (!$_SESSION['token'] && $_GET['token']) $_SESSION['token'] = $_GET['token']; // allow the token to be passed to the script via GET
  // now that we're allowing the user to provide a token ID via GET and POST, it's important we confirm that it is a real token
  // if the token provided is invalid for this IP address, reject it and create a new one
	$query="select * from " . $mySQLtable . " WHERE token = '$_SESSION[token]' AND ip = '$thisSession[ip]' ";
	$result = @mysql_query($query) ;
 
	if($result){ //if query executed without errors
		$countrow=1;
		while($row[email protected]mysql_fetch_assoc($result)){
		$countrow++;
 
		}
 
	}
	if ($countrow != 1) $thisSession['token'] = $_SESSION['token']; // set the session token for cross-site compatibility, but only if it's legit
	if (!$thisSession['token']) $thisSession['token'] = md5(uniqid(mt_rand(), true));
	$_SESSION['token'] = $thisSession['token'];
// done setting token id
 
 
// set cookie expiry
	// set the expiry the first time, but keep increasing it if the user is active
	if (!$_SESSION['expiry'] || $_SESSION['expiry'] > date('U')) {
		if (!$_SESSION['expiry']) $debugMessage .= "- Expiry time was not found in your session.<br />";
		$thisSession['expiry'] = strtotime("+" . $expirytime . " minutes");
		$_SESSION['expiry'] = $thisSession['expiry'];
	}
	else // oh; there's already a magicsession, but you walked away from your computer and now I'm going to delete it!  I'm evil that way.
	{
		$query = mysql_query("DELETE from $mySQLtable WHERE token = '$thisSession[token]' AND ip = '$thisSession[ip]' ");
		session_destroy();
		$debugMessage .= "- Session Expired.<br />";
	}
	if ($_SESSION['expiry'] < date('U')) $debugMessage .= "- Your current time exceeds the expiry time.<br />";
	unset($expirytime);
// end set cookie expiry
 
 
// load the magicsession info from database, if it matches the current session
	$query="select * from " . $mySQLtable . " WHERE token = '$_SESSION[token]' AND ip = '$thisSession[ip]' ";
 
	$result = @mysql_query($query) ;
 
	if($result){ //if query executed without errors
		$countrow=1;
		while($row[email protected]mysql_fetch_assoc($result)){
			$magicsession = $row;
		$countrow++;
 
		}
 
	}
// expand the session data back to normal arrays
if ($magicsession['sessiondata']) $magicsession['sessiondata'] = expand($magicsession['sessiondata']);
// end of loading database
 
 
 
// add current session cookie to database's session record
//	$thisSession['cookie'] = $_SESSION;
	$thisSession['session_id'] = session_id();
//
 
 
// add an array that has previously been assigned to your magicsession
// if the array is assigned by your code before this point, your array will override the cookie
// if on the other hand, you have not yet assigned the array, this will load it from the database
// using $current as an example... you'd have to assign value to it in order to see it work.
	if (!$magicsession['sessiondata']['current']) {
		$thisSession['current'] = $current;
	}
	else {
		if (!$current) $current = $magicsession['sessiondata']['current'];
		$thisSession['current'] = $current;
	}
//
 
 
// remove some things from the array, if you like
unset($thisSession['cookie']['product_arr']);
 
 
// encode the user cookie to a single string
	$thisSessionCompressed = compress($thisSession);
 
// save the current magicsession to the database
if ($magicsession['ip'] == $thisSession['ip']) {
  // update the existing magicsession
  $query = mysql_query("UPDATE $mySQLtable SET expiry='$thisSession[expiry]' WHERE token = '$thisSession[token]' AND ip = '$thisSession[ip]' ");
  $query = mysql_query("UPDATE $mySQLtable SET sessiondata='$thisSessionCompressed' WHERE token = '$thisSession[token]' AND ip = '$thisSession[ip]' ");
}
else
{
  // no magicsession exists, create it
  $query = mysql_query("INSERT INTO $mySQLtable (sessiondata, expiry, token, ip) VALUES('$thisSessionCompressed', '$thisSession[expiry]', '$thisSession[token]', '$thisSession[ip]' ) ");
}
$debugMessage .= "- mySQL Query result for write operation: " . $query . "<br />";
// done saving magicsession to database
 
if ($debug) { ?>
	<html>
	<head>
	<title>magicsession admin</title>
	<noscript>
	  <meta http-equiv="refresh" content="2">
	</noscript>
	<script language="JavaScript">
	<!--
	var sURL = unescape(window.location.pathname);
	function doLoad()
	{
	    // the timeout value should be the same as in the "refresh" meta-tag
	    setTimeout( "refresh()", 2*1000 );
	}
	function refresh()
	{
	    window.location.href = sURL;
	}
	//-->
	</script>
 
	<script language="JavaScript1.1">
	<!--
	function refresh()
	{
	    window.location.replace( sURL );
	}
	//-->
	</script>
	<script language="JavaScript1.2">
	<!--
	function refresh()
	{
	    window.location.reload( false );
	}
	//-->
	</script>
	</head>
	<body onload="doLoad()">
	<?php
	echo "Debug Information:<br />";
	echo $debugMessage . '<hr>';
	echo '<strong>Compressed:</strong><br /><div style="font-size: 6pt; border : solid 2px #777777; background : #dddddd; color : #333333; padding : 4px; width : 60%; height : 100px; overflow : auto; ">' . $thisSessionCompressed . '</div>';
	echo '<br /><strong>Un-compressed:</strong><br /><div style="font-size: 6pt; border : solid 2px #777777; background : #dddddd; color : #333333; padding : 4px; width : 60%; height : 100px; overflow : auto; ">';
	print_r($thisSession);
	echo '</div>';
	?>
	</body>
	</html>
<?php } ?>
web_development/magic_session.txt · Last modified: 2012/01/26 08:57 by import