By default, PHP's session handling mechanisms set a session cookie header and store a session even if there is no data in the session. If no data is set in the session then I don't want a Set-Cookie header sent to the client in the response and I don't want an empty session record stored on the server. If data is added to $_SESSION, then the normal behavior should continue.
My goal is to implement lazy session creation behavior of the sort that Drupal 7 and Pressflow where no session is stored (or session cookie header sent) unless data is added to the $_SESSION array during application execution. The point of this behavior is to allow reverse proxies such as Varnish to cache and serve anonymous traffic while letting authenticated requests pass through to Apache/PHP. Varnish (or another proxy-server) is configured to pass through any requests without cookies, assuming correctly that if a cookie exists then the request is for a particular client.
I have ported the session handling code from Pressflow that uses session_set_save_handler() and overrides the implementation of session_write() to check for data in the $_SESSION array before saving and will write this up as library and add an answer here if this is the best/only route to take.
My Question: While I can implement a fully custom session_set_save_handler() system, is there an easier way to get this lazy session creation behavior in a relatively generic way that would be transparent to most applications?
Well, one option would be to use a session class to start/stop/store data in the session. So, you could do something like:
class Session implements ArrayAccess {
protected $closed = false;
protected $data = array();
protected $name = 'mySessionName';
protected $started = false;
protected function __construct() {
if (isset($_COOKIE[$this->name])) $this->start();
$this->data = $_SESSION;
}
public static function initialize() {
if (is_object($_SESSION)) return $_SESSION;
$_SESSION = new Session();
register_shutdown_function(array($_SESSION, 'close'));
return $_SESSION;
}
public function close() {
if ($this->closed) return false;
if (!$this->started) {
$_SESSION = array();
} else {
$_SESSION = $this->data;
}
session_write_close();
$this->started = false;
$this->closed = true;
}
public function offsetExists($offset) {
return isset($this->data[$offset]);
}
public function offsetGet($offset) {
if (!isset($this->data[$offset])) {
throw new OutOfBoundsException('Key does not exist');
}
return $this->data[$offset];
}
public function offsetSet($offset, $value) {
$this->set($offset, $value);
}
public function offsetUnset($offset) {
if (isset($this->data[$offset])) unset($this->data[$offset]);
}
public function set($key, $value) {
if (!$this->started) $this->start();
$this->data[$key] = $value;
}
public function start() {
session_name($this->name);
session_start();
$this->started = true;
}
}
To use, at the start of your script call Session::initialize(). It will replace $_SESSION with the object, and setup the lazy loading. Afterward, you can just do
$_SESSION['user_id'] = 1;
If the session isn't started, it will be, and the user_id key would be set to 1. If at any point you wanted to close (commit) the session, just call $_SESSION->close().
You'll probably want to add some more session management functions (such as destroy, regenerate_id, the ability to change the name of the session, etc), but this should implement the basic functionality you're after...
It's not a save_handler, it's just a class to manage your sessions. If you really wanted to, you could implement ArrayAccess in the class, and on construct replace $_SESSION with that class (The benefit of doing that, is that way legacy code can still use session as they used to without calling $session->setData()). The only downside is that I'm not sure if the serialization routine that PHP uses would work properly (You'd need to put back the array into $_SESSION at some point... Probably with a register_shutdown_function()...
I have developed a working solution to this problem that uses session_set_save_handler() and a set of custom session storage methods that check for content in the $_SESSION array before writing out session data. If there is no data to write for the session, then header('Set-Cookie:', true); is used to prevent PHP's session-cookie from being sent in the response.
The latest version of this code as well as documentation and examples are available on GitHub. In the code below, the important functions that make this work are lazysess_read($id) and lazysess_write($id, $sess_data).
<?php
/**
* This file registers session save handlers so that sessions are not created if no data
* has been added to the $_SESSION array.
*
* This code is based on the session handling code in Pressflow (a backport of
* Drupal 7 performance features to Drupal 6) as well as the example code described
* the PHP.net documentation for session_set_save_handler(). The actual session data
* storage in the file-system is directly from the PHP.net example while the switching
* based on session data presence is merged in from Pressflow's includes/session.inc
*
* Links:
* http://www.php.net/manual/en/function.session-set-save-handler.php
* http://bazaar.launchpad.net/~pressflow/pressflow/6/annotate/head:/includes/session.inc
*
* Caveats:
* - Requires output buffering before session_write_close(). If content is
* sent before shutdown or session_write_close() is called manually, then
* the check for an empty session won't happen and Set-Cookie headers will
* get sent.
*
* Work-around: Call session_write_close() before using flush();
*
* - The current implementation blows away all Set-Cookie headers if the
* session is empty. This basic implementation will prevent any additional
* cookie use and should be improved if using non-session cookies.
*
* #copyright Copyright © 2010, Middlebury College
* #license http://www.gnu.org/copyleft/gpl.html GNU General Public License (GPL), Version 3 or later.
*/
/*********************************************************
* Storage Callbacks
*********************************************************/
function lazysess_open($save_path, $session_name)
{
global $sess_save_path;
$sess_save_path = $save_path;
return(true);
}
function lazysess_close()
{
return(true);
}
function lazysess_read($id)
{
// Write and Close handlers are called after destructing objects
// since PHP 5.0.5.
// Thus destructors can use sessions but session handler can't use objects.
// So we are moving session closure before destructing objects.
register_shutdown_function('session_write_close');
// Handle the case of first time visitors and clients that don't store cookies (eg. web crawlers).
if (!isset($_COOKIE[session_name()])) {
return '';
}
// Continue with reading.
global $sess_save_path;
$sess_file = "$sess_save_path/sess_$id";
return (string) #file_get_contents($sess_file);
}
function lazysess_write($id, $sess_data)
{
// If saving of session data is disabled, or if a new empty anonymous session
// has been started, do nothing. This keeps anonymous users, including
// crawlers, out of the session table, unless they actually have something
// stored in $_SESSION.
if (empty($_COOKIE[session_name()]) && empty($sess_data)) {
// Ensure that the client doesn't store the session cookie as it is worthless
lazysess_remove_session_cookie_header();
return TRUE;
}
// Continue with storage
global $sess_save_path;
$sess_file = "$sess_save_path/sess_$id";
if ($fp = #fopen($sess_file, "w")) {
$return = fwrite($fp, $sess_data);
fclose($fp);
return $return;
} else {
return(false);
}
}
function lazysess_destroy($id)
{
// If the session ID being destroyed is the one of the current user,
// clean-up his/her session data and cookie.
if ($id == session_id()) {
global $user;
// Reset $_SESSION and $user to prevent a new session from being started
// in drupal_session_commit()
$_SESSION = array();
// Unset the session cookie.
lazysess_set_delete_cookie_header();
if (isset($_COOKIE[session_name()])) {
unset($_COOKIE[session_name()]);
}
}
// Continue with destruction
global $sess_save_path;
$sess_file = "$sess_save_path/sess_$id";
return(#unlink($sess_file));
}
function lazysess_gc($maxlifetime)
{
global $sess_save_path;
foreach (glob("$sess_save_path/sess_*") as $filename) {
if (filemtime($filename) + $maxlifetime < time()) {
#unlink($filename);
}
}
return true;
}
/*********************************************************
* Helper functions
*********************************************************/
function lazysess_set_delete_cookie_header() {
$params = session_get_cookie_params();
if (version_compare(PHP_VERSION, '5.2.0') === 1) {
setcookie(session_name(), '', $_SERVER['REQUEST_TIME'] - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
}
else {
setcookie(session_name(), '', $_SERVER['REQUEST_TIME'] - 3600, $params['path'], $params['domain'], $params['secure']);
}
}
function lazysess_remove_session_cookie_header () {
// Note: this implementation will blow away all Set-Cookie headers, not just
// those for the session cookie. If your app uses other cookies, reimplement
// this function.
header('Set-Cookie:', true);
}
/*********************************************************
* Register the save handlers
*********************************************************/
session_set_save_handler('lazysess_open', 'lazysess_close', 'lazysess_read', 'lazysess_write', 'lazysess_destroy', 'lazysess_gc');
While this solution works and is mostly transparent to applications including it, it requires rewriting the entire session-storage mechanism rather than relying on the built-in storage mechanisms with a switch to save or not.
I created a lazy session proof of concept here:
it uses the native php session handler and _SESSION array
it only starts the session if a cookie has been sent or
it starts the session if something has been added the $_SESSION array
it removes the session if a session is started and $_SESSION is empty
Will extend it in the next days:
https://github.com/s0enke/php-lazy-session
this topic is under discussion for a future php version
https://wiki.php.net/rfc/session-read_only-lazy_write
Related
I've written a custom session handler class which works for the most part, until I try to log my users out and destroy the session, then I keep getting the following error:
ErrorException: 0
session_write_close(): Failed to write session data using user defined save handler. (session.save_path: D:\xampp\tmp)
I am running PHP Version 7.3.1 on XAMPP.
This is my custom class
class CornerstoneSessionHandler implements SessionHandlerInterface {
/**
* Construct the Session
* No parameters required, nothing will be returned
*/
public function __construct() {
// Set the handler to overide SESSION
session_set_save_handler(
array($this, "open"),
array($this, "close"),
array($this, "read"),
array($this, "write"),
array($this, "destroy"),
array($this, "gc")
);
// Set the shutdown function
register_shutdown_function('session_write_close');
/** Define and initialise the Session Handler */
session_start();
}
/**
* Set the open callback
*
* #param string $savePath
* #param string $sessionName
*
* #return bool return value should be true for success or false for failure
*/
public function open($savePath, $sessionName) {
// Make the $csdb global accessible
global $csdb;
// Check that the DB connection is set
return ((!empty($csdb)) && $csdb->isConnected() != 1) ? FALSE : TRUE ;
}
/**
* Set the close callback
*
* #return bool return value can only be true for success
*/
public function close() {
return TRUE;
}
/**
* Set the read callback
*
* #param string $sessionID
*
* #return string return value should be the session data or an empty string
*/
public function read($sessionID) {
// Make the $csdb global accessible
global $csdb;
// Get the session from the database
$csdb->query_prepared("SELECT session_data FROM cs_session WHERE session_id=?", [$sessionID]);
// If results returned, continue
if($csdb->getNum_Rows() > 0) {
// Get the data
$result = $csdb->get_row(NULL);
return $result->session_data;
} else { // Else return an empty string
return '';
}
}
/**
* Set the write callback
*
* #param string $sessionID
* #param string $data
*
* #return bool return value should be true for success or false for failure
*/
public function write($sessionID, $data) {
// Make the $csdb global accessible
global $csdb;
// Set the time stamp
$access_dtm = new \DateTime();
// Replace the data
$csdb->query_prepared('REPLACE INTO cs_session(session_id, session_ip_address, session_data , session_access_dtm) VALUES (?, ?, ?, ?)', [$sessionID, $_SERVER['REMOTE_ADDR'], $data, $access_dtm->format('Y-m-d H:i:s')]);
// If modified a success, return true
if($csdb->getNum_Rows() > 0) {
return TRUE;
} else { // Else, return false
return FALSE;
}
}
/**
* Set the destroy callback
*
* #param string $sessionID
*
* #return bool return value should be true for success or false for failure
*/
public function destroy($sessionID) {
// Make the $csdb global accessible
global $csdb;
// Delete the session from the database
$csdb->delete('cs_session', where(eq('session_id', $sessionID)));
// If results returned, return true
if($csdb->affectedRows() > 0) {
return TRUE;
} else { // Else, return false
return FALSE;
}
}
/**
* Set the garbage collector callback
*
* #param string $lifetime
*
* #return bool return value should be true for success or false for failure
*/
public function gc($lifetime) {
// Make the $csdb global accessible
global $csdb;
// Set the date calculation
$expiredTime = new \DateTime();
$expiredTime->modify('-' . $lifetime . ' seconds');
// Get the session from the database
$csdb->delete('cs_session', where(lt('session_access_dtm', $expiredTime->format('Y-m-d H:i:s'))));
// If results deleted, return true
if($csdb->affectedRows() > 0) {
return TRUE;
} else { // Else, return false
return FALSE;
}
}
}
I am using ezSQL as my database handler.
This is the code in the top of all my pages
/**
* Set session data so that logins work properly over all browsers
* This should fix login errors some users can face
* More info can be found at {#link https://www.php.net/manual/en/session.security.ini.php the php user manual}
*/
# PREVENTING SESSION HIJACKING
# Prevents javascript XSS attacks aimed to steal the session ID
ini_set('session.cookie_httponly', 1);
# Make sure the cookie lifetime is set to '0'
ini_set('session.cookie_lifetime', 0);
# Adds entropy into the randomization of the session ID, as PHP's random number
# generator has some known flaws
ini_set('session.entropy_file', '/dev/urandom');
# Uses a strong hash
ini_set('session.hash_function', 'whirlpool');
# Set the session save location (best for shared servers)
# Uncomment out the next line if you would like to set a custom path and haven't already set the value in your `php.ini` file.
# ini_set('session.save_path',realpath(ABSPATH . 'tmp' . DIRECTORY_SEPARATOR));
# Note: The folder referenced above must exist for it to work.
# Set the session garbage collection lifetime to custom defined minutes (PHP default is 24 minutes)
ini_set('session.gc_maxlifetime', (int)get_option("session_expire") * MINUTE_IN_SECONDS);
# Enable session garbage collection with a 1% chance of
# running on each session_start()
ini_set('session.gc_probability', 1);
ini_set('session.gc_divisor', 100);
# Uses a secure connection (HTTPS) if possible
# Allow cookies to be sent over insecure connections if not an HTTPS site
(SITE_HTTPS) ? ini_set('session.cookie_secure', 1) : ini_set('session.cookie_secure', false);
# PREVENTING SESSION FIXATION
# Session ID cannot be passed through URLs
# so only use cookies to store the session id on the client side
ini_set('session.use_only_cookies', 1);
# Set a custom session name
session_name('CSSESSID');
# Load the session class
new CornerstoneSessionHandler();
# Start the session if it's not already started
if (session_status() == PHP_SESSION_NONE) {session_start();}
The weird thing is, even though it's giving me that error, if I check the database the data is being updated no worries.
This is my logout code
/**
* Logout the user
*
* #return bool
*/
public static function logoutUser() {
// Make the $fear global accessible
global $fear; // Direct access check
// Check if session set (just in case) and start if it isn't
if(session_id() == '') {session_start();}
// Delete the $_SESSION data set in `authenticateUser()`
unset($_SESSION['HTTP_USER_AGENT']);
unset($_SESSION['_cs-uid']);
unset($_SESSION['_cs-ue']);
unset($_SESSION['_cs-ul']);
unset($_SESSION['_cs-un']);
/**
* Get the "ext.auth.php" file and run `clearCustomAuth()` function
* to clear any custom set $_SESSION items
*/
require_once( get_lib_path('ext.auth.php') );
clearCustomAuth();
// Regenerate a new session ID just to be sure
session_regenerate_id();
// Destroy the session
session_destroy();
// Check if the $_COOKIE data if set
if(isset( $_COOKIE['_cs-ti'] ) && !empty( $_COOKIE['_cs-ti'] )) {
// Delete the cookie token from the database
if(!self::deleteAuthCookie($_COOKIE['_cs-ti'])) {
// Return false if cookie couldn't be deleted
return false;
}
}
// Return true if run all the way
return true;
}
I've tried shifting around the session_regenerate_id() but if I put it after the session_destroy() it says there's no session to regenerate, but if I put it before the session_destroy() or don't have it at all, my session is deleted from my database but I can still see it in my storage in my inspector on Firefox.
It only gives me an error when trying to delete a session from the database. Everything else works no worries! I've manually tried to delete a session from the database in testing and I still get that error.
Is anyone able to see what I might be doing wrong or how I can fix it? I've only just learnt about this so pretty new to it. If I need to supply more information, just let me know. I've been searching over the web for nearly 2 days with no luck.
I don't see a reason why you need to set a shutdown handler. Sessions are automatically saved when php shuts down, even when you get a fatal error.
Also the write, destroy and gc should always return true, even if no rows were updated / deleted. This seems counter intuitive, but php considers deleting a non existing session also a success.
I'm writing a simple website which allows a user to login, fill out a form which is submitted to a database and then log out. In order to manage the session, I used the session manager which is described by TreeHouse on the following page: http://blog.teamtreehouse.com/how-to-create-bulletproof-sessions
In order to protect against hijacking, the client's IP address and user agent are stored in the session variable and compared to the server's values for these properties on each page. If they don't match, then it is assumed that the session has been hijacked and it is reset.
The implementation seems to work on my local machine without any issues, but when I uploaded it to the server, each page refresh causes the preventHijacking() function to return false (meaning it believes the session has been hijacked). However, if I echo any text within that function, the problem mysteriously disappears and the whole thing works as I expect it to (except for the bit of echoed text which is now displayed above my form :P).
I haven't a clue why this would be the case and I can't figure out how to fix it. The session manager code is below. At the start of each page, I use this to start the session and then each page simply uses or sets whatever variables it requires. If anyone could suggest why the function always returns false unless it echoes text and perhaps suggest what modification I need to make so that it will behave in the expected manner, I'd really appreciate it.
<?php
class SessionManager {
protected static $timeout = 600; // Time before automatic logout for the session
static function sessionStart($name, $limit=0, $path='/', $domain=null, $secure=null) {
// Set the cookie name before we start
session_name($name.'_Session');
// Set the domain to default to the current domain
$domain = isset($domain)? $domain : $_SERVER['SERVER_NAME'];
// Set the default secure value to whether the site is being accessed with SSL
$https = isset($secure)? $secure : isset($_SERVER['HTTPS']);
// Set the cookie settings and start the session
session_set_cookie_params($limit, $path, $domain, $secure, True);
session_start();
// Make sure the session hasn't expired and destroy it if it has
if(self::validateSession()) {
// Check to see if the session is new or a hijacking attempt
if(!self::preventHijacking()) {
// Reset session data and regenerate ID
$_SESSION=array();
$_SESSION['IPaddress'] = $_SERVER['REMOTE_ADDR'];
$_SESSION['userAgent'] = $_SERVER['HTTP_USER_AGENT'];
self::regenerateSession();
// Give a 5% chance of the session ID changing on any request
} else if (rand(1, 100) <= 5) {
self::regenerateSession();
}
$_SESSION['LAST_ACTIVITY'] = time();
} else {
$_SESSION = array();
session_destroy();
session_start();
}
}
static function preventHijacking() {
if(!isset($_SESSION['IPaddress']) || !isset($_SESSION['userAgent'])) {
return false;
}
if($_SESSION['IPaddress'] != $_SERVER['REMOTE_ADDR']) {
return false;
}
if($_SESSION['userAgent'] != $_SERVER['HTTP_USER_AGENT']) {
return false;
}
return true;
}
static function regenerateSession() {
// If this session is obsolete, it means that there already is a new id
if(isset($_SESSION['OBSOLETE']) && $_SESSION['OBSOLETE'] === True) {
return;
}
// Set current session to expire in 10 seconds
$_SESSION['OBSOLETE'] = True;
$_SESSION['EXPIRES'] = time() + 10;
// Create new session without destroying the old one
session_regenerate_id(false);
// Grab current session ID and close both sessions to allow other scripts to use them
$newSession = session_id();
session_write_close();
// Set session ID to the new one and start it back up again
session_id($newSession);
session_start();
// Now we unset the obsolete and expiration values for the session we want to keep
unset($_SESSION['OBSOLETE']);
unset($_SESSION['EXPIRES']);
}
static protected function validateSession() {
// Check if something went wrong
if(isset($_SESSION['OBSOLETE']) && !isset($_SESSION['EXPIRES'])) {
return false;
}
// Test if this is an old session which has expired
if(isset($_SESSION['EXPIRES']) && $_SESSION['EXPIRES'] < time()) {
return false;
}
// Check if the user's login has timed out
if(isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY']) > self::$timeout) {
return false;
}
return true;
}
}
?>
I could be way out here (it's been a while) but that sounds like the buffer containing the headers isn't being flushed for some reason. Providing body would force them to be flushed, so maybe not providing the body doesn't flush?
Try putting ob_end_flush(); in there before you return. That may fix it.
I've been working on the security of my site (PHP) and there's a ton of information to ingest. I've tried to implement security I've researched on OWASP, but one thing I'm a little nervous about, among other things, is how to handle SESSIONS when the user logs out.
Currently all I'm using is:
session_destroy();
But, I've read that I should change the XRSF token and start another SESSION so it forces the user to resubmit login credentials in-turn explicitly ending the users SESSION.
Is session_destroy() enough?
EDIT
I've downloaded michael-the-messenger, which I believe was created by Michael Brooks (Rook) which should be VERY secure, and I saw some code that I might want to use. Is this something that could safely replace the session_destroy() I'm using?
CODE
if($_SESSION['user']->isAuth())
{
/* if they have clicked log out */
/* this will kill the session */
if($_POST['LogMeOut'] == 'true')
{
//When the user logs out the xsrf token changes.
$tmp_xsrf = $_SESSION['user']->getXsrfToken();
$_SESSION['user']->logout();
$loginMessage = str_replace($tmp_xsrf, $_SESSION['user']->getXsrfToken(), $loginMessage);
print layout('Authorization Required', $loginMessage);
}
else
{
header("Location: inbox.php");
//user is allowed access.
}
}
else
{
// code goes on ....
LOGOUT
public function logout()
{
$_SESSION['user'] = new auth();
}
Obviously $_SESSION['user'] = new auth(); reinstantiates the object which sets a private variable $auth to false.
but one thing I'm a little nervous about, among other things, is how
to handle SESSIONS when the user logs out.
According to manual:
In order to kill the session altogether, like to log the user out, the
session id must also be unset. If a cookie is used to propagate the
session id (default behavior), then the session cookie must be
deleted. setcookie() may be used for that.
So, in order to safely destroy a session, we'd also erase it on the client-machine.
session_destroy() along with setcookie(session_name(), null, time() - 86400) will do that.
Apart from that,
What you are doing wrong and why:
Session storage merely uses data serialization internally. By storing
an object in the $_SESSION superglobal you just do
serialize/unserialize that object on demand without even knowing it.
1) By storing an object in $_SESSION you do introduce global state. $_SESSION is a superglobal array, thus can be accessed from anywhere.
2) Even by storing an object that keeps an information about logged user, you do waste system memory. The length of object representation is always greater than a length of the strings.
But why on earth should you even care about wrapping session functionality? Well,
It makes a code easy to read, maintain and test
It adheres Single-Responsibility Principle
It avoids global state (if properly used), you'll access session not as $_SESSION['foo'], but $session->read['foo']
You can easily change its behaivor (say, if you decide to use DB as session storage) without even affecting another parts of your application.
Code reuse-ability. You can use this class for another applications (or parts of it)
If you wrap all session-related functionality into a signle class, then it will turn into attractive:
$session = new SessionStorage();
$session->write( array('foo' => 'bar') );
if ( $session->isValid() === TRUE ) {
echo $session->read('foo'); // bar
} else {
// Session hijack. Handle here
}
// To totally destroy a session:
$session->destroy();
// if some part of your application requires a session, then just inject an instance of `SessionStorage`
// like this:
$user = new Profile($session);
// Take this implementation as example:
final class SessionStorage
{
public function __construct()
{
// Don't start again if session is started:
if ( session_id() != '' ) {
session_start();
}
// Keep initial values
$_SESSION['HTTP_USER_AGENT'] = $_SERVER['HTTP_USER_AGENT'];
$_SESSION['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
}
/**
* You can prevent majority of hijacks using this method
*
* #return boolean TRUE if session is valid
*/
public function isValid()
{
return $_SESSION['HTTP_USER_AGENT'] === $_SERVER['HTTP_USER_AGENT'] && $_SESSION['REMOTE_ADDR'] === $_SERVER['REMOTE_ADDR'] ;
}
public function __destruct()
{
session_write_close();
}
/**
* Fixed session_destroy()
*
* #return boolean
*/
public function destroy()
{
// Erase the session name on client side
setcookie(session_name(), null, time() - 86400);
// Erase on the server
return session_destroy();
}
public function write(array $data)
{
foreach($data as $key => $value) {
$_SESSION[$key] = $value;
}
}
public function exists()
{
foreach(func_get_args() as $arg){
if ( ! array_key_exists($arg, $_SESSION) ){
return false;
}
}
return true;
}
public function read($key)
{
if ( $this->exists($key) ){
return $_SESSION[$key];
} else {
throw new RuntimeException('Cannot access non-existing var ' .$key);
}
}
}
Maybe session_unset() is what you are looking for.
class Index extends CI_Controller {
private $data = array();
private $content_data = array();
public function __construct() {
parent::__construct();
if (isset($_GET['m2w'])) {
$stw = switch_to_web();
}
if (isset($_GET['w2m'])) {
$stm = switch_to_mobile();
}
// load mobile or desktop view
}
// Called within an helper function
function switch_to_web() {
return set_cookie('load-web', 'true', '86500');
}
function switch_to_mobile() {
return delete_cookie('load-web');
}
function is_mobile() {
// return true;
$CI = & get_instance();
if ($CI->input->cookie('load-web'))
return false;
$CI->load->library('user_agent');
if ($CI->agent->is_mobile()) {
return true;
}else
return false;
}
I have the above block of code to determine if to load the mobile or web view.
Expected order of execution:
if m2w is set, set the load-web cookie (this is done before the is_mobile function is called
is_mobile function sees the load-web cookie has been set and loads the desktop version
Actual order of execution:
if m2w is set, load-web cookie is called to be set, however the is_mobile function doesn't see it as set hence the desktop version is not loaded
the cookie is set after the is_mobile function has returned true, i check my browser cookies and observed that the cookie was actually set but not at when expected
What am I not getting right?
CI's cookies uses the native setcookie() method of PHP. Cookies requires to be sent in the HTTP headers before being available in the native $_COOKIE variable.
From the PHP doc :
Once the cookies have been set, they can be accessed on the next page load with the $_COOKIE or $HTTP_COOKIE_VARS arrays.
Here's the cookie() method from CI :
function cookie($index = '', $xss_clean = FALSE)
{
// Simply fetch from the $_COOKIE array and do XSS_Clean if needed.
return $this->_fetch_from_array($_COOKIE, $index, $xss_clean);
}
In short, you set your cookie correctly, but it won't be available until your next request. It's all because the new value isn't in $_COOKIE array.
You have multiple alternatives to fix that.
You could extend the CI_Input class and modify the set_cookie method to also add your new value to the $_COOKIE array.
You could edit your is_mobile function to also check for the $_GET['m2w'] value. (Not just the cookie.)
Personalty, I think the 2nd solution is the cleanest and easiest to do, but I don't know the full scope of your project.
Hope this helps!
I am using sfGuard as the authentication plugin in my project. I want to invoke certain client side & server side scripts on session timeout. What is the best way I can do this.
Please help!
Thanks a lot.
Well I've been reading the sfGuardSecurityUser and it extends the sfBasicSecurityUser class, which handles user authentication, profile, credentials, etc.
So, I found a function in sfBasicSecurityUser that determines whether a users sessions is timed put called isTimedOut, and also setTimedOut.
If you want to do something when user's session times out, at least on server side, you should listen to the event that is throw when this happens. Check this method:
This could be found in the symfony_core_root_dir/lib/user/sfBasicSecurityUser.class.php
public function initialize(sfEventDispatcher $dispatcher, sfStorage $storage, $options = array())
{
// initialize parent
parent::initialize($dispatcher, $storage, $options);
if (!array_key_exists('timeout', $this->options))
{
$this->options['timeout'] = 1800;
}
// force the max lifetime for session garbage collector to be greater than timeout
if (ini_get('session.gc_maxlifetime') < $this->options['timeout'])
{
ini_set('session.gc_maxlifetime', $this->options['timeout']);
}
// read data from storage
$this->authenticated = $storage->read(self::AUTH_NAMESPACE);
$this->credentials = $storage->read(self::CREDENTIAL_NAMESPACE);
$this->lastRequest = $storage->read(self::LAST_REQUEST_NAMESPACE);
if (null === $this->authenticated)
{
$this->authenticated = false;
$this->credentials = array();
}
else
{
// Automatic logout logged in user if no request within timeout parameter seconds
$timeout = $this->options['timeout'];
if (false !== $timeout && null !== $this->lastRequest && time() - $this->lastRequest >= $timeout)
{
if ($this->options['logging'])
{
$this->dispatcher->notify(new sfEvent($this, 'application.log', array('Automatic user logout due to timeout')));
}
$this->setTimedOut();
$this->setAuthenticated(false);
}
}
$this->lastRequest = time();
}
For client side, you might start thinking about HTML 5 and Javascript Workers. The idea could be setting a worker when page loads, and telling him count till session_time_out, then redirecting to a login page or something.