Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel session expires randomly

We had this issue on our website that we got CSRF errors randomly from our users. The session cookie and session data were set to be expired in 12 hours and session driver is set to use Redis. Further to our investigations we finally succeed to simulate the exception condition, so here is the scenario:

A user opens two different pages on the site, using Chrome browser with “open last closed tabs” setting on. One of the pages has a form on it (e.g. login) then the user quits the browser at some point. He reopens his browser next day (12 hours is passed so session cookie and session data are expired) Chrome tries to reload all the pages which were opened. It sends two simultaneous requests to the server while none of them has session cookie. At the server end Laravel generates two different session ID for each. Chrome receives them and overrides one on the other session cookie. Once the user attempts to submit the form (e.g. login), it generates CSRF error as the form session cookie is overridden.

We also had some AJAX post requests which we got failed CSRF errors due to this condition.

I was wondering whether or not Laravel can generate the same session ID for both requests in a secure manner.

Does anyone have any ideas how we can fix this issue?

P.S: we are using laravel 4.1 with this session configuration:

return array(

    'driver' => 'redis',

    'lifetime' => 720,

    'expire_on_close' => false,

    'files' => storage_path().'/sessions',

    'connection' => null,

    'table' => 'sessions',

    'lottery' => array(2, 100),

    'cookie' => 'laravel_session',

    'path' => '/',

    'domain' => '.ourdomain.com',
);
like image 314
Amir Avatar asked Jan 14 '15 08:01

Amir


1 Answers

After a lot of investigation on the issue in our company, I came to this result, I hope it helps, first of all here is our environment specifications:

  • PHP: 5.3.3
  • LARAVEL: 4.1
  • OS: centos 6 on server and os x mavericks in development environment
  • APACHE: 2
  • MYSQL: 5.6.19
  • REDIS: 2.4.10
  • PREDIS: 0.8.*

First of all it seems that the TokenMistmatch exception occurs in a varied different conditions, I nearly investigated all of them and was able to solve some of them, some depends on the logic behind the session and some can be bugs. In the following i will explain each situation that I have faced.

1. Expired sessions

Let's say you have configured your session for 3 hours a user opens up a form and for some reason he leaves the computer (getting a cup of coffee) so after 3 hours when the session is expired he tries to submit a form and gets a token exception. this is why everyone once in while gets a token errror regardless of how much stable the application is, I can imagine of 2 ways to prevent it and they're renewing the session cookie using ajax on a timely basis, or increasing the session expire time to a considerable amount of time.

2. Concurrent requests when session is expired

Sometimes on the load of your first page concurrent requests happen, for example the user has two different tabs open on chrome and when he reopens chrome chrome sends the requests simultaneously or you may have multiple concurrent ajax request on the load of the first page of your application. so note that the session cookie is received after the first response is received but you may send the next request before this happens and therefore you will get a new session (new cookie) on each requests, this can lead to token exception if one of these requests populates a form. you can prevent this scenario based on it's source, for example if the ajax request are causing the problem and you do not use session in ajax responses, you can disable sending the session cookie if the request is ajax, in the second scenario (clicking the submit button multiple times) you can simple disable the button once it's submitted.

3. Concurrent requests on login

When login is occurred, laravel for security reasons changes the session id, copy the session data and DESTROYS THE LAST SESSION so let say for some reason when logging in concurrent requests happen (clicking the login button multiple times) so the session id would be regenerated multiple times and DESTROYS the last generated sessions in the server side, as some of these requests still use the prior session id (which does not exist in the server side anymore) it leads to regenerating the CSRF token, please note that normally laravel does not regenerate the token on login if it can find the token in guest (not logged in) session, but in this case as the guest session is destroyed as a result it will regenerate the token and it can result to token exception in other requests that are using the original token. also note that not only this issue can result in token exception it can also result in the user being logged out after one request, because the concurrent requests can change the logged in session. I solved this issue by changing one line in the laravel code, in Illuminate\Auth\Guard:updateSession:

 protected function updateSession($id)
 {
        $this->session->put($this->getName(), $id);

        //$this->session->migrate(true);
        $this->session->migrate()
 }

passing true to the migrate method of the session store will result in destroying the session on the server after migration, so now the data will remain on the server and destroyed on their expire time rather than on this request and it will solve the issue. I don’t know if we can call this a bug in laravel, but I guess we can come up with a better solution for this.

4. Browsers

After investigating the logs it turned out that some of these token exceptions were because of the user’s browser not accepting the session cookie,I saw it the most on iOS Safari, I believe this is something we can do nothing about it.

5. Redis bug

Some of the token exceptions on our sever was because of redis, for some reason laravel could not read the session data from the redis server when opening the session so it would result to the regeneration of the CSRF token. it was happening randomly on our server, so I tried changing the session driver and this type of exceptions faded away. I tried database, apc and file drivers and none produced this issue. I have not yet found what is causing the bug but i think it can be a bug with redis or the predis library. (as you know laravel 4.1 is not using the latest version of predis because of compatibility issues).

Okay these are my experiences of the last 2 month working on this issue. I hope it may change your point of view regarding solution to this issue, the most important thing is that the token exception does not happen because of one reason it’s can be a result of multiple issue. please share with me if you have had similar incident or you have something new.

like image 169
Amir Avatar answered Nov 09 '22 09:11

Amir