Forcing Login for All Pages in Yii

July 20, 2010 — 69 Comments
The Yii Book If you like my writing on the Yii framework, you'll love "The Yii Book"!

Some time back, I had written a couple of blog posts on authentication and authorization in Yii. As a comment to one of those posts, someone shared some code (also posted in the Yii forums) that requires a login to access any page. The interesting thing about this code is that it’s placed in the primary application configuration file, not within individual Controllers. The benefit to this approach is that a little bit of code can add authorization to your entire site, no matter how many Controllers you have. I’ll explain how to use this approach in this post, although keep in mind that it’s really best for situations where users must be logged in to access almost all of the site’s content.The very first step uses the protected/config/main.php configuration file. That configuration file returns an array of values. Within that primary array (i.e., not within any of the subsections), you’ll want to add:

'behaviors' => array(
    'onBeginRequest' => array(
        'class' => 'application.components.RequireLogin'
    )
),

By placing this within the primary array, it applies to the application as a whole, as opposed to a specific component or module. This code associates one class with the onBeginRequest behavior. This is to say that every time a request is made, an instance of the RequireLogin class should be created.

Next, create a file called RequireLogin.php and store it in the protected/components directory. That file needs to define the RequireLogin class, which should be an extension of the Yii CBehavior class, which defines how application behaviors are used:

<?php
class RequireLogin extends CBehavior
{
}
?>

Within that class, only two methods need to be defined. The first is attach(), which will associate an event handler with the application:

public function attach($owner)
{
    $owner->attachEventHandler('onBeginRequest', array($this, 'handleBeginRequest'));
}

This method will receive the application as an argument (this code was found in the Yii forums, too). The attachEventHandler() function attaches to the application an event handler, saying that when the onBeginRequest event occurs, this class’s handleBeginRequest() method should be called.

The handleBeginRequest() method is defined like so:

public function handleBeginRequest($event)
{
    if (Yii::app()->user->isGuest && !in_array($_GET['r'],array('site/login'))) {
        Yii::app()->user->loginRequired();
    }
}

The method takes one argument, which will be the event (not actually used in the method). The purpose of the method is to determine the conditions in which a login must be required of the user. That enforcement is made by calling Yii::app()->user->loginRequired(). For this bare-bones example, the condition checks if the user is a guest, which is to say they aren’t logged in, and that $_GET['r'] does not have a value of site/login. The net effect is that guests can only ever access the login page. If you wanted to allow access to other pages, just add those values to the array:

if (Yii::app()->user->isGuest && !in_array($_GET['r'],array('site/login', 'site/index', 'site/contact'))) {

So that’s one way of implementing a broad login requirement without individually adjusting each Controller. To be clear, you’ll probably need to do that some as well, like to allow for access to specific actions based upon the type of logged-in user. Still, this is a simple and quite effective catchall. The person that shared the original code in my blog had put all this together within the configuration file. It is possible to do that (by creating an executed function) but the syntax is tricky and the code can really muddle up your configuration file. I think it’s best to separate it out, plus you now have a new class (RequireLogin) that you can use in other Yii-based sites.

If you enjoyed this post, then please consider following me using your favorite social media, the RSS feed, and/or by subscribing to my newsletter. Or go crazy, and buy one or more of my books . Thanks!

69 responses to Forcing Login for All Pages in Yii

  1. I think the class name should be RequireLogin instead of DenyAccess!

    Thank you for the nice tutorial!

  2. Great job Larry. It helps me to show content depended on user type. (sry 4 my english^^) Thanks!

  3. Thank you for your great tutorials on Yii. I’m new to Yii, and I found they are really useful!

    It seems “in_array($_GET['r'],array(‘site/login’)))” won’t work when urlManager is enabled. It happened to me, but it may due to something else that I massed around.

    • The last site I used this approach on had not used urlManager, but it should still work because the mod_rewrite is turning site/login into a $_GET variable behind the scenes (I think). Maybe I need to look into that case a bit more. Thanks for the nice words!

  4. Just some more information regarding post #5 with the latest Yii release.

    I don’t have the urlManager enabled and when I access my url ending in either the trailing slash or index.php with no parameters I get an error stating “Undefined index: r”. My php error_reporting parameter is set to E_ALL.

    If I change my url to “index.php?r=site/login” then I get the login page. I am working on a base install with no additional modules enabled. The only code changes I have done are to tie my login form to the database as described in other tutorials on this site.

    Thanks for this blog, it has been invaluable in getting started with Yii.

  5. Another followup:
    Changing this line:
    if (Yii::app()->user->isGuest && !in_array($_GET['r'],array(‘site/login’))) {

    To this:
    if (Yii::app()->user->isGuest && (!isset($_GET['r']) || $_GET['r'] != ‘site/login’)) {

    Solved the problem mentioned in posts #5 and #7 for me.

  6. Of if you have path urls the line needs to be:

    if (Yii::app()->user->isGuest && !in_array($_SERVER['REQUEST_URI'],array(‘/site/login’))) {

  7. I just tried Yii framework and have read a couple of tutorials in your site.They are all helpful. Thanks for great job!

  8. This stuff is gold! Thank you very much for sharing such a gem.

  9. Question – Do you think it is a good idea to move the access filter rules to the /protected/component/Controller.php file? I did this because I did not want to have to manage several different sets of access rules. I currently have three controller files and there will be more.

    I am using RBAC but only with roles in the access filter rules. I am also using controllers to specify which controllers as the action of each controller are the same in some cases. It looks something like this.

    array(‘allow’, // allow admin role to access these actions on the site and theater
    ‘actions’=>array(‘admin’,'delete’,'create’,'display2′),
    ‘controllers’ =>array(‘theater’,'site’),
    ‘roles’=>array(‘admin’),
    /*’expression’ => ‘Yii::app()->user->checkAccess(“admin”)’, */
    ),
    array(‘allow’, // allow cnthomda to perform these actions on the user controller
    ‘actions’=>array(‘admin’,'delete’,'create’,'update’),
    ‘controllers’=>array(‘user’),
    ‘users’=>array(‘cnthomda’),
    ),

    I think it works well. I plan to do more with RBAC later on. The reason I am bringing this up is you mentioned the /protected/ config/main.php above and the behaviours array to force login.

    Your site has been helpful and I thank you. The one problem is there are many similar examples out there and it has been confusing. Another problem I’ve been having is I am using Oracle and I found any place I access query results I have to change the key values to upper case. if there is something i can set so i don’t have to do this, please let me know.

  10. So i’m trying the setup above, but i’m getting stuck in a loop so login page won’t load. I tried with and without the urlManager. any help I would appreciate. Login works fine if i remove the required function.

    • There’s really no help I could provided given the (lack of) information provided. I would really need to see all the applicable code to help you. I’d recommend using either my forums or the Yii support forums.

  11. After running into errors when I switched to path-based URLs, I decided to write a version that would work in both modes. It also works whether the base path of the application is the root (eg. http://www.domain.com) or not (www.domain.com/site) — it may sound irrelevant, but half of the trouble was making it work in both cases.
    Two other things I added were using the login URL defined in the user CWebUser class instead of hard-coding “/site/login”, and prohibiting logged in users from acessing the login page, redirecting them to the app’s home URL instead.
    I’m going to share my version of handleBeginRequest in case it might be useful to anyone.


    $app = Yii::app();
    $user = $app->user;

    $request = trim($app->urlManager->parseUrl($app->request), '/');
    $login = trim($user->loginUrl[0], '/');

    // Restrict guests to public pages.
    $allowed = array($login);
    if ($user->isGuest && !in_array($request, $allowed))
    $user->loginRequired();

    // Prevent logged in users from viewing the login page.
    $request = substr($request, 0, strlen($login));
    if (!$user->isGuest && $request == $login)
    {
    $url = $app->createUrl($app->homeUrl[0]);
    $app->request->redirect($url);
    }

  12. This is how I have it:
    public function handleBeginRequest($event) {
    if (isset($_GET['r'])) {
    if (Yii::app()->user->isGuest && !in_array($_GET['r'],array(‘site/login’, ‘site/index’, ‘site/contact’, ‘site/captcha’))) {
    Yii::app()->user->loginRequired();
    }
    }
    }
    Two changes compared to the original post, the first is the same as Jay’s post (about undefined index ‘r’ on $_GET (I moved the test
    if (isset($_GET['r'])) {
    to the top of the function (no big deal, I just like it better that way).
    The second change is to add ‘site/captcha’ to the list of pages that do not require login. This is because when the user goes to ‘site/contact’ (which is in the no-login list), Yii will need to call ‘site/captcha’ to show the image, and in this case we do not need login either.

    Thanks for your great posts btw.

  13. if a login is required for all pages, I believe the layout(header, mainmenu, footer) of the login page should be different from the views/layouts/main.php for sure, otherwise guest could also see the links in header etc.. I don’t know how to achieve this, it seems like all the pages will have the same header and footer(from main.php). Just finish Larry’s Yii tutorial today, probably need some time to figure out this myself. Thanks for the great tutorial!

    • Thanks for the nice words. The code for changing the layout of a single page can be found in my tutorial on Views, I believe (or on Controllers, as the code actually goes in the Controller).

  14. Great stuff Larry, is there anyway to use a wildcard so you can pass variables using the get method: example- /site/login?id=5. Under the method you describe it fails, because the ‘?id=5′ isn’t in the allowed array. Tried /site/login* but it didn’t work.

    Thanks

    • Thanks for the nice words. If you’re using the syntax controller/action, then values passed in the URL would be controller/action/thing/value, so site/login/id/5/.

  15. Dear Larry and João Pedro Francese,

    thanks for your tutorial and share script, it really helpful.
    i’m really new in web programming.

    regards,
    Welly

  16. I also found the above code didn’t work for me straight away.

    Although João Pedro Francese code is probably the best, I thought I’d give my simple take on what worked for me.

    if (Yii::app()->user->isGuest && !in_array($_SERVER['PATH_INFO'], Yii::app()->user->loginUrl )) {
    Yii::app()->user->loginRequired();
    }

    Basically the $_SERVER['PATH_INFO'] looks like it only includes the query string after the file name and before any variables. In a url like test.com/index.php/site/login?this=that, $_SERVER['panth_info'] would just be /site/login

    Of course I would much rather use some kind of built in Yii request object that specifically tells me what controller / action that Yii think’s it’s about to execute, and then compare that to the designated Yii login controller / action.

    Also note that because Yii::app()->user->loginUrl is an Array, I figured Yii allows for multiple login pages and because of that we should check all of them. This is easily done with the same in_array function as the original code so why not!

    • Thanks Mark. Your suggestion worked for me. I used CHttpRequest::pathInfo instead of $_SERVER['PATH_INFO'], because (from the docs) “CHttpRequest encapsulates the $_SERVER variable and resolves its inconsistency among different Web servers.”

      class RequireLogin extends CBehavior
      {
      public function attach($owner)
      {
      $owner->attachEventHandler(‘onBeginRequest’, array($this, ‘handleBeginRequest’));
      parent::attach($owner);
      }

      public function handleBeginRequest($event)
      {
      if (Yii::app()->user->isGuest && !in_array(Yii::app()->request->pathInfo, array(‘site/login’, ‘site/index’, ‘site/contact’))) {
      Yii::app()->user->loginRequired();
      }
      }
      }

  17. HI, ,
    I have followed the steps u mentioned above. But its showing me the following error “The page isn’t redirecting properly”.

    Can you please help..

    • Not without more details. Please use my support forums or the Yii support forums.

    • It’s been a long while since this post, but if you’ve come to this thread and are encountering the same “The page isn’t redirecting properly” error, I was getting the same thing and resolved it using the code in my response to Mark (above).

  18. It’s work…..this is what i’m looking for….thanks alot

  19. I also had the error others have had with an endless redirect. The $_GET['r'] is not ever set. I wrote the if statement as:
    if (Yii::app()->user->isGuest && !strstr($_SERVER['REQUEST_URI'], “/site/login”)) {
    Yii::app()->user->loginRequired();
    }

    This always worked and should be good for all applications. The PATH_INFO suggestion would crash if you hit the root of your application without any additional path like http://www.yourstuff.com.

  20. thank you very much this works perfectly for me. I have been thinking of how to get this done

  21. If I make the contact page only available to authenticated users, then the captcha doesn’t display?

  22. Also, shouldn’t parent::attach not be called? The docs say it should: http://www.yiiframework.com/doc/api/1.1/CBehavior#attach-detail

  23. Thanks a lot Larry – This is not the first example I use from your site. Why not change your article series’ name to “Yii – the missing examples” ;-)
    If all the code you provide here were listed in the API docs, a lot of questions would be unnecessary.

  24. RequireLogin.php fails when using urlmanager to seo’ify the urls. This fixes that:

    // Restrict guests to public pages.
    $allowed = array($login,’user/recovery/recovery’,'user/activation/activation’);
    if ($user->isGuest)
    {
    $loginRequired = true;
    foreach ($allowed as $page)
    {
    if (($pos = strpos($request,$page)) !== false && $pos == 0)
    {
    $loginRequired = false;
    break;
    }
    }
    if ($loginRequired)
    {
    $user->loginRequired();
    }
    }

  25. Also of use (and probably a better way than using “$_GET['r']” to detect the path) is to utilise CHttpRequest http://www.yiiframework.com/doc/api/1.1/CHttpRequest#pathInfo-detail

    You may also have a different path for login: if so this article should help in overriding the default.
    http://www.yiiframework.com/forum/index.php/topic/10873-set-returnurl-by-overriding-loginrequired/

  26. Larry, Thanks for all the great info!!

    I wanted to point out that if you are rewriting the url to exclude “index.php” (i.e. ‘urlManager’=>array(…’showScriptName’=>false,… in config\main.php), then $_GET['r'] is undefined.

    Instead I used: “Yii::app()->request->getPathInfo()”
    So…
    if (Yii::app()->user->isGuest && ‘site/login’ !== Yii::app()->request->getPathInfo()) {…

  27. i don’t think a custom code is required for this purpose, yii is having a solution for this.
    reefer this page – http://www.heirbaut.nl/2010/02/23/forcing-a-yii-application-to-authenticate/

  28. Many thanks for this great tutorial. You save me a lot of time. Thank you.

  29. $loginRequired = true;
    foreach ($allowed as $page)
    {
    if (($pos = strpos($request,$page)) !== false && $pos == 0 || $request==”) //if request is nil, for index page
    {
    $loginRequired = false;
    break;
    }
    }

    I’d do this to avoid automatic redirect to login when accesing the site. The request is ” in case you access the site from a browser and just type: www . yiisite .com

  30. Leonard Prabangkoro January 12, 2013 at 4:12 pm

    when i try to logout, i get this : “Undefined index: r”, help me… :(

    • Leonard Prabangkoro January 12, 2013 at 4:22 pm

      nevermind… fix it… exhaustion making me miss my small flaws… thanks… i’ll ask another time, great tutorial.. :)

  31. Hello , i tried this method and its great but i got a “This webpage is not available

    Reload” or “redirect loop” when i want to redirect to any link , in my case i want to force user to be redirect to a specific action

  32. Thank you so much larry for your tutorial and posts.I think the following code will solve the url issue.The $_GET['r'] variable may not be available always.
    But I found this is working always.

    public function handleBeginRequest($event)
    {

    $app = Yii::app();
    $user = $app->user;

    $request = trim($app->urlManager->parseUrl($app->request), ‘/’);
    $login = trim($user->loginUrl[0], ‘/’);

    // Restrict guests to public pages.
    $allowed = array($login,”);
    if ($user->isGuest && !in_array($request, $allowed))
    $user->loginRequired();

    // Prevent logged in users from viewing the login page.
    $request = substr($request, 0, strlen($login));
    if (!$user->isGuest && $request == $login)
    {
    $url = $app->createUrl($app->homeUrl[0]);
    $app->request->redirect($url);
    }
    }

  33. Error :
    CHttpException

    Unable to resolve the request “site/error”. (C:\wamp\www\yiiapp\yii\framework\web\CWebApplication.php:286)

    #0 C:\wamp\www\yiiapp\yii\framework\base\CErrorHandler.php(331): CWebApplication->runController(‘site/error’)
    #1 C:\wamp\www\yiiapp\yii\framework\base\CErrorHandler.php(204): CErrorHandler->render(‘error’, Array)
    #2 C:\wamp\www\yiiapp\yii\framework\base\CErrorHandler.php(129): CErrorHandler->handleException(Object(CHttpException))
    #3 C:\wamp\www\yiiapp\yii\framework\base\CApplication.php(732): CErrorHandler->handle(Object(CExceptionEvent))
    #4 [internal function]: CApplication->handleException(Object(CHttpException))
    #5 {main}

    Help me

  34. work for me (Yii 1.1.10) many thanks for share it !!! ;)

Trackbacks and Pingbacks:

  1. Forcing Login for All Pages in Yii | Remember 4 Me - December 30, 2013

    […] Forcing Login for All Pages in Yii […]

Comments are great, but I'd strongly prefer any requests for assistance get made in the support forums. Thanks!