2

I have tried both suggestions at: Codeigniter session bugging out with ajax calls

But even with $config['sess_time_to_update'] = PHP_INT_MAX I am still seeing random logouts.

I also took the suggestions of creating class MY_Session

User's are still reporting random logouts and my logs also seem to indicate this.

EDIT: Here is my session config:

$config['sess_cookie_name']     = 'phppos';
$config['sess_expiration']      = 86400;
$config['sess_expire_on_close'] = TRUE;
$config['sess_encrypt_cookie']  = FALSE;
$config['sess_use_database']    = TRUE;
$config['sess_table_name']      = 'sessions';
$config['sess_match_ip']        = FALSE;
$config['sess_match_useragent'] = TRUE;
$config['sess_time_to_update']  = PHP_INT_MAX;
Community
  • 1
  • 1
Chris Muench
  • 17,444
  • 70
  • 209
  • 362
  • have you tried changing the session name and encryption keys? max encryption key length is 32, and on the session name try using a name with no underscores or dashes. just plain `char`. Or possible, some part of your script is overriding the memory used by session. – tomexsans Jun 19 '14 at 01:38
  • session name is phppos encryption key is 32 chars. Not sure what you mean by my script override the memory used by session. – Chris Muench Jun 19 '14 at 01:42
  • `sess_time_to_update` should be less than `sess_expiration` which is not in your case. im using http://upshots.org/codeigniter/codeigniter-ajax-session-bug-fix this with an extra column `prevent_update` in session table. – Karan Thakkar Jun 19 '14 at 05:27
  • @karanthakkar the reason I made it so high was so when it is time to re-generate the session it never would. Do you think this is bad? I am looking into use just plain old php sessions – Chris Muench Jun 19 '14 at 05:33
  • @ChrisMuench if the session is not updated you will have unchanged `session_id`. that is invitation to security issues and `last_activity` will also `not update` as it is updated at every update. I found the tutorial i was talking about and here it goes http://blog.jonaszamora.es/en/codeigniter-fix-for-session-loss-caused-by-ajax/ – Karan Thakkar Jun 19 '14 at 05:51
  • Thank you for the link but I am just going to use native sessions. I found a library on github that is a drop in replacement. I just don't trust the database backed sessions. See http://stackoverflow.com/questions/24299433/native-session-modified-for-code-igniter-2-1-4-does-this-make-sense – Chris Muench Jun 19 '14 at 06:14

1 Answers1

6

I think you're suffering AJAX session race condition. I had the same problem, and I solved it overriding the CI session class. You have the article here:

https://web.archive.org/web/20181223212220/http://www.hiretheworld.com/blog/tech-blog/codeigniter-session-race-conditions

The cleanest thing would be use a MY_Session class, in your libraries folder, and then override the methods written in the link above. Sorry for the lack of indentation, :P

class MY_Session extends CI_Session
{
    public function __construct()
    {
        parent::__construct();
    }


/*********************************************************************
 * overrides to codeigniter session library
 *
 * Handle race condition during session_id updates.
 * - Keep the old session id around in case we have to handle race
 *   conditions.
 * - Changes are marked with the string "old_session_id_changes".
 *
 * session table changes:
 * ALTER TABLE `sessions` ADD COLUMN `old_session_id` VARCHAR(40)  DEFAULT NULL COMMENT 'old session id' AFTER `user_data`,
 *  ADD INDEX `old_session_id`(`old_session_id`);
 * DELETE FROM `sessions`;
 *********************************************************************/
 
/**
 * Fetch the current session data if it exists
 *
 * @return  bool
 */
public function sess_read()
{
  // Fetch the cookie
  $session = $this->CI->input->cookie($this->sess_cookie_name);
 
  // No cookie?  Goodbye cruel world!...
  if ($session === NULL)
  {
    log_message('debug', 'A session cookie was not found.');
    return FALSE;
  }
 
  // Decrypt the cookie data
  if ($this->sess_encrypt_cookie === TRUE)
  {
    $session = $this->CI->encrypt->decode($session);
  }
  else
  {
    // encryption was not used, so we need to check the md5 hash
    $hash  = substr($session, strlen($session)-32); // get last 32 chars
    $session = substr($session, 0, strlen($session)-32);
 
    // Does the md5 hash match?  This is to prevent manipulation of session data in userspace
    if ($hash !==  md5($session.$this->encryption_key))
    {
      log_message('error', 'The session cookie data did not match what was expected. This could be a possible hacking attempt.');
      $this->sess_destroy();
      return FALSE;
    }
  }
 
  // Unserialize the session array
  $session = $this->_unserialize($session);
 
  // Is the session data we unserialized an array with the correct format?
  if ( ! is_array($session) OR ! isset($session['session_id'], $session['ip_address'], $session['user_agent'], $session['last_activity']))
  {
    $this->sess_destroy();
    return FALSE;
  }
 
  // Is the session current?
  if (($session['last_activity'] + $this->sess_expiration) < $this->now)
  {
    $this->sess_destroy();
    return FALSE;
  }
 
  // Does the IP match?
  if ($this->sess_match_ip === TRUE && $session['ip_address'] !== $this->CI->input->ip_address())
  {
    $this->sess_destroy();
    return FALSE;
  }
 
  // Does the User Agent Match?
  if ($this->sess_match_useragent === TRUE && trim($session['user_agent']) !== trim(substr($this->CI->input->user_agent(), 0, 120)))
  {
    $this->sess_destroy();
    return FALSE;
  }
 
  // Is there a corresponding session in the DB?
  if ($this->sess_use_database === TRUE)
  {
    /*
     * begin old_session_id_changes
     *
     * Search both session_id and old_session_id fields for the
     * incoming session id.
     *
     * used to be:
     * $this->CI->db->where('session_id', $session['session_id']);
     *
     * Manually create the OR condition because it causes the least
     * disturbance to existing code.
     *
     * Store the session id from the cookie so that we can see if we
     * came in through the old session id later.
     */
    $this->CI->db->where( '(session_id = ' . $this->CI->db->escape($session['session_id']) . ' OR old_session_id = ' . $this->CI->db->escape($session['session_id']) . ')' );
    $this->cookie_session_id = $session['session_id'];
    /*
     * end old_session_id_changes
     */
 
    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->limit(1)->get($this->sess_table_name);
 
    // No result?  Kill it!
    if ($query->num_rows() === 0)
    {
      $this->sess_destroy();
      return FALSE;
    }
 
    // Is there custom data?  If so, add it to the main session array
    $row = $query->row();
    if ( ! empty($row->user_data))
    {
      $custom_data = $this->_unserialize($row->user_data);
 
      if (is_array($custom_data))
      {
        foreach ($custom_data as $key => $val)
        {
          $session[$key] = $val;
        }
      }
    }
 
    /*
     * begin old_session_id_changes
     *
     * Pull the session_id from the database to populate the curent
     * session id because the old one is stale.
     *
     * Pull the old_session_id from the database so that we can
     * compare the current (cookie) session id against it later.
     */
    $session['session_id'] = $row->session_id;
    $session['old_session_id'] = $row->old_session_id;
    /*
     * end old_session_id_changes
     */
  }
 
  // Session is valid!
  $this->userdata = $session;
  unset($session);
 
  return TRUE;
}
 
// --------------------------------------------------------------------
 
/**
 * Write the session data
 *
 * @return  void
 */
public function sess_write()
{
  // Are we saving custom data to the DB?  If not, all we do is update the cookie
  if ($this->sess_use_database === FALSE)
  {
    $this->_set_cookie();
    return;
  }
 
  // set the custom userdata, the session data we will set in a second
  $custom_userdata = $this->userdata;
  $cookie_userdata = array();
 
  // Before continuing, we need to determine if there is any custom data to deal with.
  // Let's determine this by removing the default indexes to see if there's anything left in the array
  // and set the session data while we're at it
  foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
  {
    unset($custom_userdata[$val]);
    $cookie_userdata[$val] = $this->userdata[$val];
  }
 
  /*
   * begin old_session_id_changes
   *
   * old_session_id has its own field, but it doesn't need to go into
   * a cookie because we'll always retrieve it from the database.
   */
  unset($custom_userdata['old_session_id']);
  /*
   * end old_session_id_changes
   */
 
  // Did we find any custom data?  If not, we turn the empty array into a string
  // since there's no reason to serialize and store an empty array in the DB
  if (count($custom_userdata) === 0)
  {
    $custom_userdata = '';
  }
  else
  {
    // Serialize the custom data array so we can store it
    $custom_userdata = $this->_serialize($custom_userdata);
  }
 
  // Run the update query
  $this->CI->db->where('session_id', $this->userdata['session_id']);
  $this->CI->db->update($this->sess_table_name, array('last_activity' => $this->userdata['last_activity'], 'user_data' => $custom_userdata));
 
  // Write the cookie.  Notice that we manually pass the cookie data array to the
  // _set_cookie() function. Normally that function will store $this->userdata, but
  // in this case that array contains custom data, which we do not want in the cookie.
  $this->_set_cookie($cookie_userdata);
}
 
// --------------------------------------------------------------------
 
/**
 * Update an existing session
 *
 * @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;
  }
 
  // _set_cookie() will handle this for us if we aren't using database sessions
  // by pushing all userdata to the cookie.
  $cookie_data = NULL;
 
  /*
   * begin old_session_id_changes
   *
   * Don't need to regenerate the session if we came in by indexing to
   * the old_session_id), but send out the cookie anyway to make sure
   * that the client has a copy of the new cookie.
   *
   * Do an isset check first in case we're not using the database to
   * store extra data.  The old_session_id field only exists in the
   * database.
   */
  if ((isset($this->userdata['old_session_id'])) &&
      ($this->cookie_session_id === $this->userdata['old_session_id']))
  {
    // 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];
    }
 
    $this->_set_cookie($cookie_data);
    return;
  }
  /*
   * end old_session_id_changes
   */
 
  // 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 = '';
  do
  {
    $new_sessid .= mt_rand(0, mt_getrandmax());
  }
  while (strlen($new_sessid) < 32);
 
  // 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 and update the session data array
  $this->userdata['session_id'] = $new_sessid = md5(uniqid($new_sessid, TRUE));
  $this->userdata['last_activity'] = $this->now;
 
  // 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];
    }
 
    /*
     * begin old_session_id_changes
     *
     * Save the old session id into the old_session_id field so that
     * we can reference it later.
     *
     * Rewrite the cookie's session id if there are zero affected rows
     * because that means that another request changed the database
     * under the current request.  In this case, we want to return a
     * value consistent with the previous request.  Reread immediately
     * after the update call here to minimize timing problems.  This
     * should be in a transaction for databases that support them.
     *
     * Also rewrite the userdata so that future calls to sess_write
     * will output the correct cookie data.
     *
     * used to be:
     * $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)));
     */
    $this->CI->db->query($this->CI->db->update_string($this->sess_table_name, array('last_activity' => $this->now, 'session_id' => $new_sessid, 'old_session_id' => $old_sessid), array('session_id' => $old_sessid)));
 
    if ($this->CI->db->affected_rows() === 0)
    {
      $this->CI->db->where('old_session_id', $this->cookie_session_id);
      $query = $this->CI->db->get($this->sess_table_name);
 
      // We've lost track of the session if there are no results, so
      // don't set a cookie and just return.
      if ($query->num_rows() == 0)
      {
        return;
      }
 
      $row = $query->row();
      foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
      {
        $this->userdata[$val] = $row->$val;
        $cookie_data[$val] = $this->userdata[$val];
      }
 
      // Set the request session id to the old session id so that we
      // won't try to regenerate the cookie again on this request --
      // just in case sess_update is ever called again (which it
      // shouldn't be).
      $this->cookie_session_id = $this->userdata['old_session_id'];
    }
    /*
     * end old_session_id_changes
     */
  }
 
  // Write the cookie
  $this->_set_cookie($cookie_data);
}
 
/*********************************************************************
 * end overrides to codeigniter session library
 *********************************************************************/
Sumit
  • 2,242
  • 1
  • 24
  • 37
Federico J.
  • 15,388
  • 6
  • 32
  • 51
  • That was a good article that I didn't find before. It seems even though I essentially eliminated session rotation it can still happen. I have decided to use native php sessions instead as database driven sessions still have a couple other problems. – Chris Muench Jun 24 '14 at 14:42
  • If you're using PHP native session with CI, check this library, works like a charm: https://bitbucket.org/xperez/core-session-storage-for-codeigniter. I hate the way of sending all the time the user data to the cookie, if it's too long, it leads you to loose your session with the sent of the request – Federico J. Jun 24 '14 at 14:53
  • Maybe your problem is you're storing so much data, your cookie is so long, and when sending the request the browser cut the cookie. As CI uses the cookie to authenticate you, if it receives a shorter cookie it can't decode it and it thinks you're not authenticated. – Federico J. Jun 24 '14 at 14:57