Forcing Login for All Pages in Yii

July 20, 2010
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.