Wednesday, April 14, 2010

LoginRequired and Ajax Updates

My Issue:
If my user session times out while an access restricted page is visible (say I left it open overnight), and the user then clicks on a button that performs an ajax call, the login required display appears within the div that the ajax update should happen in, rather than replacing the entire current page.

Potential Solutions:
Option 1) Extend the CWebUser and override the loginRequired() function to check for $request->isAjaxRequest (this would then need to redirect to a page which causes a javascript redirect)

Option 2) Modify the Site actionLogin to check the request type and render a different page which causes a javascript redirect.

Option 2 is the least invasive method, so I am going to go with that option and see how things work out ;)

Site Note: Could override the controller->redirect method, but I don't want to necessarily block all redirects -- I can forsee needing to have one ajax view action redirect to another ajax view action. If the controller override was used, it would have to be done on every controller - hardly efficient.


To begin, I open up the SiteController.php and modify the actionLogin method as follows:

public funciton actionLogin()
{
    $model = new LoginForm;
    if (isset($_POST['ajax'] && $_POST['ajax'] ==='login-form')
    {
      // ... standard stuff here
    } 
   
    if ( $request = Yii::app()->getRequest() && $request->isAjaxRequest)
    {
       // ensure we're rendering this in a parent window, not an update div
       $this->renderPartial('loginRedirect', array(), false, true);
       Yii::app()->end();
    }
    ... //original code continues
}

Then the new views/site/ folder - loginRedirect.php
<script>
  window.location = '<?php echo $this->createAbsoluteUrl('site/login')?>';
</script> 



EDIT:

Going back to option 1:

Extend CWebUser (Saved into /protected/components/WebUser.php)
class WebUser extends CWebUser
{
    public function loginRequired()
    {
       $app = Yii::app();
       $request = $app->getRequest();
       $this->setReturnUrl( $request->getUrl());
       if (($url = $this->loginUrl)!== null)
       {
           if (is_array($url))
           { 
             ... nothing here changes
           }
           if ( $request->isAjaxRequest )
           {
             // this is nasty -- just for test. Change this to a renderPartial w/ the redirect script.
             echo "You must be logged in to perform this action";
           } 
           else
           {
               $request->redirect($url);
           }
       }
    }
}

and in the main/config.php
   'components'=>array(
    'user'=>array(
        'class'=>'WebUser',
        ...
     ),
    ...

16 comments:

  1. Doesn't appear to be recognizing the ajax request check...

    ReplyDelete
  2. This won't work, because the CHttpRequest::redirect ( which is what the controller::redirect wraps ) does an actual header change -- losing the ajax setting on the request.

    ReplyDelete
  3. Passing this one on to the experts here:
    http://www.yiiframework.com/forum/index.php?/topic/8666-requirelogin-and-ajax-issues/

    ReplyDelete
  4. You could extend CWebUser::loginRequired() or CAccessControlFilter::accessDenied()

    You could even do some fancy javascript to check the ajax results to see if the user had logged out, and redirect the user if so

    I haven't done this myself, these are just my initial thoughts

    ReplyDelete
  5. Going with Y!!'s recommendation that extending the CWebUser was the easiest way to go, I went ahead and did that. The revised code is edited in now.

    ReplyDelete
  6. Heya,

    This is working well for most Ajax activities except pagination in CGridView.. Any ideas why that would be?

    ReplyDelete
  7. Actually any ajax activity in CGridView is not working with this solution for me. I just get a blank div where the table used to be..

    ReplyDelete
  8. You're talking about the view not working any time, not just when the session times out, correct?

    If so, does your data provider include a 'having' condition? I've found that to break the CGridView in *every* install (with or without) this modification. I haven't tested it in 1.1.3 though.

    ReplyDelete
  9. nope works fine until the session times out..

    ReplyDelete
  10. if the session times out and you try to filter/sort or click on any pagination link, all that happens is the div where the grid used to be is blanked out. Give it a go yourself, try having a grid with or without pagination, open a new browser window and logout then go back to the window with the grid and do any of the above activities, you'll see what i mean :)

    ReplyDelete
  11. I will definitely try to duplicate that! Someone I was talking to a month or so ago ran into a similar situation, but it was to do with a custom Controller that was bypassing some of the session checking code:

    http://www.yiiframework.com/forum/index.php?/topic/10045-solved-active-session-check/

    Does any of that look familiar? I will definitely try to duplicate the problem myself in the meantime!

    ReplyDelete
  12. Hey Dana,

    Yup I checked out that post but I'm not really running any custom controllers, I am running an IP Blocker check but it shouldn't interfere with anything.. Let me know how you go with your replicating..

    I'm using Yii 1.1.3, it seems they changed a whole bunch of Jquery to use the .live() method but in my experience the .live() method causes more trouble then it solves..

    ReplyDelete
  13. Hi: I have a question, in the method loginRequired of the class WebUser
    the function $this->renderPartial don't work
    any idea?

    ReplyDelete
  14. I actually had it echo out the script redirect directly "<script>window.location=(insert url here);</script>" and then Yii::app()->end(); Simply because I haven't had a chance to dive back into it and make it more elegant.

    ReplyDelete
  15. Hi,
    Could it be possible that
    public $autoRenewCookie=false;
    from UserIdentity in config could cause the cookie to expire even if requests are passed regularily?

    ReplyDelete
  16. if ( $request->isAjaxRequest )
    {
    // this is nasty -- just for test. Change this to a renderPartial w/ the redirect script.
    echo "You must be logged in to perform this action";
    }

    Not working in my app

    ReplyDelete