My application tends to live a long live in the client space and for several reasons the user might still have a web page open where one CSRF token is used, but where the CSRF token is no longer valid (session expiry after disconnect, login/logout in other tab/…).
I alread fixed the fact that the enduser might open several tabs pointing to the site "at the same time" (when the closed navigator is reopened with remembered tab urls), which implied a change in CHttpRequest (by extending the class in a YHttpRequest I created and add some CSRF caching there)[size=2]. [/size]
[size=2]
[/size]
Next thing I would like to do is to force a reload of the page when the CSRF token is no longer valid.
I can see two methods:
a) Polling the server and let the server decide if the CSRF token is still valid;
Compare locally with the YII_CSRF_TOKEN cookie.
In both cases the ideal would be a blocking popup with a button for the user to proceed with reloading the page.
I am checking if somebody already did this to avoid writing the code. I haven’t seen an extension for this, but there could very well be one for it. I’ld appreciate the sharing ;-).
function manageError(jqXHR) {
dialog = $('<div id="errorDialog"></div>').dialog({
autoOpen: false,
closeOnEscape: true,
modal: true,
resizable: false,
show: {effect: 'drop', direction: 'up'},
close: function(event, ui) {
dialog.dialog('destroy');
$('#errorDialog').remove();
window.location.reload();
},
title: 'Something went wrong with your request'
});
if ((jqXHR.status == 400) && (jqXHR.responseText === 'Here you can put a string that is surely in the response text when CSRF is no longer valid')) {
CSRF_error = '<h1>You have exceeded the time allocated to filling this form in. This page is going to refresh.</h1>';
}
dialog.html(CSRF_error);
dialog.dialog('open');
}
…
// I use this ajax snippet as a separate ajax call, but you can use for long polling
$.ajax({
'type':'POST',
'url':url,
'cache':false,
'error':function(jqXHR){
manageError(jqXHR);
}
});
Edit: I am using here jQuery+jQuery UI. The latter is not mandatory as there are several modal window libraries out there, you just use the one that suits you.
Good to see that others experienced the same issue & thanks for sharing.
I am kind of thinking of a solution that does not require me to add this to every ajax call which is built with Yii’s utility functions.
At the javascript level it would be interesting to use one of the methods that allow to hook into $.ajax events (I believe that is possible).
I almost preferred the initial post (in french) as that almost avoids me to translate although my rule is to use Yii::t everywhere.
I’ll probably resort to checking the YII_CSRF_TOKEN cookie value with the known value in the page to void that the user enters a lot of configuration which he would have to re-enter when his token expired.
Well that app was an ajax-based one so it was not a big deal in my case… But you can see here: http://api.jquery.com/ajaxError/ it should work
So you had time to see it
But what’s the purpose then of having CSRF expiration? Shouldn’t you just extend its validity? Also you could use the not-so-rare scenario where seconds before expiration, you ask the user something like “Are you still here?” but I guess it really depends on your app.
By doing this, I got this alert on initial page load (after deleting the YII_CSRF_TOKEN in the cookie) and it turns out that my page has differrent YII_CSRF_TOKENs in the same HTML page! That can not be ok and might be the real reason behind most CSRF_TOKEN issues.
I found this same issue on my live server but couldn’t reproduce it in local. In application log i found that on form submission $_POST array is empty causing CSRF token failure. Do anyone knows about this issue?
It seems that my code needs a correction for inexisting tokens: [size="2"]
var r=(csrf===null);
should be [/size][size="2"]
var r=(csrf===undefined);
(two times).[/size]
[size="2"]2. I you have httpOnly set to true for the CSRF cookie, I suppose that you have already overloaded the CHttpRequest class because I do not see how you can set it with the default class.
In that case, I suggest that you create another which is not protected by httpOnly. It could be a hash and/or timestamp of the CRSF cookie which you use to check if the CSRF cookie was updated. So you would have one cookie hidden to javascript and the other visible.[/size]
[size="2"]A hash of the cookie would be easiest IMHO and you could add the hash algorithm in the Monitoring code (or move the monitoring code your CHttpRequest implementation so that all related code is in one location).[/size]
Right, I didn’t think of nested configuration values.
For info, my overloaded createCookie function looks like this (it relies on ENinjaMutex).
When a user opens his browser, he may have multiple pages pointing at your site and all will try to get a CSRF token.
So this code makes CSRF generation for a user exclusive (based on userHostAddress and userAgent - not "perfect" but good enough) and caches the token for 30 seconds in order to reuse the same token as the one that was created for the other tabs.