Stay logged in with PHP Sessions

Building own applications

When you love building everything yourself and dont want to depend on frameworks you most likely came across the need of building a login system that has the capability to let the user stay logged in into your site.

The straight forward way would be to use PHP build in _SESSION System to store login information. But this session system has a garbage collection which cleans the session data from time to time.

STAY LOGGEDIN!

First of all what you can do to lower that effect is to set the garbage interval very high:

session.gc_maxlifetime = 65535

Think this will solve your problem? Nope.


Your users will only log out later but won’t stay logged into your site.

The solution is to use own session handlers for this. I prefare to use MySQL to store session data.

Create a session.php file with this code:

<?php

define("SESSION_LIFETIME", 60*60*24*30);

final class Session
{
 private $db;
 private $lifeTime;
 public function __construct(PDO $db)
 {
  $this->db = $db;
  $this->lifeTime = SESSION_LIFETIME;
 }
 public function db_q($q, array $r)
 {
  try {
   $stmt = $this->db->prepare($q);
   $stmt->execute($r);
   $errInfo = $stmt->errorInfo();
   if($errInfo[2] != "")
   {
    var_dump("DB - qrf - error - errorInfo(): [0]: ".$errInfo[0]."\n[1]: ".$errInfo[1]."\n\n[2]: ".$errInfo[2]." (Query: ".$q."\n");
    return false;
   }
   return true;
  }
  catch(PDOException $e) {
   return false;
  }
 }
 public function db_qrf($q, $r, $f)
 {
  try {
   $stmt = $this->db->prepare($q);
   $stmt->execute($r);
   $errInfo = $stmt->errorInfo();
   if($errInfo[2] != "")
   {
    var_dump("DB - db_qrf - error - errorInfo(): [0]: ".$errInfo[0]."\n[1]: ".$errInfo[1]."\n\n[2]: ".$errInfo[2]." (Query: ".$q."\n");
   }
   return $stmt->fetchColumn($f);
  }
  catch(PDOException $e) {
   return false;
  }
 }
 function open($savePath, $sessName)
 {
  return true;
 }
 function close()
 {
  $this->gc();
  return true;
 }
 function read($sessID)
 {
  return $this->db_qrf("SELECT `session_data` AS d from sessions WHERE session_id=? AND session_expires>".time(), [$sessID], 0);
 }
 function write($sessID,$sessData)
 {
  $newExp = time()+$this->lifeTime;
  if($this->db_qrf("SELECT COUNT(*) from sessions WHERE session_id=?", [$sessID], 0) > 0)
  {
   if($this->db_q("UPDATE sessions SET session_expires=?,session_data=?,IPv4=INET_ATON(?),Useragent=? WHERE session_id=?", [$newExp, $sessData, $_SERVER["REMOTE_ADDR"], $_SERVER["HTTP_USER_AGENT"], $sessID]))
   {
    return true;
   }
  }
  else
  {
   if($this->db_q("INSERT INTO sessions (session_id,session_expires,session_data,IPv4,Useragent) VALUES (?,?,?,?,?)", [$sessID, $newExp, $sessData, $_SERVER["REMOTE_ADDR"], $_SERVER["HTTP_USER_AGENT"]]))
   {
    return true;
   }
  }
  return false;
 }
 function destroy($sessID)
 {
  if($this->db_q("DELETE FROM sessions WHERE session_id=?", [$sessID]))
  {
   return true;
  }
  return false;
 }
 function gc()
 {
  $c = $this->db_qrf("SELECT COUNT(*) from sessions WHERE session_expires<".time(), [], 0);
  if($c > 0)
  {
   $this->db_q("DELETE from sessions WHERE session_expires<".time(), []);
  }
  return $c;
 }
}

$db = new PDO("mysql:host=localhost;dbname=test", "root", "");
$session = new Session($db);
session_name("MYSESSION");
session_set_save_handler(array(&$session,"open"), array(&$session,"close"), array(&$session,"read"), array(&$session,"write"), array(&$session,"destroy"), array(&$session,"gc"));
session_start();

if(isset($_GET["l"]) && $_GET["l"] == "1")
{
 session_destroy();
 echo "User logged out! <a href=\"".str_replace("?l=1", "",  $_SERVER["REQUEST_URI"])."\">Login again</a>";
}
elseif(!isset($_SESSION["loggedin"]))
{
 $_SESSION["loggedin"] = true;
echo "Now logged in!";
}
elseif($_SESSION["loggedin"] == true)
{
 echo "User ist already logged in - <a href=\"".$_SERVER["REQUEST_URI"]."?l=1\">logout</a>";
}

In MySQL create DB test and execute this query:

CREATE TABLE IF NOT EXISTS `sessions` (
`session_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '',
`session_expires` int(10) unsigned NOT NULL DEFAULT '0',
`session_data` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`IPv4` int(16) unsigned NOT NULL,
`Useragent` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`session_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

You can also use InnoDB if you want. Pick wisely because this can affect the performance of your application.

What does the code do?

First of all you need to specify a livetime value for your sessions. So if your user clicks on “Stay logged in” you will use the 30 day value (see the first lines).Then we initialize our library for the session handling which holds all functions that describe how to behave with sessions.
After everything is initialized, we start the session with a $db which has a resource that leads to our PDO instance.
After you write session_start() you can use the $_SESSION Superglobal variable as you would do normally in PHP at any position in your code. PHP will use our library which saves data to mysql instead of using PHP’s internal session system.

When executed, you will see a new record in your database:
23-10-2015 10-35-16

DEMO

Try the demo: DEMO

 

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>