CakePHP’s security component is awesome. I use it all the time for its CSRF protection (Which will be the subject of a future post).
But sometimes, it does not work as expected. Frustration ensues.
In one of my projects, I have a form with a few text areas on it. The user uses this form to create or edit a story. After adding the security component to the controller, I noticed that when the user submits the form they get blackholed, and the story was not saved. With users pouring their hearts into the stories, this is really bad.
This behaviour seemed strange, to say the least.
The first thing I did was look for the session timing out on me. In core.php I have the following:
Configure::write('Session.timeout', '120');
based on Cake’s configuration, these should set the session timeout to 20 minutes (120 multiplied by 10). This may be a little too short for writing stories, so I raised the timeout value to 240 – resulting in 40 minutes, which is definitely enough. The stories are not THAT long.
However, this did not change the system’s behaviour. It almost seemed like the session times out long before the 40 minutes passed.
Enter Security component
When I removed the Security component from the controller, all was working great. It was time to delve into the code.
So, how does this security component work, anyway?
When you use the security component, Cake generates your forms differently.
Notice: In order for the security component to work correctly, you must use the form helper to create all of the fields on the form, as well as creating the form with $form->create() and closing it with $form->end().
Consider this in the view:
//add.ctp
echo $form->create('Story', array('target'=> '_parent') );
echo $form->input('title' , array('label' => false));
echo $form->input('excerpt', array('rows' => '20', 'cols' => '80', 'label'=> false));
echo $form->input('body', array('rows' => '20', 'cols' => '80', 'label'=> false));
echo $form->input('notes', array('rows' => '20', 'cols' => '80', 'label'=> false));
echo $form->end(array('name' => 'save'));
?>
When the security component is not activated, the resulting form looks like this:
<fieldset style="display:none;">
<input type="hidden" name="_method" value="POST" />
</fieldset>
<div class="input text">
<input name="data[Story][title]" type="text" value="" id="StoryTitle" />
</div>
<div class="input text">
<textarea name="data[Story][excerpt]" cols="80" rows="20" id="StoryExcerpt" ></textarea>
</div>
<div class="input text">
<textarea name="data[Story][body]" cols="80" rows="20" id="StoryBody" ></textarea>
</div>
<div class="input text">
<textarea name="data[Story][notes]" cols="80" rows="20" id="StoryNotes" ></textarea>
</div>
<div class="submit">
<input type="submit" name="save" value="Submit" />
</div>
</form>
As you can see, this is just a simple html form.
To have the security component work its magic, you add it to the uses array in the controller:
var $components = array('Security','Auth','RequestHandler');
With the Security component activated this way, the form is generated with a few extra fields:
<fieldset style="display:none;">
<input type="hidden" name="_method" value="POST" />
<input type="hidden" name="data[_Token][key]" value="5a364d27f9e39686bb6032ceed0a4ebcc1abba8e" id="Token1821306795" />
</fieldset>
<div class="input text">
<input name="data[Story][title]" type="text" value="" id="StoryTitle" />
</div>
<div class="input text">
<textarea name="data[Story][excerpt]" cols="80" rows="20" id="StoryExcerpt" ></textarea>
</div>
<div class="input text">
<textarea name="data[Story][body]" cols="80" rows="20" id="StoryBody" ></textarea>
</div>
<div class="input text">
<textarea name="data[Story][notes]" cols="80" rows="20" id="StoryNotes" ></textarea>
</div>
<div class="submit">
<input type="submit" name="save" value="Submit" />
</div>
<fieldset style="display:none;">
<input type="hidden" name="data[_Token][fields]" value="6e6401136c6c253e621550b05eaefa112da437c2%3An%3A0%3A%7B%7D" id="TokenFields1859539122" />
</fieldset>
</form>
Assuming you used the $form helper to generate the form and all its fields, the Security component adds two more hidden fields to it:
data[_Token][key] – this is a token that marks this form as generated using the Security component. The token is unique to each form, and when the user submits the form back, Security component verifies that the token is valid. This is to ensure that the form originated from your website, and prevents attackers from crafting similar forms themselves and submitting them to your application.
data[_Token][fields] is a hash of all the fields created using the $form helper. When the form is submitted a valid key, Security component also validates that the hash matches. This way, if an attacker managed to get a valid token, they can’t add fields to the form that weren’t there to begin with.
(Note: it is possible to instruct the Security component to disregard specific form-fields when generating and validating the data[_Token][fields] hash. You can do it in your beforeFilter() method by using the $this->Security->disabledFields array).
Blackholing Requests
If either the key or hash submitted with the form is invalid (or missing altogether), the form submit request will be blackholed. This will usually result in your users getting a blank page, and the data not saved anywhere. Obviously, this was happening to my stories, but why?
The token was valid, because I got it from the Security component to begin with. The hash was valid, as I never tempered with the form. All the signs pointed to a timeout…
Your token has a time to live as well!
Diving into Cake’s Security component code, I found the following:
..
function _generateToken(&$controller) {
...
$authKey = Security::generateAuthKey();
$expires = strtotime('+' . Security::inactiveMins() . ' minutes');
$token = array(
'key' => $authKey,
'expires' => $expires,
'allowedControllers' => $this->allowedControllers,
'allowedActions' => $this->allowedActions,
'disabledFields' => $this->disabledFields
);
...
So, when a token is generated, it gets an expiration period. Mmm… so let’s have a look at the inactiveMins() method.
..
function inactiveMins() {
$_this =& Security::getInstance();
switch (Configure::read('Security.level')) {
case 'high':
return 10;
break;
case 'medium':
return 100;
break;
case 'low':
default:
return 300;
break;
}
}
..
See what happens here? Regardless of the timeout you configure for your sessions, the token expiry time relies only on your Security.level settings. This means that my tokens may expire long before my session does. Which, apparently, is exactly what happens…
The way the session timeout is calculated, is that the number you set for Session.timeout in core.php is multiplied by a number depending on your Security.level setting.
For Security.level = high, this number is 10.
For Security.level = medium, this number is 100.
For Security.level = low, this number is 300.
See the correlation? Instead of returning the calculated session timeout, this method returns only part of the equation!
Now, there may be some very good reasons for that, but none of them help me when I want all the benefits of a high Security.level setting together with allowing my users ample time to write their stories.
It is time to change cake’s code!
..
function inactiveMins() {
$_this =& Security::getInstance();
$timeout = Configure::read('Session.timeout');
if (!isset($timeout)) {
$timeout = 1; // if not configured - use the original pattern
}
switch (Configure::read('Security.level')) {
case 'high':
$factor = 10 ;
break;
case 'medium':
$factor = 100 ;
break;
case 'low':
default:
$factor = 300 ;
break;
}
return $factor * $timeout / 60;
}
Using the piece of code above, inactiveMins(), when called, returns a number of minutes that’s equal to your session timeout. That’s it, problem solved. Security component will not blackhole my innocent users. Now, let the stories roll…
Popularity: 10% [?]
3 Responses to Cake’s Security Component Timeout problem
Leave a Reply Cancel reply
Recent Comments
- Medovarszki: This post saved my hair from falling out but yeah, I beat my head against the wall now too
- Shehryar: Probably the ebst spring web mvc tutorial I have read, explain everything in great detail. i loved it, this...
- armand: You do not resize the image. You just display it smaller using html tag attributes. It would be nice to...
- COTP: ** Just to clarify, in CakeErrorController.php (the copy in app/Controller/) public function beforeRender() {...
- COTP: For CakePHP 2.0+, you can copy lib/Cake/Controller/CakeErrorC ontroller.php to app/Controller/CakeErrorCon...
- Medovarszki: This post saved my hair from falling out but yeah, I beat my head against the wall now too
Categories
- Android (1)
- appengine (3)
- Be nice to your users (6)
- CakePHP (13)
- General (5)
- Googlemaps (3)
- Java (11)
- javaScript (4)
- jQuery (11)
- Seam (2)
- Security (4)
- Spring MVC (1)
- Spring Webflow (2)
- User Interface (5)
- Wordpress (2)
Archives
- January 2012 (2)
- December 2011 (1)
- July 2011 (2)
- June 2011 (4)
- May 2011 (1)
- December 2010 (3)
- November 2010 (1)
- August 2010 (2)
- July 2010 (2)
- June 2010 (4)
- May 2010 (4)
- April 2010 (5)
- March 2010 (3)

[...] Cake’s Security Component Timeout problem – duckranger.com [...]
Thanks for this post; you probably just saved me a considerable amount of debugging! It’s odd to me that SecurityComponent doesn’t respect app configured session timeouts by default. Have you brought this up with the core team?
Thanks! I was having this problem in two projects and this probably saved me a lot of debugging. This problem exists in both Cake 1.2 and 1.3. I hope the Cake devs correct this so that it doesn’t have to be fixed with a core hack.