Improving Session Stability in CakePHP 2.x

Original author: Mark Scherer
  • Transfer
  • Tutorial
From a translator: when developing Web-payment.ru on the CakePHP framework, we were faced from the very beginning with the fact that user logs occurred every few hours, and this is too short a period of time. At the same time, no matter how much timeout and cookieTimeout we would set in the kernel settings, nothing changed. This article has solved this problem for us.

Last year, I addressed this issue at least twice, but to no avail. After a long search for a solution, I again postponed it indefinitely. It should also be said here that when it comes to debugging session / cookie things (in this case, authentication), the process of searching for bugs has never been simple, since it depends on many factors that, when combined, complicate the detection of a problem.

The Cake documentation also does not say that for long-running php sessions it is necessary to increase the value of the max_lifetime internal variable. I generally stumbled upon it by accident, because I always thought that the framework itself took care of it, however, even after I made the database a container of sessions, I did not notice any improvements. Therefore, I thought that cleaning the server-side memory here apparently had nothing to do with it, or at least it affects not only it alone.

So, in the end, we have the following:
  • Setting timeouts was a waste of time, as was changing the Security level, which I set to low .
  • Moving from php / cake to the database didn't work either, although of course it does provide a bit more control.
  • Increasing the value of gc_maxlifetime in php.ini, again, did not give much effect.

After all these actions, the logouts continued at completely different time intervals - sometimes after a few minutes, sometimes a few hours, but nothing more. This problem is important not only for creating social networks, but also for other sites running on Cake.

I tried several times to add encrypted cookies, such as RememberMe, to quickly re-login at that moment of logout, however, due to a PHP bug related to srand / mt_srand and suhosin, this solution did not work. Thanks to Miles ’s post about the bug, I decided to take the problem seriously and do something about it (many users complained about inexplicable logouts over the past few months).

There is also another problem remotely related to this situation, the essence of which is that PHP (Sake) stores session cookies, determining a fixed lifetime for them, however, their lifetime is not updated during requests and thus it turns out that at some point, session cookies become invalid. Currently, there is nothing we can do about it ...

Automatic login return as a solution to the problem


Several years passed, and finally, I still took advantage of Miles’s draft work and set up its AutoLogin component to work with my applications.
The essence of the component’s work is that it stores the user data in a cookie during login, and as soon as the session is lost again for some unknown reason, the component restores it. This is quiet and does not interfere with the user's work.
You can find the component code in in my Tools plugin (use this repository).

Features of use

Having successfully tested it in several Cake2.x applications, I want to share with you a simple guide to its use. To connect it, you just need to add the component globally in the AppController:
public $components = array('Session', 'RequestHandler', 'AutoLogin', 'Auth', ...);

It is important to declare its connection before connecting the authorization component, in order to avoid any erroneous messages about “no authorization”.

You can use /Config/configs.php (or any other configuration directory) to determine its settings:
$config['AutoLogin'] = array(
    'controller' => 'account',
    'username' => 'login',
    'requirePrompt' => false
    ... //для ознакомления с другими параметрами смотрите описание компонента
);

I ask you to be careful when using the last parameter - requirePrompt . When you turn it off, AutoLogin will be used for all attempts to log in. This requires users to constantly log out correctly while using the site on public networks (especially dangerous for Internet cafes, where strangers can take over a user’s account even a few days after the last use). Therefore, you must make sure that the component is used only on sites where all users are warned and aware of this.

That is, in the usual case, you need to add a checkbox to the authorization form:
if (Configure::read('AutoLogin.active')) {
    echo $this->Form->input('auto_login', array('type'=>'checkbox', 'label'=>__('Запомнить меня')));
}

That's all. The component can be easily tested by storing sessions in the database and trimming the session table after login. The component will return the user back, and also create a new row for the session in the database table. If everything is done as it should, the user will not even notice that he lost authorization for a fraction of a second.

The above if construct can be omitted. I use it to dynamically turn on / off the component depending on the environment in which it works, however if you intend to use it, make sure that you set active to true in Configure.

Now about fixing the suhosin bug. According to the explanation of Miles from the links above, most Linux Apache environments come with suhosin patch by default, which changes the way srand works. In the case of my WAMP server, this is not so, so everything works on it right away, however, in order for the component to work in the Linux environment, you just need to add this line to the end of /etc/php5/apache2/php.ini :
suhosin.srand.ignore = Off

And don't forget to restart Apache or do force-reload for /etc/init.d/apache2 .
Tip: I added an example to check for the presence of this bug (see the file for checking in the repository). If you are not sure if you have this problem, run it in your environment.

Other tips: If you want to disable AutoLogin for a specific site, you just need to change the active value to false in the site config or do it dynamically in the controller builder. Always remember: you cannot add it to the list of components dynamically, since you need to include it before the Auth part, however you can always disable a component if it has already been added there before.

The default debug mode is auto-detect. During development (debug> 0), it was enabled initially, however, of course, you can overwrite this value in your configs.
Another important parameter is expires - it defaults to two weeks and is easily modifiable.

Alternatives


2.x now also has alternative solutions to the problem. One of them is to use a special cookieAuthenticate adapter .

Cake session engine parsing


It took me a lot of time to understand in detail the principle of the sessions. Now I finally get it. I want to pay special attention to the fact that using the code above without a deep understanding of it is likely to simply mask the problem ... well, or only cure the symptoms of the disease, if you want. Yes, this method will work, but I think that you would have liked it better if everything worked without using the tricks with remembering the session described above.

First, we need to understand how sessions are created, validated and updated when clicking on a button:
  • The user arrives for the first time, session cookies are generated (and valid for x seconds), the session timeout value is stored in them (y seconds)
  • The user clicks again: Of course, we increase the session duration (certain y seconds + another z seconds), storing it in a cookie as a value
  • However, cookies alone do not receive a new lifetime (all the same x seconds). This is a limitation in the processing of cookies, their timeout is fixed.

Having found this out, it becomes completely clear that when the cookie specified during its creation expires, the session will be destroyed, even if you increase its life to several years. Thus, it is extremely important to understand that in order for these settings to make sense, you need to set a great value for the session duration and even more for the cookie lifetime:
Configure::write('Session', array(
    'defaults' => 'php',
    'timeout'  => 14400,  // 4 часа, обращается к 'session.gc_maxlifetime' в //настройках PHP
    'cookieTimeout' => 10 * 14400, // 20 часов, обращается к 'session.cookie_lifetime' в //настройках PHP 
    // ...
));

Both values ​​are set in minutes in the CakePHP config.
Given these changes, the user leaves the session if he:
  • did not produce any session activity (clicks, ajax) for 4 hours
  • reached a 20 hour timeout cookie (regardless of activity)

And since such settings will arrange for him to log out every 20 hours, it makes sense to make more values ​​(several months, for example) or use it in combination with the component described above to memorize the session.

So, we repeat again:
'session.gc_maxlifetime' => относительная величина, увеличивается при активности
'session.cookie_lifetime' => абсолютное значение, может быть присвоено только в //начале сессии

And do not forget to check your ini parameters, which relate to session.gc_divisor and andsession.gc_probability to make sure that the memory cleaning works and is carried out at the right time.



Also popular now: