Home Programming CakePHP 1.3 Application Development Cookbook

CakePHP 1.3 Application Development Cookbook

books-svg-icon Book
eBook $25.99 $17.99
Print $43.99
Subscription $15.99 $10 p/m for three months
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
eBook $25.99 $17.99
Print $43.99
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
  1. Free Chapter
    Authentication
About this book
CakePHP is a rapid development framework for PHP that provides an extensible architecture for developing, maintaining, and deploying web applications. While the framework has a lot of documentation and reference guides available for beginners, developing more sophisticated and scalable applications require a deeper knowledge of CakePHP features, a challenge that proves difficult even for well established developers.The recipes in this cookbook will give you instant results and help you to develop web applications, leveraging the CakePHP features that allow you to build robust and complex applications. Following the recipes in this book you will be able to understand and use these features in no time. We start with setting up authentication on a CakePHP application. One of the most important aspects of a CakePHP application: the relationship between models, also known as model bindings. Model binding is an integral part of any application's logic and we can manipulate it to get the data we need and when we need. We will go through a series of recipes that will show us how to change the way bindings are fetched, what bindings and what information from a binding is returned, how to create new bindings, and how to build hierarchical data structures. We also define our custom find types that will extend the three basic ones, allowing our code to be even more readable and also create our own find type, with pagination support. This book also has recipes that cover two aspects of CakePHP models that are fundamental to most applications: validation, and behaviors.
Publication date:
March 2011
Publisher
Packt
Pages
360
ISBN
9781849511926

 

Chapter 1. Authentication

This chapter will cover the following topics:

  • Setting up a basic authentication system

  • Using and configuring the Auth component

  • Allowing logins with e-mail or username

  • Saving the user details after login

  • Getting the current user's information

  • Using prefixes for role-based access control

  • Setting up Access Control Layer based authentication

  • Integrating with OpenID

 

Introduction


This chapter explains how to set up authentication on a CakePHP application, starting from the most basic setup and finishing with advanced authorization mechanisms. This is accomplished through the use of tools that are built into the framework core, which allow us to quickly set up secure areas without losing flexibility to build more complex solutions.

The first two recipes show us how to set up a basic, yet fully working authentication system. The next three recipes allow our users to log in using different information, have their user details saved after a successful login, and show us how to get this user information. The sixth recipe shows a more complex authorization technique that relies on route prefixes. The seventh recipe sets up a complex authentication system through the use of CakePHP's Access Control Layer. Finally, the last recipe shows us how to integrate our application with OpenID.

 

Setting up a basic authentication system


The first task to be completed when we are in the process of adding authentication to an application is to identify which controllers will need user access. Normally we would make every controller and action protected by default, and then we would specify which areas of our application allow public access.

Getting ready

We must have a users table that should contain, at least, two fields: username (to hold the username) and password (to hold a hash made out of the user's password).

If you don't have a table for this purpose, you can use the following SQL statement to create it:

CREATE TABLE `users`(
`id` INT UNSIGNED AUTO_INCREMENT NOT NULL,
`username` VARCHAR(255) NOT NULL,
`password` CHAR(40) NOT NULL,
PRIMARY KEY(`id`)
);

How to do it...

  1. 1. Create a file named users_controller.php and place it inside your app/controllers folder with the following contents:

    <?php
    class UsersController extends AppController {
    public function login() {
    }
    public function logout() {
    $this->redirect($this->Auth->logout());
    }
    }
    ?>
    
  2. 2. Create a file named login.ctp in your app/views/users folder (create the folder if you don't have one already), and add the following contents:

    <?php
    echo $this->Form->create(array('action'=>'login'));
    echo $this->Form->inputs(array(
    'legend' => 'Login',
    'username',
    'password'
    ));
    echo $this->Form->end('Login');
    ?>
    
  3. 3. Create a file named app_controller.php in your app/ folder with the following contents:

    <?php
    class AppController extends Controller {
    public $components = array(
    'Auth' => array(
    'authorize' => 'controller'
    ),
    'Session'
    );
    public function isAuthorized() {
    return true;
    }
    }
    ?>
    
  4. 4. Modify the UsersController, and add the following code before the login method:

    public function beforeFilter() {
    parent::beforeFilter();
    $this->Auth->allow('add');
    }
    public function add() {
    if (!empty($this->data)) {
    $this->User->create();
    if ($this->User->save($this->data)) {
    $this->Session->setFlash('User created!');
    $this->redirect(array('action'=>'login'));
    } else {
    $this->Session->setFlash('Please correct the errors');
    }
    }
    }
    
  5. 5. Create a file named add.ctp and place it in your app/views/users folder with the following contents:

    <?php
    echo $this->Form->create();
    echo $this->Form->inputs(array(
    'legend' => 'Signup',
    'username',
    'password'
    ));
    echo $this->Form->end('Submit');
    ?>
    

    We now have a fully working authentication system. We can add new users by browsing to http://localhost/users/add, logging in by browsing to http://localhost/users/login, and finally logging out by browsing to http://localhost/users/logout.

    After creating a user, you should see the login form with a success message, as shown in the following screenshot:

How it works...

We start by creating two actions in the UsersController class: login(), to show and process submissions of the login form, and logout(), to handle users logging out.

You may be surprised that the login() method has no logic whatsoever. To display the form, all we need to do is display the action's view. The form submission is taken care of by the Auth component, leaving us with no need to implement any controller logic. Therefore, the only implementation we need is to create a view for this action, which includes a simple form with two fields: username, and password.

Note

The inputs method of CakePHP's FormHelper is a shortcut designed to avoid multiple calls to the input method. By using it, we can create a full form with elements without the need to call FormHelper::input() several times.

The logout() controller action simply calls the Auth component's logout() method. This method removes the logged-in user data from the session, and returns the address to which the user should be redirected after logging out, obtained from the previously configured logoutRedirect setting of the component (defaults to the application's home page if the setting was not configured.)

Next, we add two components to the controller: Session, and Auth. The Session component is needed to create the messages (through the use of its setflash() method) that informs the user if a login attempt was unsuccessful, or if a user was created.

The Auth component operates between your controller's actions and the incoming request by means of the beforeFilter callback method. It uses it's authorize setting to check what type of authentication scheme is to be used.

Note

To obtain more information about the authorize setting, see the recipe Using and configuring the Auth component.

Once the Auth component is added to a controller, all actions in that controller are not accessible unless there is a valid user logged in. This means that if we had any actions that should be public (such as the login() and add() actions in our controller), we would have to tell the Auth component about them.

If one wishes to make some actions public, one can add the name of these actions to the allowedActions setting of the Auth component, or by calling its allow() method. We use the later approach to tell the Auth component that the add() action should be reachable without a logged-in user. The login() action is automatically added to the list of public actions by the Auth component.

When the user attempts to reach an action that is not within the public actions, the Auth component checks the session to see if a user is already logged in. If a valid user is not found, it redirects the browser to the login action. If there is a user who is logged in, it uses the controller's isAuthorized method to check if the user has access. If its return value is true, it allows access, otherwise access is rejected. In our case, we implemented this method in AppController, our base controller class. If the attempted action requires a user who is logged in, the login() action is executed. After the user submits data using the login form, the component will first hash the password field, and then issue a find operation on the User model to find a valid account, using the posted username and password. If a valid record is found, it is saved to the session, marking the user as logged in.

Hashing a password confirmation field

When the Auth component is enabled on a controller and the user submits a form with a field named password (regardless if it is being rendered in the login form), the component will automatically hash the password field before executing the controller's action.

Note

The Auth component uses the salt defined in the configuration setting Security.salt (in your app/config/core.php file) to calculate the hash. Different salt values will produce different hashes even when using the same password. Therefore, make sure you change the salt on all your CakePHP applications, thus enhancing the security of your authentication system.

This means that the action will never hold the plain password value, and this should be particularly noted when utilizing mechanisms to confirm password validations. When you are implementing such validation, make sure you hash the confirmation field using the proper method:

if (!empty($this->data)) {
$this->data['User']['confirm_password'] = $this->Auth->password($this->data['User']['confirm_password']);
// Continue with processing
}

See also

  • Using and configuring the Auth component

  • Getting the current user's information

 

Using and configuring the Auth component


If there is something that defines the Auth component, it is its flexibility that accounts for different types of authentication modes, each of these modes serving different needs. In this recipe, you will learn how to modify the component's default behavior, and how to choose between the different authentications modes.

Getting ready

We should have a fully working authentication system, so follow the entire recipe Setting up a basic authentication system.

We will also add support to have disabled user accounts. Add a field named active to your users table with the following SQL statement:

ALTER TABLE `users`
ADD COLUMN `active` TINYINT UNSIGNED NOT NULL default 1;

How to do it...

  1. 1. Modify the definition of the Auth component in your AppController class, so it looks like the following:

    public $components = array(
    'Auth' => array(
    'authorize' => 'controller',
    'loginRedirect' => array(
    'admin' => false,
    'controller' => 'users',
    'action' => 'dashboard'
    ),
    'loginError' => 'Invalid account specified',
    'authError' => 'You don\'t have the right permission'
    ),
    'Session'
    );
    
  2. 2. Now while still editing your app/app_controller.php file, place the following code right below the components property declaration, at the beginning of the beforeFilter method in your AppController class:

    public function beforeFilter() {
    if ($this->Auth->getModel()->hasField('active'))
    {$this->Auth->userScope = array('active' => 1);
    }
    }
    
  3. 3. Copy the default layout from cake/libs/view/layouts/default.ctp to your app/views/layouts directory, and make sure you place the following line in your layout where you wish to display authentication messages:

    <?php echo $this->Session->flash('auth'); ?>
    
  4. 4. Edit your app/controllers/users_controller.php file and place the following method right below the logout() method:

    public function dashboard() {
    }
    
  5. 5. Finally, create the view for this newly added action in a file named dashboard.ctp and place it in your app/views/users folder with the following contents:

    <p>Welcome!</p>
    

    If you now browse to http://localhost/users/login and enter the wrong credentials (wrong username and/or password), you should see the error message shown in the following screenshot:

How it works...

As the Auth component does its magic right before a controller action is executed, we either need to specify its settings in the beforeFilter callback, or pass them in an array when adding the component to the components property. A common place to do it is in the beforeFilter() method of the AppController class, as by doing so we can share the same authentication settings throughout all our controllers.

This recipe changes some Auth settings, so that whenever a valid user logs in, they are automatically taken to a dashboard action in the UsersController (done via the loginRedirect setting.) It also adds some default error messages through the component's respective settings: loginError for when the given account is invalid, and authError for when there is a valid account, but the action is not authorized (which can be achieved by returning false from the isAuthorized() method implemented in AppController.)

It also sets the component's userScope setting in AppController::beforeFilter(). This setting allows us to define which conditions the User find operation need to match to allow a user account to log in. By adding the userScope setting, we ensure that only user records that have the active field set to 1 are allowed access.

Changing the default user model

As you may have noticed, the role of the User model is crucial, not only to fetch the right user account, but also to check the permissions on some of the authentication schemes. By default, the Auth component will look for a User model, but you can change which model is to be used by setting the userModel property or the userModel key in the settings array.

For example, if your user model is Account, you would add the following setting when adding the Auth component to your controller:

'userModel' => 'Account'

Or equivalently, you would add the following to the beforeFilter method of your AppController class, in the block of code where you are setting up the component:

$this->Auth->userModel = 'Account';

There's more...

The $authorize property of the Auth component (or the authorize key in the Auth component settings array) defines which authentication scheme should be used. Possible values are:

  • controller: It makes the component use the controller's isAuthorized method, which returns true to allow access, or false to reject it. This method is particularly useful when obtaining the logged-in user (refer to the Getting the current user's information recipe)

  • model: It is similar to controller; instead of using the controller to call the method, it looks for the isAuthorized method in the User model. First, it tries to map the controller's action to a CRUD operation (one of'create', 'read', 'update', or'delete'), and then calls the method with three arguments: the user record, the controller that is being accessed, and the CRUD operation (or actual controller action) that is to be executed.

  • object: It is similar to model; instead of using the model to call the method, it looks for the isAuthorized method in a given class. In order to specify which class, set the AuthComponent::$object property to an instance of such a class. It calls the method with three arguments: the user record, the controller that is being accessed, and the action that is to be executed.

  • actions: It uses the Acl component to check for access, which allows a much more grained access control.

  • crud: It is similar to actions; the difference lies in the fact that it first tries to map the controller's action to a CRUD operation (one of'create', 'read', 'update', or'delete'.)

See also

  • Getting the current user's information

  • Setting up Access Control Layer based authentication

 

Allowing logins with username or e-mail


By default the Auth component will use the given username posted in the login form to check for a valid user account. However, some applications have two separate fields: one to define the username, and another one to define the user's e-mail. This recipe shows how to allow logins using either a username or an e-mail.

Getting ready

We should have a fully working authentication system, so follow the entire recipe, Setting up a basic authentication system.

We also need the field to hold the user's e-mail address. Add a field named email to your users table with the following SQL statement:

ALTER TABLE `users`
ADD COLUMN `email` VARCHAR(255) NOT NULL;

We need to modify the signup page so users can specify their e-mail address. Edit your app/views/users/add.ctp file and make the following changes:

<?php
echo $this->Form->create();
echo $this->Form->inputs(array(
'legend' => 'Signup',
'email',
'username',
'password'
));
echo $this->Form->end('Submit');
?>

How to do it...

  1. 1. Edit your app/views/users/login.ctp file and make the following changes to it:

    <?php
    echo $this->Form->create(array('action'=>'login'));
    echo $this->Form->inputs(array(
    'legend' => 'Login',
    'username' => array('label'=>'Username / Email'),
    'password'
    ));
    echo $this->Form->end('Login');
    ?>
    
  2. 2. Edit your UsersController class and make sure the login action looks like the following:

    public function login() {
    if (
    !empty($this->data) &&
    !empty($this->Auth->data['User']['username']) &&
    !empty($this->Auth->data['User']['password'])
    ) {
    $user = $this->User->find('first', array(
    'conditions' => array(
    'User.email' => $this->Auth->data['User']['username'],
    'User.password' => $this->Auth->data['User']['password']
    ),
    'recursive' => -1
    ));
    if (!empty($user) && $this->Auth->login($user)) {
    if ($this->Auth->autoRedirect) {
    $this->redirect($this->Auth->redirect());
    }
    } else {
    $this->Session->setFlash($this->Auth->loginError, $this->Auth->flashElement, array(), 'auth');
    }
    }
    }
    

    If you now browse to http://localhost/users/login and you can enter the user's e-mail and password to log in, as shown in the following screenshot:

How it works...

When the Auth component is unable to find a valid user account using the username and password fields, it gives the control back to the login action. Therefore, in the login action we can check if there is any submitted data. If that is the case, we know that the Auth component was not able to find a valid account.

With this in mind, we can try to find a user account with an e-mail that matches the given username. If there is one, we log the user in and redirect the browser to the default action, similar to what the component would do on a successful attempt.

If we cannot find a valid user account, we simply set the flash message to the default error message specified in the Auth component.

There's more...

You may have noticed that when looking for the user record, we used $this->Auth->data rather than $this->data to use the actual posted values. The reason for this is because the Auth component will not only automatically hash the password field, but also remove its value from the controller's data property, so if you need to show the login form again, the password field will not be pre-filled for the user.

See also

  • Getting the current user's information

 

Saving the user details after login


One of the most typical functionalities offered by sites with authentication capabilities is the ability to let the user choose (by clicking on a checkbox) whether they want the system to remember their account after logging in.

Getting ready

We should have a working authentication system, so follow the entire recipe, Setting up a basic authentication system.

How to do it...

  1. 1. Edit your app/app_controller.php file and add the following Auth component settings to the Auth component. Also add the Cookie component by making the following changes to the components property: AppController (in the $components property) must include the following mandatory setting (if it is not there, add it inside the array of settings for the component):

    public $components = array(
    'Auth' => array(
    'authorize' => 'controller',
    'autoRedirect' => false
    ),
    'Cookie',
    'Session'
    );
    
  2. 2. Edit your app/views/users/login.ctp view file and make the following changes:

    <?php
    echo $this->Form->create(array('action'=>'login'));
    echo $this->Form->inputs(array(
    'legend' => 'Login',
    'username',
    'password',
    'remember' => array('type' => 'checkbox', 'label' => 'Remember me')
    ));
    echo $this->Form->end('Login');
    ?>
    
  3. 3. Now, add the following code to the end of the login action of your UsersController class:

    if (!empty($this->data)) {
    $userId = $this->Auth->user('id');
    if (!empty($userId)) {
    if (!empty($this->data['User']['remember'])) {
    $user = $this->User->find('first', array(
    'conditions' => array('id' => $userId),
    'recursive' => -1,
    'fields' => array('username', 'password')
    ));
    $this->Cookie->write('User', array_intersect_key(
    $user[$this->Auth->userModel],
    array('username'=>null, 'password'=>null)
    ));
    } elseif ($this->Cookie->read('User') != null) {
    $this->Cookie->delete('User');
    }
    $this->redirect($this->Auth->redirect());
    }
    }
    
  4. 4. Next, add the following code to the beginning of the logout() method of your UsersController class:

    if ($this->Cookie->read('User') != null) {
    $this->Cookie->delete('User');
    }
    
  5. 5. Finally, add the following method to your AppController class, right below the components property declaration:

    public function beforeFilter() {
    if ($this->Auth->user() == null) {
    $user = $this->Cookie->read('User');
    if (!empty($user)) {
    $user = $this->Auth->getModel()->find('first', array(
    'conditions' => array(
    $this->Auth->fields['username'] => $user[$this->Auth->fields['username']],
    $this->Auth->fields['password'] => $user[$this->Auth->fields['password']]
    ),
    'recursive' => -1
    ));
    if (!empty($user) && $this->Auth->login($user)) {
    $this->redirect($this->Auth->redirect());
    }
    }
    }
    }
    

How it works...

The first task we needed to accomplish was to disable the automatic redirect in the Auth component. By doing so, we are able to catch both successful and failed log in attempts, which allows us to check if they remember me checkbox is selected. If the checkbox is indeed checked, we create a cookie named User that contains the values for the username and password fields with a value equal to the user ID that logged in. Remember that the password value is automatically encrypted by the Auth component, so it is safe for storage. The Cookie component adds another layer of security by automatically encrypting and decrypting the given values.

In AppController::beforeFilter(), when there is no logged-in user, we check to see if the cookie is set. If it is, we use the values for the username and password fields stored in the cookie to log in a user, and then redirect the browser to the login action.

Finally, we delete the cookie when it is appropriate (when a user logs in without the checkbox selected, or when the user manually logs out).

See also

  • Getting the current user's information

 

Getting the current user's information


CakePHP's authentication system will provide us with the necessary tools to build a strong, flexible Auth based application. We can then use it to fetch the current user information and make it available throughout our application.

In this recipe, we will see how to save the current logged-in user's information so it is accessible from any point of our CakePHP application, including its layout, while adding a helpful method to the User model to make the job easier.

Getting ready

We should have a working authentication system, so follow the recipe, Setting up a basic authentication system.

How to do it...

  1. 1. Add the following method to your AppController class:

    public function beforeFilter() {
    $user = $this->Auth->user();
    if (!empty($user)) {
    Configure::write('User', $user[$this->Auth->getModel()->alias]);
    }
    }
    
  2. 2. Also in your AppController class, add the following method inside the class definition:

    public function beforeRender() {
    $user = $this->Auth->user();
    if (!empty($user)) {
    $user = $user[$this->Auth->getModel()->alias];
    }
    $this->set(compact('user'));
    }
    
  3. 3. Copy the default CakePHP layout file named default.ctp from your cake/libs/view/layouts folder to your application's app/views/layouts folder. Place the following code in the app/views/layouts/default.ctp folder. While editing this layout, add the following code right where you want login / logout links to appear:

    <?php if (!empty($user)) { ?>
    Welcome back <?php echo $user['username']; ?>!
    <?php
    echo $this->Html->link('Log out', array('plugin'=>null, 'admin'=>false, 'controller'=>'users', 'action'=>'logout'));
    } else {
    echo $this->Html->link('Log in', array('plugin'=>null, 'admin'=>false, 'controller'=>'users', 'action'=>'login'));
    }
    ?>
    
  4. 4. Add the following method to the User model. If you do not have a model created for the users table, proceed to create a file named user.php and place it in your app/models directory. If you do have one already, make sure you add the get method to it:

    <?php
    class User extends AppModel {
    public static function get($field = null) {
    $user = Configure::read('User');
    if (empty($user) || (!empty($field) && !array_key_exists($field, $user))) {
    return false;
    }
    return !empty($field) ? $user[$field] : $user;
    }
    }
    ?>
    

How it works...

By storing the user record in an application-wide configuration variable, we are able to obtain the current user information from anywhere in our application, whether it is controllers, components, models, and so on. This gives us the power to know if there's a logged-in user at any point.

We also need to make sure that views are able to learn whether there is a logged-in user. Even though a view could, technically speaking, still have access to the configure variable, it is normally more elegant to set a view variable to avoid any interaction with PHP classes from the view (except for the view helpers).

Note

When you set variables for the view in AppController, it is very important to make sure no controller action will overwrite the variable. Choose a unique name wisely, and make sure you don't set a view variable with the same name in your controllers.

Finally, we add a handy method to the User model, so we can obtain the current user from our controllers without having to deal with the Configure variable. We can also use the get method to collect a particular bit of user information. For example, to fetch the current user's username from a controller, we would do something like the following:

$userName = User::get('username');

You should not have to load the User model class yourself, as the Auth component does it for you.

See also

  • Allowing logins with e-mail or username.

 

Using prefixes for role-based access control


Even though CakePHP provides a very powerful access control layer, sometimes we just need to implement user roles without having to go into the details of specifying which role is allowed access to which action.

This recipe shows how to limit access to certain actions by role-using routing prefixes, which constitutes a perfect solution for simple role-based authentication. In order to accomplish this recipe, we will assume the need to add three user roles in our application: administrators, managers, and users.

Getting ready

We should have a working authentication system, so follow the recipe, Setting up a basic authentication system. The users table should also contain a field to hold the user's role (named role.) Add this field with the following SQL statement:

ALTER TABLE `users`
ADD COLUMN `role` VARCHAR(255) DEFAULT NULL AFTER `password`;

How to do it...

  1. 1. Edit your app/config/core.php file and look for the line that defines the Routing.prefixes setting. If it is commented out, uncomment it. Then change it to:

    Configure::write('Routing.prefixes', array('admin', 'manager'));
    
  2. 2. Add the following code at the end of your UsersController class definition:

    public function dashboard() {
    $role = $this->Auth->user('role');
    if (!empty($role)) {
    $this->redirect(array($role => true, 'action' => 'dashboard'));
    }
    }
    public function admin_dashboard() {
    }
    public function manager_dashboard() {
    }
    
  3. 3. Create a view for each of these actions, and put content into it to reflect which view is being rendered. Therefore, you would have to create three files:

    • app/views/users/admin_dashboard.ctp

    • app/views/users/manager_dashboard.ctp

    • app/views/users/dashboard.ctp

    For example, the contents for dashboard.ctp could simply be:

    <h1>Dashboard (User)</h1>
    
  4. 4. Edit your app/controllers/app_controller.php file and change the components property declaration to include the following setting for the Auth component:

    public $components = array(
    'Auth' => array(
    'authorize' => 'controller',
    'loginRedirect' => array(
    'admin' => false,
    'controller' => 'users',
    'action' => 'dashboard'
    )
    ),
    'Session'
    );
    
  5. 5. While still editing your AppController class, change the isAuthorized method and replace it entirely with the following:

    public function isAuthorized() {
    $role = $this->Auth->user('role');
    $neededRole = null;
    $prefix = !empty($this->params['prefix']) ?
    $this->params['prefix'] :
    null;
    if (
    !empty($prefix) &&
    in_array($prefix, Configure::read('Routing.prefixes'))
    ) {
    $neededRole = $prefix;
    }
    return (
    empty($neededRole) ||
    strcasecmp($role, 'admin') == 0 ||
    strcasecmp($role, $neededRole) == 0
    );
    }
    
  6. 6. Copy the default CakePHP layout file named default.ctp from your cake/libs/view/layouts folder to your application's app/views/layouts folder. While editing this layout, place the following code in the app/views/layouts/default.ctp layout file, right where you want the link to the dashboard to appear.

    <?php
    $dashboardUrl = array('controller'=>'users', 'action'=>'dashboard');
    if (!empty($user['role'])) {
    $dashboardUrl[$user['role']] = true;
    }
    echo $this->Html->link('My Dashboard', $dashboardUrl);
    ?>
    

How it works...

CakePHP will recognize prefixes defined in the Routing.prefixes setting as part of the URL, when they are preceding a normal route. For example, if admin is a defined prefix, the route /admin/articles/index will translate to the admin_index action in ArticlesController.

Since we are utilizing the controller authentication scheme in the Auth configuration, we know that every time a user is trying to access a non-public action, AppController::isAuthorized() is executed, and inside the method we set true if the user has access, or false otherwise.

Knowing that, we can check to see if a prefix is being used when a controller action is about to be executed. If the current route being accessed includes a prefix, we can match that prefix against the user's role to make sure they have access to the requested resource.

We are able to link to a role-only resource just by prefixing it with the appropriate prefix in the route. For example, to link to the manager's dashboard, the URL would be:

array(
'manager' => true,
'controller' => 'users',
'action' => 'dashboard'
);

See also

  • Setting up Access Control Layer based authentication.

 

Setting up Access Control Layer-based authentication


The more roles an application has, the more complex its Access Control Layer becomes. Luckily, one of the authentication schemes provided by the Auth component allows us to easily define which actions are accessible by certain roles (known as groups), using command-line tools. In this recipe, you will learn how to set up ACL on your application.

Getting ready

We should have a table to hold the roles, named groups.

If you do not have one already, create it using the following statement:

CREATE TABLE `groups`(
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
PRIMARY KEY(`id`)
);

If you do not have any records in your groups table, create some by running the following SQL statement:

INSERT INTO `groups`(`id`, `name`) VALUES
(1, 'Administrator'),
(2, 'Manager'),
(3, 'User');

We must also have a users table to hold the users, which should contain a field (named group_id) to contain a reference to the group a user belongs to. If you do not have such a table, create it using the following statement:

CREATE TABLE `users`(
`id` INT NOT NULL AUTO_INCREMENT,
`group_id` INT NOT NULL,
`username` VARCHAR(255) NOT NULL,
`password` CHAR(40) NOT NULL,
PRIMARY KEY(`id`),
KEY `group_id`(`group_id`),
CONSTRAINT `users__groups` FOREIGN KEY(`group_id`) REFERENCES `groups`(`id`)
);

We also need to have the ARO / ACO tables initialized. Using your operating system console, switch to your application directory, and run:

  • If you are on a GNU Linux / Mac / Unix system:

    ../cake/console/cake schema create DbAcl
    
  • If you are on Microsoft Windows:

    ..\cake\console\cake.bat schema create DbAcl
    

How to do it...

Note

The following initial steps are very similar to what is shown in Setting up a basic authentication system. However, there are some differences between the two that are crucial, so make sure you go through these instructions carefully.

  1. 1. Create a controller for the User model (in a file named users_controller.php placed inside your app/controllers folder), which should contain the following:

    <?php
    class UsersController extends AppController {
    public function login() {
    }
    public function logout() {
    $this->redirect($this->Auth->logout());
    }
    }
    ?>
    
  2. 2. Create a file named login.ctp in your app/views/users folder (create the folder if you do not have one already), with the following contents:

    <?php
    echo $this->Form->create(array('action'=>'login'));
    echo $this->Form->inputs(array(
    'legend' => 'Login',
    'username',
    'password'
    ));
    echo $this->Form->end('Login');
    ?>
    
  3. 3. Create a file named app_controller.php in your app/ folder. Make sure it contains the following:

    <?php
    class AppController extends Controller {
    public $components = array(
    'Acl',
    'Auth' => array(
    'authorize' => 'actions',
    'loginRedirect' => array(
    'admin' => false,
    'controller' => 'users',
    'action' => 'dashboard'
    )
    ),
    'Session'
    );
    }
    ?>
    
  4. 4. Modify the UsersController class and add the following code before its login() method:

    public function beforeFilter() {
    parent::beforeFilter();
    $this->Auth->allow('add');
    }
    public function add() {
    if (!empty($this->data)) {
    $this->User->create();
    if ($this->User->save($this->data)) {
    $this->Session->setFlash('User created!');
    $this->redirect(array('action'=>'login'));
    } else {
    $this->Session->setFlash('Please correct the errors');
    }
    }
    $this->set('groups', $this->User->Group->find('list'));
    }
    
  5. 5. Add the view for the action in the folder app/views/users by creating a file named add.ctp with the following contents:

    <?php
    echo $this->Form->create();
    echo $this->Form->inputs(array(
    'legend' => 'Signup',
    'username',
    'password',
    'group_id'
    ));
    echo $this->Form->end('Submit');
    ?>
    
  6. 6. Create a file named group.php and place it in your app/models folder with the following contents:

    <?php
    class Group extends AppModel {
    public $actsAs = array('Acl' => 'requester');
    public function parentNode() {
    if (empty($this->id) && empty($this->data)) {
    return null;
    }
    $data = $this->data;
    if (empty($data)) {
    $data = $this->find('first', array(
    'conditions' => array('id' => $this->id),
    'fields' => array('parent_id'),
    'recursive' => -1
    ));
    }
    if (!empty($data[$this->alias]['parent_id'])) {
    return $data[$this->alias]['parent_id'];
    }
    return null;
    }
    }
    ?>
    
  7. 7. Create a file named user.php and place it in your app/models folder with the following contents:

    <?php
    class User extends AppModel {
    public $belongsTo = array('Group');
    public $actsAs = array('Acl' => 'requester');
    public function parentNode() {
    }
    public function bindNode($object) {
    if (!empty($object[$this->alias]['group_id'])) {
    return array(
    'model' => 'Group',
    'foreign_key' => $object[$this->alias]['group_id']
    );
    }
    }
    }
    ?>
    

    Note

    Take note of the IDs for all the records in your groups table, as they are needed to link each group to an ARO record.

  8. 8. Run the following commands in your console (change the references to 1, 2, 3 to meet your own group IDs, if they are different).

    • If you are on a GNU Linux / Mac / Unix system, the commands are:

      ../cake/console/cake acl create aro root Groups
      ../cake/console/cake acl create aro Groups Group.1
      ../cake/console/cake acl create aro Groups Group.2
      ../cake/console/cake acl create aro Groups Group.3
      
    • If you are on Microsoft Windows, the commands are:

      ..\cake\console\cake.bat acl create aro root Groups
      ..\cake\console\cake.bat acl create aro Groups Group.1
      ..\cake\console\cake.bat acl create aro Groups Group.2
      ..\cake\console\cake.bat acl create aro Groups Group.3
      
  9. 9. Add the following code at the end of your UsersController class definition:

    public function dashboard() {
    $groupName = $this->User->Group->field('name',
    array('Group.id'=>$this->Auth->user('group_id'))
    );
    $this->redirect(array('action'=>strtolower($groupName)));
    }
    public function user() {
    }
    public function manager() {
    }
    public function administrator() {
    }
    
  10. 10. Create a view for each of these actions, and put some distinctive content on each one of them to reflect which view is being rendered. Therefore, you have to create three files:

    • app/views/users/user.ctp

    • app/views/users/manager.ctp

    • app/views/users/administrator.ctp.

    For example the contents for user.ctp could simply be:

    <h1>Dashboard (User)</h1>
    
  11. 11. We have to tell ACL about these restricted actions. Run the following commands in your console.

    • If you are on a GNU Linux / Mac / Unix system, the commands are:

      ../cake/console/cake acl create aco root controllers
      ../cake/console/cake acl create aco controllers Users
      ../cake/console/cake acl create aco controllers/Users logout
      ../cake/console/cake acl create aco controllers/Users user
      ../cake/console/cake acl create aco controllers/Users manager
      ../cake/console/cake acl create aco controllers/Users administrator
      
    • If you are on Microsoft Windows, the commands are:

      ..\cake\console\cake.bat acl create aco root controllers
      ..\cake\console\cake.bat acl create aco controllers Users
      ..\cake\console\cake.bat acl create aco controllers/Users logout
      ..\cake\console\cake.bat acl create aco controllers/Users user
      ..\cake\console\cake.bat acl create aco controllers/Users manager
      ..\cake\console\cake.bat acl create aco controllers/Users administrator
      
  12. 12. Finally, we have to grant permissions by linking each ARO (groups) to each ACO (controller's actions). Run the following commands in your console.

    • If you are on a GNU Linux / Mac / Unix system, the commands are:

      ../cake/console/cake acl grant Group.1 controllers/Users all
      ../cake/console/cake acl grant Group.2 controllers/Users/logout all
      ../cake/console/cake acl grant Group.2 controllers/Users/manager all
      ../cake/console/cake acl grant Group.3 controllers/Users/logout all
      ../cake/console/cake acl grant Group.3 controllers/Users/user all
      
    • If you are on Microsoft Windows, the commands are:

      ..\cake\console\cake.bat acl grant Group.1 controllers/Users all
      ..\cake\console\cake.bat acl grant Group.2 controllers/Users/logout all
      ..\cake\console\cake.bat acl grant Group.2 controllers/Users/manager all
      ..\cake\console\cake.bat acl grant Group.3 controllers/Users/logout all
      ..\cake\console\cake.bat acl grant Group.3 controllers/Users/user all
      

    We now have a fully working ACL based authentication system. We can add new users by browsing to http://localhost/users/add, logging in with http://localhost/users/login, and finally logging out with http://localhost/users/logout.

Users should only have access to http://localhost/users/user, managers to http://localhost/users/manager, and administrators should be able to access all those actions, including http://localhost/users/administrator.

How it works...

When setting the authorize configuration option of the Auth component to actions, and after adding Acl to the list of controller-wide components, CakePHP will check to see if the current action being accessed is a public action. If this is not the case, it will check for a logged-in user with a matching ACO record. If there is no such record, it will deny access.

Once there is a matching ACO for the controller action, it will use the bindNode method in the User model to see how a user record is matched to an ARO. The method implementation we added specifies that a user record should be looked up in the aros table by means of the group that the user belongs to.

After having both the matching ACO and ARO, it lastly checks to see whether there is a valid permission set up (in the aros_acos table) for the given ARO and ACO records. If it finds one, it allows access, otherwise it will reject authorization.

It is of vital importance that each record in the groups table has a matching ARO record. We set that association by issuing aro create commands to link each group ID to an ARO record of the form Group.ID, where ID is the actual ID.

Similarly, all controller actions that are not within the defined public actions should have a matching ACO record. Just as with AROs, we create the association between controller's actions and ACOs issuing aco create commands, setting the ACO name to be the action name, and making them child of an ACO which name is the controller name.

Finally, to grant the permission of an ARO (group) to an ACO (controller's actions), we issue acl grant commands, specifying as the first argument the ARO (Group.ID) and the second argument either a whole controller (such as controllers/Users), or a specific controller action (such as controllers/Users/logout). The last argument to the grant command (all) simply gives a further control of the type of access, and makes more sense when using ACL to control access to custom objects, or when using the crud authentication scheme.

There's more...

While developing an application, the task of matching each controller action to an ACO may be somewhat troublesome. Fortunately, several people in the CakePHP community felt the need for an easier solution. One of the solutions that I'd recommend is adopting acl_extras, a plugin developed by Mark Story, the lead developer of the CakePHP 1.3 release. By using this plugin, you will be able to continuously synchronize your controllers with the acos table. Find more about it, including its installation instructions, at http://github.com/markstory/acl_extras.

See also

  • Using prefixes for role-based access control.

 

Integrating with OpenID


OpenID (http://openid.net) is a great way to allow users to log in without having to have an actual username in your application. It is a solution that is widely adopted, and has proven itself on many popular sites (such as Google, Yahoo, MySpace, and AOL).

This recipe shows how to add support for OpenID logins in a transparent way, while still working with a valid Auth implementation.

Getting ready

We should have a working authentication system, so follow the recipe, Setting up a basic authentication system.

We will also need the PHP OpenID Library. Download the latest release from https://github.com/openid/php-openid/downloads and extract the folder named Auth from the downloaded file into your app/vendors folder. You should now have a directory named Auth inside your vendors folder.

Finally, we need to download the OpenID plugin for CakePHP. Go to http://github.com/mariano/openid/downloads and download the latest release. Uncompress the downloaded file into your app/plugins folder. You should now have a directory named openid inside app/plugins.

How to do it...

  1. 1. Edit your AppController class and change the reference for the Auth component from Auth to Openid.OpenAuth. The components property should now look like this:

    public $components = array(
    'Openid.OpenAuth' => array(
    'authorize' => 'controller'
    ),
    'Session'
    );
    
  2. 2. Next, edit the login view (in app/views/users/login.ctp) and add a field to allow the user to specify their OpenID URL. The view should now look like this:

    <?php
    echo $this->Form->create(array('action'=>'login'));
    echo $this->Form->inputs(array(
    'legend' => 'Login',
    'openid' => array('label' => 'OpenID URL'),
    'username',
    'password'
    ));
    echo $this->Form->end('Login');
    ?>
    

    You should now be able to log in using either a valid username and password combination, or an OpenID URL, as shown in the following screenshot:

How it works...

As the OpenAuth component (a part of the openid plugin) extends the CakePHP built-in Auth component, it works in a similar fashion. When the component cannot seem to find a way to log in the user with a username and password, it will check whether the OpenID URL is specified.

If this is the case, it will attempt to authenticate the URL against the OpenID server. When it does, the user is taken to the OpenID server so the application can be granted permission to access the OpenID credentials. When permission is given, the user is taken back to the application, at a point on which the OpenAuth component is able to mark the user as logged in, and resume the normal application work flow.

There's more...

The openid plugin has further options to customize its behavior; including the ability to specify which user information should be given back. Check the documentation in http://github.com/mariano/openid.

Being a standard Auth implementation, this integration can be combined with any of the other recipes we have seen in this chapter, which allows for a flexible open authentication solution. If you do, make sure to note that the user given back by the OpenAuth component does not contain a valid user record, so you should create one upon log in.

Even when you are using the OpenAuth component which clearly has a different name than Auth, you can still use $this->Auth to set properties or call, for example, the allow method. This is possible because the component creates an alias.

See also

  • Getting the current user's information.

CakePHP 1.3 Application Development Cookbook
Unlock this book and the full library FREE for 7 days
Start now