Welcome Guest, Not a member yet? Register   Sign In
(Seemingly) random session destruction
#1

[eluser]treeface[/eluser]
Hello all,

I'm curious if you could lead me in the right direction as I'm a bit confounded by a problem I'm having. Every once in a while as I'm browsing my site, I will be logged out. The only pattern I've been able to find (and I'm not sure this is even accurate) is that it tends to happen more often (though not always) when I make a second get request before the first one has finished. Basically what I mean is I will click two separate links in rapid succession before the first click has had a chance to complete its task. It doesn't always destroy the session when I do this, but sometimes it does.

If anyone has any ideas, I'd love to hear them...

Thanks!

<b>edit:</b> I've been able to track down the exact process by which I can replicate this now. If you click one link, wait a half second, then click another *before* the first link loads, it destroys the session. So there's a period of time after the first GET has processed (I assume) the session class and before it's executed its operation where it will log me out if I click another link.

I'm at a complete loss for where to start with solving this. Thoughts?
#2

[eluser]jedd[/eluser]
Hi treeface,

More information please -- have a read of [url="//forums/viewthread/148442/"]this thread[/url] where I cite 4 other threads worth reading, and ask a few questions.

I should probably get around to transcribing some of this stuff to the wiki I guess - it comes up regularly enough.

Btw, try to avoid editing extant posts unless it's a typo fix or something trivial - it's harder to track flow of actions and results if a single post gets modified.

When you say you can replicate this process you don't mention what happens if you wait more or less than a half second, or how accurate that measurement is, or where the two components (server and browser) are located - same machine (which is common for development) or separate machines (which might introduce some timing concerns).

Keep assumptions well separated from observations and expectations.
#3

[eluser]treeface[/eluser]
Hey jedd,

Thanks for the helpful response! My sincerest apologies for the ramble-of-thought that was my initial post. I'm used to other forums where the rule of the land is to edit your original post with an <b>edit</b> tag...my fault!

I am in the process of reading all of those threads, but in the meantime, here are some details about the problem...

1. Let's say there are two links on my website (sorry, it's currently live but admin-only, so I can't show it to you) side-by-side. When you are logged in (encrypted sessions using session table in DB for validation), if you click on one of them the browser (and this is any browser I've tried...Chrome, Firefox, Safari, IE) will start the get request to the server (which is located in California where I am also located, but this is on the internet, not a dev environment).

2. Before the browser departs from the original page with the two links, but after you've clicked the first link, you still have the ability to interact with the original page. During this time, if you click the second link (say) 200 milliseconds or less after the first one, it just goes to the second link with no problems. No session destruction at all.

3. If you click on the second link after 200 milliseconds but before the browser departs the original page, it will try to go to the second page, but somewhere along the way, the session will have been destroyed, thus redirecting me back to my login page.

I try to replicate this on other CI sites (like this forum, for example) and it doesn't, at first glance, seem to happen. So clearly it's something to do with what I'm doing wrong, but I don't know where to start looking (that is, after I get done reading those threads). Also, I should mention that I have a small extension to the CI_Session and CI_Router libraries that go like this:

Code:
/*
* Session Class Extension
*/
class MY_Session extends CI_Session {
   /*
    * Do not update an existing session on ajax calls
    *
    * @access    public
    * @return    void
    */
    function sess_update() {
        if ( !isAjax() ){
            parent::sess_update();
        }
    }
}

And this...

Code:
class MY_Router extends CI_Router {

    var $error_controller = 'error';
    var $error_method_404 = 'error_404';

    function My_Router() {
        parent::CI_Router();
    }

    // this is just the same method as in Router.php, with show_404() replaced by $this->error_404();
    function _validate_request($segments) {
        // Does the requested controller exist in the root folder?
        if (file_exists(APPPATH.'controllers/'.$segments[0].EXT)) {
            return $segments;
        }

        // Is the controller in a sub-folder?
        if (is_dir(APPPATH.'controllers/'.$segments[0])) {        
            // Set the directory and remove it from the segment array
            $this->set_directory($segments[0]);
            $segments = array_slice($segments, 1);

            if (count($segments) > 0) {
                // Does the requested controller exist in the sub-folder?
                if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$segments[0].EXT)) {
                    return $this->error_404();
                }
            }
            else {
                $this->set_class($this->default_controller);
                $this->set_method('index');

                // Does the default controller exist in the sub-folder?
                if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->default_controller.EXT)) {
                    $this->directory = '';
                    return array();
                }
            }

            return $segments;
        }

        // Can't find the requested controller...
        return $this->error_404();
    }

    function error_404() {
        $segments = array();
        $segments[] = $this->error_controller;
        $segments[] = $this->error_method_404;
        return $segments;
    }

    function fetch_class() {
        // if method doesn't exist in class, change
        // class to error and method to error_404
        $this->check_method();

        return $this->class;
    }

    function check_method() {
        $class = $this->class;
        if (class_exists($class)) {
            if ($class == 'doc') {
                return;
            }

            if (! in_array('_remap', array_map('strtolower', get_class_methods($class)))  // don't check controllers using _remap()
                    && ! in_array(strtolower($this->method), array_map('strtolower', get_class_methods($class)))) {
                $this->class = $this->error_controller;
                $this->method = $this->error_method_404;
                include(APPPATH.'controllers/'.$this->fetch_directory().$this->error_controller.EXT);
            }
        }
    }    
}

Please let me know if you need any more info or anything at all. Thanks again for your help!
#4

[eluser]WanWizard[/eluser]
This can happen.

The session library (by default) rotates the session id every 300 seconds.

If you do a page request that triggers such a rotation, but abort the request (by pressing escape, clicking on another link, etc), your browser will not receive the cookie with the new session id, but the session will be rotated by the application. Even if you abort the request, once the server starts processing it, it will finish it. Even if the output has nowhere to go.

This way you end up with a session id in your cookie that is no longer valid.

I posted a suggestion for a MY_Session some time ago that kept both the current session id and the previous session id in the database, and if a session id was not found, would check the previous id column. A little less secure, but a saveguard against this kind of problems...
#5

[eluser]treeface[/eluser]
WanWizard....awesome! It sounds like that's exactly what's happening. I will try this later tonight and report back here with the news.

Thanks a lot WanWizard and jedd!
#6

[eluser]treeface[/eluser]
Hey WanWizard,

I'm having a tough time implementing this such that it will work. I've done the following things, but it still logs out as I described above. I've added a field in the sessions table called prev_session_id, I've added this code to sess_update():

Code:
//starting around line 197 where it does the check for sess_use_database
$qStr = "SELECT * FROM ".$this->sess_table_name." WHERE (session_id=? OR prev_session_id=?)";
$paramArr = array($session['session_id'], $session['session_id']);
//$this->CI->db->where('session_id', $session['session_id']);

if ($this->sess_match_ip == TRUE)
{
    $qStr .= " AND ip_address=?";
    $paramArr[] = $session['ip_address'];
    //$this->CI->db->where('ip_address', $session['ip_address']);
}

if ($this->sess_match_useragent == TRUE)
{
    $qStr .= " AND user_agent=?";
    $paramArr[] = $session['user_agent'];
    //$this->CI->db->where('user_agent', $session['user_agent']);
}
    
$query = $this->CI->db->query($qStr, $paramArr);
//$query = $this->CI->db->get($this->sess_table_name);

In sess_write() I've added this middle line to around line 280 or so:

Code:
// Run the update query
$this->CI->db->where('session_id', $this->userdata['session_id']);
$this->CI->db->or_where('prev_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));

And in sess_update() I altered this following line to include an update for the prev_session_id column aroudn line 380:

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

If you could point out any errors in my thinking or if I'm missing something somewhere, I'd be eternally grateful. :-]
#7

[eluser]WanWizard[/eluser]
I don't see anything wrong with this code (disclaimer: I'm still on my first coffee).

Guess it's time for some serious debugging. Start by activating the profiler so you can look at the contents of the session after each page request, and the queries that are run against the session table. That will show you what happens with the session_id.

If it changes after you're thrown back to the login, you will have to add some debugging to the session library to see the contents of the cookie received, and the actions the libraries takes. Use firebug or log_message() calls. If it doesn't change, than the issues is not in the session library, but elsewhere. Again, the contents of the session in the profiler should show you what is missing, and might lead you to the code that deletes those missing items from the session.




Theme © iAndrew 2016 - Forum software by © MyBB