In the end I have made my own CSRF system which turned out to be quite quick and easy and it doesn't suffer from the issues of the built in one. Mine uses a single cookie with a default expirey which can be overridden when called in a form. {{ csrfField() }}
sets the hidden field and the cookie. The cookie is valid for all forms on the site, so if someone has multiple forms open in multiple tabs, they wont have issues. For ajax form submission, it is also possible to set a new cookie and return a new csrf field with the error in the form. So the user can just resubmit with out refreshing and loosing their content.
/**
* Generate the CSRF stamp (name) and token in the cookie & field
*/
public static function csrfField($expire = 60) {
$csrf = \Phalcon\DI::getDefault()->getShared('cookies')->get('csrf');
$exp = explode('|', $csrf);
if (ISSET($exp[0]) && ISSET($exp[1])) {
$stamp = $exp[0];
$hash = $exp[1];
return '<input type="hidden" name="' . $stamp . '" value="' . $hash . '">';
} else {
$seed = mt_rand(1000, mt_getrandmax());
$hash = md5($seed);
$stamp = time();
\Phalcon\DI::getDefault()->getShared('cookies')->set('csrf', $stamp . '|' . $hash, time() + ($expire * 60));
if(\Phalcon\DI::getDefault()->getShared('cookies')->get('csrf')) {
return '<input type="hidden" name="' . $stamp . '" value="' . $hash . '">';
}
}
}
/**
* Check the CSRF tocken
* @return bool
*/
public static function csrfCheck() {
$csrf = \Phalcon\DI::getDefault()->getShared('cookies')->get('csrf');
$exp = explode('|', $csrf);
if (ISSET($exp[0]) && ISSET($exp[1])) {
$stamp = $exp[0];
$token = $exp[1];
$str = \Phalcon\DI::getDefault()->getShared('request')->getPost("$stamp");
if ($str === $token) {
return TRUE;
}
}
return FALSE;
}