Yii Framework Forum: Csrf Token Invalid For Long Sessions And Multiple Tabs - Yii Framework Forum

Jump to content

Page 1 of 1
  • You cannot start a new topic
  • You cannot reply to this topic

Csrf Token Invalid For Long Sessions And Multiple Tabs Any suggestion/implementation to reload pages when CSRF invalid? Rate Topic: -----

#1 User is offline   le_top 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 386
  • Joined: 08-June 10
  • Location:France (Ile-de-France/Val d'Oise)

Posted 29 May 2013 - 07:17 PM

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).


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;
B) 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 ;-).
0

#2 User is offline   bennouna 

  • Master Member
  • PipPipPipPip
  • Yii
  • Group: Members
  • Posts: 1,168
  • Joined: 05-January 12
  • Location:Morocco

Posted 30 May 2013 - 05:54 AM

I did it a while ago like this:

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.
0

#3 User is offline   le_top 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 386
  • Joined: 08-June 10
  • Location:France (Ile-de-France/Val d'Oise)

Posted 30 May 2013 - 06:18 AM

Hi

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.
0

#4 User is offline   bennouna 

  • Master Member
  • PipPipPipPip
  • Yii
  • Group: Members
  • Posts: 1,168
  • Joined: 05-January 12
  • Location:Morocco

Posted 30 May 2013 - 06:41 AM

View Postle_top, on 30 May 2013 - 06:18 AM, said:

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).


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

View Postle_top, on 30 May 2013 - 06:18 AM, said:

I almost preferred the initial post (in french) as that almost avoids me to translate although my rule is to use Yii::t everywhere.


So you had time to see it :)

View Postle_top, on 30 May 2013 - 06:18 AM, said:

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.


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.
0

#5 User is offline   le_top 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 386
  • Joined: 08-June 10
  • Location:France (Ile-de-France/Val d'Oise)

Posted 30 May 2013 - 08:05 AM

View Postbennouna, on 30 May 2013 - 06:41 AM, said:

So you had time to see it :)

Actually, it is the first post that arrives in the email notification ;-).

View Postbennouna, on 30 May 2013 - 06:41 AM, said:

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.


I do not think that there is actual management of CSRF expiration itself - it has the lifetime of the session.


The user can loose the session for several reasons:
- loss of internet connection for an extended period (no internet, or hibernation of the computer);
- close/open the connection in a seperate tab;
- close/open the navigator (open several tabs again) -> fixed this issue by caching the CSRF token for 5 seconds (for given IP, browser, ...);
- backoffice 'superuser' function. For support reasons the operator logs in to the user session and forgets that he opened several accounts.
0

#6 User is offline   le_top 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 386
  • Joined: 08-June 10
  • Location:France (Ile-de-France/Val d'Oise)

Posted 30 May 2013 - 09:10 AM

I created the following utility function.
I call this function in my layout file to apply it to all controllers using the layout:


    public static function MonitorSession($timeoutSeconds=2,$jsActionCode=null) {
        $timeoutMs=$timeoutSeconds*1000;
        Yii::app()->clientScript->registerCoreScript('jquery');
        if(Yii::app()->request->enableCsrfValidation) {
            $csrfTokenName = CJavaScript::encode(Yii::app()->request->csrfTokenName);
            $csrfTokenValueRegex= CJavaScript::encode('"'.Yii::app()->request->csrfToken.'"');

            if($jsActionCode===null) {
                $expiredMessage=CJavaScript::encode(Yii::t('app','Your session expired and this page must be reloaded.'));
                $jsActionCode=<<<EOJS
 					alert($expiredMessage);
 					location.reload(true);
EOJS;
            }
            $jsMonitorScript=<<<EOJS
                    (function($) {
                        "strict";
                        function checkCSRF() {
                            if(!$.cookie)return;
                            var csrf=$.cookie($csrfTokenName);
                            if(csrf===null||!csrf.match($csrfTokenValueRegex)) {
                                window.clearInterval(timer);
                                $jsActionCode
                            }
                        }
                        window.setInterval(checkCSRF,$timeoutMs);
                    })(jQuery);
EOJS;
                Yii::app()->clientScript->registerScript(__FILE__."#MonitorSession", $jsMonitorScript,CClientScript::POS_READY);
        }
    }

0

#7 User is offline   le_top 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 386
  • Joined: 08-June 10
  • Location:France (Ile-de-France/Val d'Oise)

Posted 30 May 2013 - 10:27 AM

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.
0

#8 User is offline   le_top 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 386
  • Joined: 08-June 10
  • Location:France (Ile-de-France/Val d'Oise)

Posted 09 June 2013 - 02:44 PM

I updated my code to add checking for the user Id.
( I know that there is some code duplication here, but for the purpose of sharing that is not critical)



    public static function MonitorSession($timeoutSeconds=2,$jsActionCode=null) {
        $timeoutMs=$timeoutSeconds*1000;
        Yii::app()->clientScript->registerCoreScript('jquery');
        $md5=md5("SessionId".Yii::app()->user->getId());


        $name=md5(Yii::app()->id."idindex");
        if(!Yii::app()->request->cookies->contains('name')) {
            $cookie = new CHttpCookie($name, $md5);
            Yii::app()->request->cookies->add($name, $cookie);
        } else {
            Yii::app()->request->cookies[$name]->value=$md5;
        }

        if($jsActionCode===null) {
            $expiredMessage=CJavaScript::encode(Yii::t('app','Your session expired and this page must be reloaded.'));
            $jsActionCode=<<<EOJS
                 	alert($expiredMessage);
                 	location.reload(true);
EOJS;
        }

        # TODO: combine code.
        if(!Yii::app()->user->getIsGuest()) {
            $csrfTokenName = CJavaScript::encode(Yii::app()->request->csrfTokenName);
            $csrfTokenValueRegex= CJavaScript::encode('"'.Yii::app()->request->csrfToken.'"');

            $jsMonitorScript=<<<EOJS
                    (function($) {
                        "strict";
                        var timer;
                        function checkId() {
                            if(!$.cookie)return;
                            var id=$.cookie('$name');
                            if(id===null||!id.match('$md5')) {
                                window.clearInterval(timer);
                                $jsActionCode
                            }
                        }
                        timer=window.setInterval(checkId,$timeoutMs);
                    })(jQuery);
EOJS;
            Yii::app()->clientScript->registerScript(__FILE__."#MonitorId", $jsMonitorScript,CClientScript::POS_READY);
        }
        if(Yii::app()->request->enableCsrfValidation) {
            $csrfTokenName = CJavaScript::encode(Yii::app()->request->csrfTokenName);
            $csrfTokenValueRegex= CJavaScript::encode('"'.Yii::app()->request->csrfToken.'"');

            $jsMonitorScript=<<<EOJS
                    (function($) {
                        "strict";
                        var timer;
                        function checkCSRF() {
                            if(!$.cookie)return;
                            var csrf=$.cookie($csrfTokenName);
                            if(csrf===null||!csrf.match($csrfTokenValueRegex)) {
                                window.clearInterval(timer);
                                $jsActionCode
                            }
                        }
                        timer=window.setInterval(checkCSRF,$timeoutMs);
                    })(jQuery);
EOJS;
            Yii::app()->clientScript->registerScript(__FILE__."#MonitorSession", $jsMonitorScript,CClientScript::POS_READY);
        }
    }

0

#9 User is offline   le_top 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 386
  • Joined: 08-June 10
  • Location:France (Ile-de-France/Val d'Oise)

Posted 12 June 2013 - 06:22 PM

I made another update and wrote a wiki article with the updated code: http://www.yiiframew...he-client-side/ .
0

Share this topic:


Page 1 of 1
  • You cannot start a new topic
  • You cannot reply to this topic

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users