24

My CodeIgniter app uses the session library and saves data to the DB.

I've been having some problems where blank sessions are created after a certain ajax call.

Upon investigating, it seems that there were 2 simultaneous functions calls that fired off that require a session validation. One would fail and the other would be fine.

I was able to fix this by not having them fire off simultaneously. But I still don't understand the REASON why it fails. Does it have to do with one call updating the user cookie and the 2nd call invalidating? Or maybe when reading the DB it dies somehow?

I looked over the Session core class a bit and have not found any clues to the cause.

If any one had the same problem before I would appreciate any advice on how to debug or what the cause is.

Thanks!

EDIT:

I originally said there was a 408 status return. That was an unrelated case.

This is the function that fires off MyVar.refresh() in parallel:

function (event)
{
    var self$ = this.a$;
    var uid  = this.b$.val();
    var tid  = this.c$.val();
    var jqxhr = $.post('/controller1/index',{'uid':uid,'tid':tid,'action':true},function(re)
    {
        if(re.message != 'success')
        {
            MyVar.alert('<span class="msg_error sprite"></span>' + re.error);
            MyVar.refresh();
        } 

    },'json');
    MyVar.refresh();
    return stopDefault(event);
};

POSSIBLE SOLUTIONS:

Found this: http://codeigniter.com/forums/viewthread/102456/

Apparently it doesn't play well with ajax. One solution is to disallow session update if it is an ajax call; only problem is that our site is mostly built with ajax..

Also, just lowered the sess_time_to_update to something very frequent and ajax was doing fine. Also did a browser refresh and it did not timeout. Not sure why if the session ID has already changed upon an ajax call and browser cookies were never updated.

lamp_scaler
  • 787
  • 2
  • 10
  • 18
  • HTTP 408 is a request timeout and is returned when the server stops waiting for expected input from the browser. Without knowing what your ajax calls look like or what your controller is doing with them, your question is impossible to answer. – Mike S. Nov 02 '11 at 14:17
  • which method worked for you? – Zabs Oct 09 '13 at 10:57
  • @Zabs this may be helpful. I am using the solution from this thread: https://github.com/EllisLab/CodeIgniter/pull/1900 – lamp_scaler Oct 09 '13 at 12:38
  • 1
    If no answer below worked, check this question: http://stackoverflow.com/questions/24297447/codeigniter-random-session-logouts-already-tried-override-session-class-and-rai It's a Session Race Condition (check my answer there, and read the article posted by the guys of hiretheworld, very nice and informative) – Federico J. Jun 25 '14 at 08:08

9 Answers9

26

Try this

<?php
/**
 * ------------------------------------------------------------------------
 * CI Session Class Extension for AJAX calls.
 * ------------------------------------------------------------------------
 *
 * ====- Save as application/libraries/MY_Session.php -====
 */

class MY_Session extends CI_Session {

    // --------------------------------------------------------------------

    /**
     * sess_update()
     *
     * Do not update an existing session on ajax or xajax calls
     *
     * @access    public
     * @return    void
     */
    public function sess_update()
    {
        $CI = get_instance();

        if ( ! $CI->input->is_ajax_request())
        {
            parent::sess_update();
        }
    }

}

// ------------------------------------------------------------------------
/* End of file MY_Session.php */
/* Location: ./application/libraries/MY_Session.php */

The problem is in the sess_update function of the session class, that generates a new session_id after X seconds. Every page have a session_id, if the session_id expires before the ajax call is made, that call will fail.

Create a php file in /application/libraries/ with the name MY_Session (or whatever prefix you set), paste this code there and that is all. This function will override the sess_update function in the session class, checking on every request if that request was made by ajax, skipping the sess_update function.

Its a bad idea set the sess_expiration at higher values. This is a security feature that will protect you against session hijaking

PD: i'm not very fluent in english, if you dont understand something just let me know.

Agustin Baez
  • 371
  • 3
  • 4
  • 14
    **Warning.** Use `=&` and not `=` when assigning the CI instance to a variable. Assigning by reference allows you to use the original CodeIgniter object rather than creating a copy of it. Given that sessions are used at high frequency, this is currently a memory hog. – Jordan Arsenault Sep 09 '12 at 20:58
  • Wait, but this file my_session.php will be called automatically? I don't need to call this library somewhere? – Marcelo Agimóvel Mar 12 '17 at 20:19
5

Until it is merged into the stable branch, the solution (finally!) is to use Areson's commit 245bef5 combined with the database schema:

CREATE TABLE IF NOT EXISTS  `ci_sessions` (
    session_id varchar(40) DEFAULT '0' NOT NULL,
    ip_address varchar(45) DEFAULT '0' NOT NULL,
    user_agent varchar(120) NOT NULL,
    last_activity int(10) unsigned DEFAULT 0 NOT NULL,
    user_data text NOT NULL,
    prevent_update int(10) DEFAULT NULL,
    PRIMARY KEY (session_id),
    KEY `last_activity_idx` (`last_activity`)
);

For more information, read pull 1283 comments top-to-bottom.

Jordan Arsenault
  • 7,100
  • 8
  • 53
  • 96
3

We had this problem, it was due to the sess_time_to_update parameter in config.php. CI use this to update the session ID to a new one. If the change happen in an ajax call, CI sends a new cookie to tell the browser the new session ID. Unfortunatly, browsers seems to ignore this cookie and keep the old session ID.

We fixed it by setting the sess_time_to_update to sess_expiration in the config.

$config['sess_time_to_update'] = $config['sess_expiration']; 
  • What version of CI are you using? Why would the ajax call create a new cookie or session? – lamp_scaler Nov 02 '11 at 14:42
  • Also, doesn't this just makes it so that the user is timed out after the sess_expiration time is reached? For example, if expiration is 2 hours and the user has been active for 2 hours, the last_activity time will never be updated and the user will be forced to timeout after 2 hours. – lamp_scaler Nov 02 '11 at 15:14
  • 1
    Both sess_time_to_update and sess_expirations are calculated on the last_activity, so setting them to the same value makes the sessions expire before they are updated. – Francois Gelinas Nov 02 '11 at 17:01
  • sess_time_to_update is used in the sess_update function of the session class. This is the function that change the key in the user cookie when the timeout is reached, in your case in an AJAX call. – Francois Gelinas Nov 02 '11 at 17:03
  • I hope it is enough to just set the **$config['sess_expiration']** to a less value than **$config['sess_time_to_update']**. – shasi kanth Jul 19 '12 at 12:57
1

I had this problem too in codeigniter version 2.1.3, when i use the following configuration:

$config['sess_use_database']    = TRUE;

$config['sess_time_to_update']  = 300;

I think it has nothing to do with ajax requests but rather with a bug in codeigniter.

It seems that when you store the session in the database a logout is forced after 300 seconds. After 3 hours of searching and analyzing i found a clear bug in the code and a unclear one as well, i've solved the bug as follows:

Create a new file: MY_Session.php in the application/libraries folder

Add the following code to it:

<?php
// fixed by sirderno 2013

if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 

class MY_Session extends CI_Session
{

    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Update an existing session
     *
     * @access  public
     * @return  void
     */
    public function sess_update()
    {
        // We only update the session every five minutes by default
        if (($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now)
        {
            return;
        }

        // Save the old session id so we know which record to
        // update in the database if we need it
        $old_sessid = $this->userdata['session_id'];
        $new_sessid = '';
        while (strlen($new_sessid) < 32)
        {
            $new_sessid .= mt_rand(0, mt_getrandmax());
        }

        // To make the session ID even more secure we'll combine it with the user's IP
        $new_sessid .= $this->CI->input->ip_address();

        // Turn it into a hash
        $new_sessid = md5(uniqid($new_sessid, TRUE));

        // Update the session data in the session data array
        $this->userdata['session_id'] = $new_sessid;
        $this->userdata['last_activity'] = $this->now;

        // _set_cookie() will handle this for us if we aren't using database sessions
        // by pushing all userdata to the cookie.
        $cookie_data = NULL;

        // Update the session ID and last_activity field in the DB if needed
        if ($this->sess_use_database === TRUE)
        {
            // set cookie explicitly to only have our session data
            $cookie_data = array();
            foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
            {
                $cookie_data[$val] = $this->userdata[$val];
            }

            $cookie_data['session_id'] = $new_sessid;  // added to solve bug

                    //added to solve bug
            if (!empty($this->userdata['user_data']))
                $cookie_data['user_data'] = $this->userdata['user_data'];

            $this->CI->db->query($this->CI->db->update_string($this->sess_table_name, array('last_activity' => $this->now, 'session_id' => $new_sessid), array('session_id' => $old_sessid)));

        }

        // Write the cookie
        $this->_set_cookie($cookie_data);
    }

    /**
     * Write the session cookie
     *
     * @access  public
     * @return  void
     */
    public function _set_cookie($cookie_data = NULL)
    {
        if (is_null($cookie_data))
        {
            $cookie_data = $this->userdata;
        }

        // Serialize the userdata for the cookie
        $cookie_data = $this->_serialize($cookie_data);

        if ($this->sess_encrypt_cookie == TRUE)
        {
            $cookie_data = $this->CI->encrypt->encode($cookie_data);
        }
        else
        {
            // if encryption is not used, we provide an md5 hash to prevent userside tampering
            $cookie_data = $cookie_data.md5($cookie_data.$this->encryption_key);
        }

        $_COOKIE[ $this->sess_cookie_name ] = $cookie_data;  // added to solve bug

        $expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time();

        // Set the cookie
        setcookie(
                    $this->sess_cookie_name,
                    $cookie_data,
                    $expire,
                    $this->cookie_path,
                    $this->cookie_domain,
                    $this->cookie_secure
                );
    }   
}


?>

The clear bug is that it didn't store the 'user_data' in the updated cookie. The unclear bug is that it executes the function sess_read() in file Session.php after updating the new session id, i don't know why this happens, because i expected that it executes before updating and not after like it's written in the constructor of Session.php. So the sess_read() function starts reading the old cookie information with the old session id and wants to compare it with session id in the database, but after the session_id update it's not there anymore in the database, so this causes the logout.

This line of code in function sess_read of the Session.php file is responsible for reading the old cookie information:

$session = $this->CI->input->cookie($this->sess_cookie_name);

So in function _set_cookie of MY_Session.php i added this line of code to update the old cookie information of the server with the new one:

$_COOKIE[ $this->sess_cookie_name ] = $cookie_data;  // added to solve bug

With this fix the 'sess_time_to_update' in combination with 'sess_use_database' should work fine. This is a plain and simple bug fix.

Hkachhia
  • 4,463
  • 6
  • 41
  • 76
user1881928
  • 606
  • 6
  • 8
0

well the good solutions are here. do anything with sess_time_to_update etc try the below solutions

  1. https://degreesofzero.com/article/fixing-the-expiring-session-problem-in-codeigniter.html
  2. http://ellislab.com/forums/viewthread/138823/#725078

to solution number "1." i update a little script more. after cracking a lot with CI i get the points that there are two reasons for losting of CI SESSIONS. one is when bad ajax calls are made the session get UPDATED and sessions get lost; second is after bad ajax call it effects the sess_destroy function in SESSION library of CI. SO I MADE A LITTLE CHANGE FOR "1." SOLUTION WHICH IS

/*add this code to MY_Session.php*/     
function sess_destroy()
{
// Do NOT update an existing session on AJAX calls.
if (!$this->CI->input->is_ajax_request())
{
return parent::sess_destroy();
}
/* WHEN USER HIS/HER SELF DO A LOGOUT AND ALSO IF PROGRAMMER SET TO LOGOUT USING AJAX CALLS*/
$firsturlseg = $this->CI->security->xss_clean( $this->CI->uri->segment(1) );        
$securlseg = $this->CI->security->xss_clean( $this->CI->uri->segment(2) );      
if((string)$firsturlseg==(string)'put ur controller name which u are using for login' &&    (string)$securlseg==(string)'put url controler function for logout')
{
 return parent::sess_destroy();
}
}

hope ths helps u people also

khalrd
  • 60
  • 6
0

There seems to be a flaw in the core CI session class handling session.

Found an alternate session library which works like a charm.

CI alternate session library

I would recommend to extend the core CI_Session class rather than replacing it.

To extend, create a file MY_Session.php in application/libraries. Paste the content of the alternate library, replace class CI_Session to class MY_Session extends CI_Session.

Remove protected from _flashdata_mark(), _flashdata_sweep(), _get_time(), _set_cookie(), _serialize(), _unserialize(), _sess_gc() functions.

Hope it helps.

Zeshan
  • 2,496
  • 3
  • 21
  • 26
0

There seems to still be a lot of older CI versions in use and I wanted to add my two cents, even though this thread is old. I just spent a few days solving the problem of AJAX calls in Code Igniter and I have a solution that covers the main issues, although some of the solution isn't 'wonderful'. The CI version that I am ( still ) using is 2.1.3

My application requires that AJAX calls update the last_activity field to maintain a valid session, so it is not good enough for me to simply abandon updating the session on AJAX calls.

The error checking for sess_update and sess_read are inadequate in this CI version ( I have not investigated more recent versions ) and a lot of the problems start there.

Part one: sess_update()

Multiple AJAX calls create race conditions which result in a locked the database for the later calls. If we try to run an update query but the database is locked, we get an error, the query returns false, but the cookie is still updated with new data?... BAD! Also, we don't need a new session_id for every Ajax call. We only need to update last_activity. Try this:

    function sess_update()
{
    // We only update the session every five minutes by default
    if (($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now)
    {
        return;
    }

    // Save the old session id so we know which record to
    // update in the database if we need it

    $old_sessid = $this->userdata['session_id'];
    //Assume this is an AJAX call... keep the same session_id
    $new_sessid = $old_sessid;

    if( !$this->CI->input->is_ajax_request() ){ 
        //Then create a new session id
        while (strlen($new_sessid) < 32)
        {
            $new_sessid .= mt_rand(0, mt_getrandmax());
        }

        // To make the session ID even more secure we'll combine it with the user's IP
        $new_sessid .= $this->CI->input->ip_address();

        // Turn it into a hash
        $new_sessid = md5(uniqid($new_sessid, TRUE));

    }

    // _set_cookie() will handle this for us if we aren't using database sessions
    // by pushing all userdata to the cookie.
    $cookie_data = NULL;

    // Update the session ID and last_activity field in the DB if needed
    if ($this->sess_use_database === TRUE)
    {

        //TRY THE QUERY FIRST!
        //Multiple simultaneous AJAX calls will not be able to update because the Database will be locked. ( Race Conditions )
        //Besides... We don't want to update the cookie if the database didn't update
        $query = $this->CI->db->query($this->CI->db->update_string($this->sess_table_name, array('last_activity' => $this->now, 'session_id' => $new_sessid), array('session_id' => $old_sessid)));
        if( $query ){

            // Update the session data in the session data array
            $this->userdata['session_id'] = $new_sessid;
            $this->userdata['last_activity'] = $this->now;

            // set cookie explicitly to only have our session data
            $cookie_data = array();
            foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
            {
                $cookie_data[$val] = $this->userdata[$val];
            }

            // Write the cookie
            $this->_set_cookie($cookie_data);

        }else{
            //do nothing... we don't care, we still have an active retreivable session and the update didn't work
            //debug: error_log( "ERROR::" . $this->CI->db->_error_message() ); //Shows locked session database
        }
    }else{
        // Update the session data in the session data array
        $this->userdata['session_id'] = $new_sessid;
        $this->userdata['last_activity'] = $this->now;

        // Write the cookie
        $this->_set_cookie($cookie_data);
    }
}

part 2: sess_read()

Very similar problem here... The database is sometimes locked during a query. Except we can't ignore the errors this time. We are trying to read the session to see if it exists... so if we get a locked database error, we can check for the error and try again ( a couple times if need be ). In my testing I never made it more than 2 tries in ). Also, I don't know about you, but I don't want php to fail on a fatal error by not checking for a false query result. You'll need this at the top of the session.php file if you want to try this code directly:

var $sess_query_attempts = 5;

Also note, this isn't the entire sess_read function

$query = $this->CI->db->get($this->sess_table_name);

//Multiple AJAX calls checking
//But adding add a loop to check a couple more times has stopped premature session breaking
$counter = 0;
while( !$query && $counter < $this->sess_query_attempts     ){

    usleep(100000);//wait a tenth of a second

   $this->CI->db->where('session_id', $session['session_id']);

    if ($this->sess_match_ip == TRUE)
   {
        $this->CI->db->where('ip_address', $session['ip_address']);
    }

    if ($this->sess_match_useragent == TRUE)
    {
        $this->CI->db->where('user_agent', $session['user_agent']);
    }

    $query = $this->CI->db->get($this->sess_table_name);

    $counter++;
}
if ( !$query || $query->num_rows() == 0)
{
    $this->CI->db->where('session_id', $session['session_id']);
    $query = $this->CI->db->get( $this->sess_table_name );

    $this->sess_destroy();
    return FALSE;
}

Anyway, imho there isn't a complete answer to this problem out there and I felt like I should share my findings with those who may still be experiencing early session timeouts on sites which use tons of AJAX like mine.

Squivo
  • 917
  • 1
  • 10
  • 21
0

I had the exact same problem when uploading images with ajax, and i set the sess_expiration in config to:

$config['sess_expiration'] = time()+10000000;

And it fixed my problem.

georgesamper
  • 4,989
  • 5
  • 40
  • 59
-3

write session_start() in all your controller constructor

Vikram Deshmukh
  • 12,304
  • 4
  • 36
  • 38