How to use AuthComponent in CakePHP

Posted by Jad on September 24, 2007

Wherever I went - irc, trac, google groups and certain blogs - I noticed people complaining about either not knowing how to use the AuthComponent or having problems with it.

After reading the only tutorials available across all usual blogs I rely on for all my CakePHP needs, I realized their was a gap I could fill. The reasons are:

  1. Lack of a CPC way for quick and easy implementation.
  2. No real explanation or solution for using in a real application environment (multiple controllers, beforeFiters, actions, etc.).
  3. Not covering the different authorization methods it’s capable of: default, controller, actions, crud, model and object

I know I can’t cover everything at once, I just don’t have the time for it, but I will start with the first two and probably cover the third part in a serie of posts.

CPC way for easy implementation

This is to make it fairly simple for anyone to see, understand and configure the different constants included in AuthComponent. All the descriptions were taken directly from the component’s source code to have everything in one place when setting it up.

class CustomController extends AppController
{
   var $components = array('Auth');

   function __setAuth()
   {
      if (isset($this->Auth))
      {
         /**
          * The name of an optional view element to render when an Ajax request is made
          * with an invalid or expired session
          *
          * @var string
          */
         $this->Auth->ajaxLogin = null;

         /**
          * The name of the model that represents users which will be authenticated.  Defaults to 'User'.
          *
          * @var string
          */
         $this->Auth->userModel = 'User';
         /**
          * Additional query conditions to use when looking up and authenticating users,
          * i.e. array('User.is_active' => 1).
          *
          * @var array
          */
         $this->Auth->userScope = array();

         /**
          * Allows you to specify non-default login name and password fields used in
          * $userModel, i.e. array('username' => 'login_name', 'password' => 'passwd').
          *
          * @var array
          */
         $this->Auth->fields = array('username' => 'username', 'password' => 'passwd');

         /**
          * the hash function to use, options: sha1, sha256, md5
          *
          * @var string
          */
         $this->Auth->hash = 'sha1';
         /**
          * The session key name where the record of the current user is stored.  If
          * unspecified, it will be "Auth.{$userModel name}".
          *
          * @var string
          */
         $this->Auth->sessionKey = null;

         /**
          * If using action-based access control, this defines how the paths to action
          * ACO nodes is computed.  If, for example, all controller nodes are nested
          * under an ACO node named 'Controllers', $actionPath should be set to
          * "Controllers/".
          *
          * @var string
          */
         $this->Auth->actionPath = null;

         /**
          * A URL (defined as a string or array) to the controller action that handles
          * logins.
          *
          * @var mixed
          */
         $this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');

         /**
          * Normally, if a user is redirected to the $loginAction page, the location they
          * were redirected from will be stored in the session so that they can be
          * redirected back after a successful login.  If this session value is not
          * set, the user will be redirected to the page specified in $loginRedirect.
          *
          * @var mixed
          */
         $this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'dashboard');

         /**
          * The the default action to redirect to after the user is logged out.  While AuthComponent does
          * not handle post-logout redirection, a redirect URL will be returned from AuthComponent::logout().
          * Defaults to AuthComponent::$loginAction.
          *
          * @var mixed
          */
         $this->Auth->logoutRedirect = null;

         /**
          * The name of model or model object, or any other object has an isAuthorized method.
          *
          * @var string
          */
         $this->Auth->object = null;

         /**
          * Error to display when user login fails.  For security purposes, only one error is used for all
          * login failures, so as not to expose information on why the login failed.
          *
          * @var string
          */
         $this->Auth->loginError = 'Login failed.  Invalid username or password.';

         /**
          * Error to display when user attempts to access an object or action to which they do not have
          * acccess.
          *
          * @var string
          */
         $this->Auth->authError = 'You are not authorized to access that location.';

         /**
          * Determines whether AuthComponent will automatically redirect and exit if login is successful.
          *
          * @var boolean
          */
         $this->Auth->autoRedirect = true;

         /**
          * Controller actions for which user validation is not required.
          *
          * @var array
          */
         $this->Auth->allowedActions = array();

      }
   }

   function beforeFilter()
   {
      $this->__setAuth();
   }
}

Simple implementation for real application usage

Here are the things I believed it should cover to best serve all needs in the application:

1. By default, all pages should have all actions allowed. The implementation inside app_controller should be transparent, meaning that if I don’t want AuthComponent in a CustomController, I don’t have to do anything to disable it.

2. Avoid using the CustomController->beforeFilter() callback. Enabling the AuthComponent should be as easy as setting $components, $pageTitle, etc. inside the controller.

3. Simplify big controllers’ actions management. Checking for authorization on just a couple actions in a big controller where everything else shouldn’t be checked currently requires that you list all the actions you want to allow, what about listing only the ones you want to deny? Imagine a controller with 25 actions where only 4 have to be authorized, right now you’d need to set the 21 actions you don’t want to authorize.

4. Enable wildcard (*), single action (string) or a list of actions (array). To follow with the main CakePHP way of doing things, the Auth constants used to enable/disable the component should accept both string and array values.

5. Never have wildcard (*) be the value for Auth->allowedActions. Right now, Auth->deny(’actionName’) used with Auth->allow() (which makes CakePHP sets the Auth->allowedActions to ‘*’) or without it at all, is useless. Now, since in number 3 I say denying only a couple actions in a big controller are made easier, the only reason for this to still be a requirement is for the exceptional cases when you need to further customize the way AuthComponent is set in your CustomController’s beforeFilter() callback with just a couple of lines.

The code

You will first need to include Auth to the components your AppController calls for.

/app/app_controller.php

var $components = array('Auth');

Then add those 2 constants to the same file.

/**
 * Methods where public is allowed access
 *
 * @access  public
 * @var     array
 */
var $authAllow = array();

/**
 * Methods where public is denied access
 *
 * @access  public
 * @var     array
 */
var $authDeny = array();

authAllow: Optional, mixed.
Use that if you want to add, on the CustomController level, to the default Auth->allowedActions you have set.

authDeny: Optional, mixed.
Use that for all the actions you wish to check for authorization before proceeding.

Now the real coding part. Again in the same file:

function beforeFilter()
{
   $this->__setAuth();
   //any of the Auth constants you wish to overwrite
   //on a Controller level
   if($this->__sessionAuth())
   {
      $this->__initAuth();
   }
}

function beforeRender()
{
   $this->__sessionAuth();
}

function __sessionAuth()
{
   if ($this->Session->check('Message'))
   {

      $auth_session = $this->Session->read('Message');
      if (array_key_exists('auth', $auth_session) && array_key_exists('message', $auth_session['auth']))
      {
         //invalidating login form
         $auth_flash = $auth_session['auth']['message'];
         $userModel =& $this->Auth->getModel();
         $userModel->invalidate($this->Auth->fields['username'], $auth_flash);
         unset($auth_session['auth']);
         $this->Session->write('Message', $auth_session);
         return false;
      }
   }
   return true;
}

function __setAuth()
{
   if (isset($this->Auth))
   {
      /**
       * The name of the component to use for Authorization or set this to
       * 'controller' will validate Controller::action against Controller::isAuthorized(user, controller, action)
       * 'actions' will validate Controller::action against an AclComponent::check()
       * 'crud' will validate mapActions against an AclComponent::check()
       * array('model'=> 'name'); will validate mapActions against model $name::isAuthorize(user, controller, mapAction)
       * 'object' will validate Controller::action against object::isAuthorized(user, controller, action)
       *
       * @var string
       * @access public
       */
       $this->Auth->authorize = false;

      /**
       * The name of an optional view element to render when an Ajax request is made
       * with an invalid or expired session
       *
       * @var string
       */
      $this->Auth->ajaxLogin = null;

      /**
       * The name of the model that represents users which will be authenticated.  Defaults to 'User'.
       *
       * @var string
       */
      $this->Auth->userModel = 'User';
      /**
       * Additional query conditions to use when looking up and authenticating users,
       * i.e. array('User.is_active' => 1).
       *
       * @var array
       */
      $this->Auth->userScope = array();

      /**
       * Allows you to specify non-default login name and password fields used in
       * $userModel, i.e. array('username' => 'login_name', 'password' => 'passwd').
       *
       * @var array
       */
      $this->Auth->fields = array('username' => 'username', 'password' => 'passwd');

      /**
       * the hash function to use, options: sha1, sha256, md5
       *
       * @var string
       */
      $this->Auth->hash = 'sha1';
      /**
       * The session key name where the record of the current user is stored.  If
       * unspecified, it will be "Auth.{$userModel name}".
       *
       * @var string
       */
      $this->Auth->sessionKey = null;

      /**
       * If using action-based access control, this defines how the paths to action
       * ACO nodes is computed.  If, for example, all controller nodes are nested
       * under an ACO node named 'Controllers', $actionPath should be set to
       * "Controllers/".
       *
       * @var string
       */
      $this->Auth->actionPath = null;

      /**
       * A URL (defined as a string or array) to the controller action that handles
       * logins.
       *
       * @var mixed
       */
      $this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');

      /**
       * Normally, if a user is redirected to the $loginAction page, the location they
       * were redirected from will be stored in the session so that they can be
       * redirected back after a successful login.  If this session value is not
       * set, the user will be redirected to the page specified in $loginRedirect.
       *
       * @var mixed
       */
      $this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'index');

      /**
       * The the default action to redirect to after the user is logged out.  While AuthComponent does
       * not handle post-logout redirection, a redirect URL will be returned from AuthComponent::logout().
       * Defaults to AuthComponent::$loginAction.
       *
       * @var mixed
       */
      $this->Auth->logoutRedirect = null;

      /**
       * The name of model or model object, or any other object has an isAuthorized method.
       *
       * @var string
       */
      $this->Auth->object = null;

      /**
       * Error to display when user login fails.  For security purposes, only one error is used for all
       * login failures, so as not to expose information on why the login failed.
       *
       * @var string
       */
      $this->Auth->loginError = 'Login failed.  Invalid username or password.';

      /**
       * Error to display when user attempts to access an object or action to which they do not have
       * acccess.
       *
       * @var string
       */
      $this->Auth->authError = 'You are not authorized to access that location.';

      /**
       * Determines whether AuthComponent will automatically redirect and exit if login is successful.
       *
       * @var boolean
       */
      $this->Auth->autoRedirect = true;

      /**
       * Controller actions for which user validation is not required.
       *
       * @var array
       */
      $this->Auth->allowedActions = array();

   }
}

function __initAuth()
{
   if (isset($this->Auth))
   {
      $ignored_methods = array();

      if ($this->authAllow == '*' || empty($this->authAllow))
      {
         $this->authAllow = am(array_keys($this->Auth->actionMap),
                                 get_class_methods($this->__controllerName($this->name)));
         $ignored_methods = am($ignored_methods,
                                 get_class_methods($this->__controllerName('App')),
                                 get_class_methods($this->__controllerName('')));
      }

      if (is_string($this->authAllow))
      {
         $this->authAllow = array($this->authAllow);
      }
      if (is_string($this->authDeny))
      {
         $this->authDeny = array($this->authDeny);
      }

      if (!empty($this->authDeny))
      {
         if (in_array('*', $this->authDeny))
         {
            $this->authDeny = am(array_keys($this->Auth->actionMap),
                                    get_class_methods($this->__controllerName($this->name)));
         }
         $ignored_methods = am($ignored_methods, $this->authDeny);
      }

      if (!empty($this->authAllow))
      {
         foreach($this->authAllow as $method_name)
         {
            if (!in_array($method_name, $ignored_methods))
            {
               $this->Auth->allow($method_name);
            }
         }
      }
   }
}

function __controllerName($name)
{
   return $name . 'Controller';
}

Examples

In our examples I will use CustomController with the following actions: customAction - anotherCustomAction - finalCustomAction. All the examples below are done by editing the /controllers/custom_controller.php file.

1. By default, allow access to all actions.

//do nothing special in your controller

//or you could use the wildcard
var $authAllow = '*';

//or even
var $authAllow = array('customAction', 'anotherCustomAction', 'finalCustomAction');

2. Make only ‘anotherCustomAction’ accessible to everyone

var $authAllow = 'anotherCustomAction';

//or, just for the sake of showing the different possible ways
var $authDeny = array('customAction', 'finalCustomAction');

3. You want to check authorization only for ‘finalCustomAction’, ‘create’, ‘delete’:

//or, again just for showcasing the different methods
var $authDeny = array('finalCustomAction', 'create', 'delete');

4. You need to create a new beforeFilter() callback in CustomController:

//set authDeny or authAllow just like you would any of the above cases
var $authAllow = 'customAction';

function beforeFilter()
{
   $this->__setAuth();
   //any changes you wish to bring to the default settings you made in AppController
   //should come here.
   //ie: if you want to use a different Model for authorization
   //or if you want to set an Auth->userScope for this specific controller
   $this->__initAuth();
}

Finally, to show the authorization’ error messages (if any) in your login view:

$session->flash('auth');

Notes

  1. Auth will just exit without doing anything if the referenced controller’s name is ‘TestsController’.
  2. Auth considers ‘login’ and ‘logout’ to be allowed methods, including them in Auth->allow() can make problems.
  3. All files should have no whitespace before the opening () tag and none after the closing (?>) tag. That will cause an error like: Warning (2): session_start() [function.session-start]: Cannot send session cache limiter - headers already sent (output started at /path/to/app/controllers/custom_controller.php:2) [CORE/cake/libs/session.php, line 154]

That’s it for now. Next in the AuthComponent serie, I will cover the ‘controller’, ‘model’ and ‘object’ Auth->authorize options.

Trackbacks

Use this link to trackback from your own site.

Comments

Leave a response

  1. ajmacaro Mon, 24 Sep 2007 09:18:57 EDT

    i want to see this in action. :) now where are those
    controllers options… im excited.

  2. ACL Mon, 24 Sep 2007 15:28:21 EDT

    Auth->authorize=0; will disable acl checking or else you get error viewing pages.

  3. Jad Mon, 24 Sep 2007 16:23:34 EDT

    I left ACL to default and all worked fine.

  4. Chris Hartjes Wed, 26 Sep 2007 05:40:38 EDT

    Hey Jad, thanks for the nice plug.

    Yeah, my blog posting is more a “here’s how the options in Auth work” rather than a “here’s how to actually use Auth in a real application” posting. You’ve put together a nice practical tutorial here.

  5. Jad Wed, 26 Sep 2007 07:17:40 EDT

    @Chris: thanks for dropping by. I can’t blame you for only scratching the surface with your post, after all you must be really busy developing the amazing framework we get to use, so props to that!

    Look out for the next one where I will be uncovering all I discovered while working with the Auth and ACL components (different types of authorizations available, userScope, etc.) - not that you really need tutorials, but more because I care to hear your feedback.

  6. Chris Hartjes Wed, 26 Sep 2007 16:29:33 EDT

    @Jad

    Hey, I still need tutorials from time to time…despite my involvement with CakePHP there are still areas that I am not 100% familiar with. I’m definitely a “learn by seeing a tutorial” type of person.

  7. Jad Wed, 26 Sep 2007 17:16:14 EDT

    @Chris: I hear ya - am that kind too, tutorials and source digging. I tend to compare coding to driving, you can learn the basics and go with that or you can know your shit inside out and optimize it to suit you best - for a relatively big difference in performance and elegance.

  8. links for 2007-09-27 « Richard@Home Thu, 27 Sep 2007 08:17:54 EDT

    […] Loud Baking » Blog Archive » How to use AuthComponent in CakePHP A practical demonstration of the new Auth component for CakePHP 1.2 (tags: cakephp auth tutorial) […]

  9. Marc Wed, 17 Oct 2007 04:03:16 EDT

    I want tutorials to get a nice kick-start. Learning how others do things is a great way to check the quality of your own work.

  10. Jad Wed, 17 Oct 2007 18:27:03 EDT

    @Marc: I wanted to post it here but gwoo preferred I post on the bakery. I’ve been procrastinating now for a while, it’s all written, revised by him and everything, it’s only about posting it now; I should kick myself this week and finish with that.
    And I completely agree with you on the fact that checking what others are doing is a great way to learn/improve the way we do things ourself and sometimes, realize how good our solution is compared to other, either way it’s a plus.
    On a last note, thanks for dropping by!

    Jad

  11. Spout Mon, 29 Oct 2007 22:36:53 EDT

    Thanks, this works successfully!
    I see in auth.php that $hash is no present in the class, so I commented it.

  12. zsw Thu, 29 Nov 2007 07:15:50 EST

    replace
    $this->Auth->hash = ’sha1′;
    by
    Security::setHash(’sha1′);

  13. Jon Gales Tue, 29 Jan 2008 00:49:07 EST

    This is exactly what I was looking for (having to set all the allowed actions seemed really tedious for a mostly public site). I did run into one small kink and was able to fix it.

    Using the code supplied here my login form broke–it didn’t show error messages and the returning form came back auto-populating the hashed password field. Not cool. Making my login action an ignored method fixed this.

    Replace:

    $ignored_methods = array();

    With:

    $ignored_methods = array(’login’);

    The form started working again and all is well.

  14. GogoKodo Sat, 19 Apr 2008 20:20:52 EDT

    I’m having trouble getting this to work 100%.
    The log in seems to work, it validates properly and redirects me to the page I specified (non-protected page). But then if I try to follow any links to protected pages I get a redirect loop (infinite redirect).

  15. gerroy Wed, 11 Jun 2008 09:15:33 EDT

    I write as you:
    Security::setHash(”md5″);
    but I got the following error:
    Invalid Login.
    this means the password was wrong,
    can you tell me the reason and contact me with email
    my email:mnbghj929.student@sina.com
    Thanks!
    my cakephp version:1.2…rc
    I come from china.Welcome you !

  16. Gonzalo Wed, 16 Jul 2008 02:04:29 EDT

    I’m new to cakephp and I’m trying to use the Auth component. Which is working fine at my project but I’m stucked with (I think) the most obvious thing of the world.

    I want to show a diferent element (or a variation of the same element) depending if the user is logged or not. The only way I found to do it is by checking it in every function and setting a variable that I check on the view.

    Is there any simpler way to do it ?
    Thanks in advance.

Comments