Book Image

CakePHP 2 Application Cookbook

By : James Watts, Cake Software Foundation, Inc.
Book Image

CakePHP 2 Application Cookbook

By: James Watts, Cake Software Foundation, Inc.

Overview of this book

Table of Contents (20 chapters)
CakePHP 2 Application Cookbook
Credits
Foreword
About the Authors
About the Reviewer
www.PacktPub.com
Preface
Index

Adding a login


It's not going to be long before you need to control access to certain areas of your application.

In this recipe, we'll look at adding a basic authentication layer to our existing products section, with a login view to enter your credentials.

Getting ready

For this recipe, we'll need a table for our users. Create a table named users, using the following SQL statement:

CREATE TABLE users (
  id VARCHAR(36) NOT NULL,
  username VARCHAR(20),
  password VARCHAR(100),
  created DATETIME,
  modified DATETIME,
  PRIMARY KEY(id)
);

We'll then create a User.php file in app/Model/, which will have the following content:

<?php
App::uses('AppModel', 'Model');
App::uses('SimplePasswordHasher', 'Controller/Component/Auth');
    
class User extends AppModel {
}

We'll also need a UsersController.php file in app/Controller/ with the following content:

<?php
App::uses('AppController', 'Controller');
    
class UsersController extends AppController {
}

Finally, also create a Users directory in app/View/, and create register.ctp and login.ctp files in the new directory.

How to do it...

Perform the following steps:

  1. Add the validation rules to the User model in app/Model/User.php with the following $validate property:

    public $validate = array(
      'username' => array(
        'required' => array(
          'rule' => 'notEmpty',
          'message' => 'Please enter a username'
        )
      ),
      'password' => array(
        'required' => array(
          'rule' => 'notEmpty',
          'message' => 'Please enter a password'
        )
      )
    );
  2. In the same class, add this beforeSave() method:

    public function beforeSave($options = array()) {
      if (!parent::beforeSave($options)) {
        return false;
      }
      if (isset($this->data[$this->alias]['password'])) {
        $hasher = new SimplePasswordHasher();
        $this->data[$this->alias]['password'] = $hasher->hash($this->data[$this->alias]['password']);
      }
      return true;
    }
  3. Locate the AppController.php file in app/Controller/, and add the following $components property to the same class:

    public $components = array(
      'Session',
      'Auth' => array(
        'loginRedirect' => array('controller' => 'products'),
        'logoutRedirect' => array(
          'controller' => 'users',
          'action' => 'login'
        )
      )
    );
  4. Open the UsersController.php file and add the following methods:

    public function beforeFilter() {
      parent::beforeFilter();
      $this->Auth->allow();
    }
    
    public function register() {
      if ($this->request->is('post')) {
        $this->User->create();
        if ($this->User->save($this->request->data)) {
          $this->Session->setFlash(__('New user registered'));
          return $this->redirect(array('action' => 'login'));
        }
        $this->Session->setFlash(__('Could not register user'));
      }
    }
    
    public function login() {
      if ($this->request->is('post')) {
        if ($this->Auth->login()) {
          return $this->redirect($this->Auth->redirectUrl());
        }
        $this->Session->setFlash(__('Incorrect username or password'));
      }
    }
    
    public function logout() {
      return $this->redirect($this->Auth->logout());
    }
  5. Locate the register.ctp file in app/View/Users/ and introduce the following content:

    <h2><?php echo __('Register'); ?></h2>
    <?php
    echo $this->Form->create('User');
    echo $this->Form->inputs();
    echo $this->Form->end(__('Register'));
  6. In the same directory, open the login.ctp file, and add the following content:

    <h2><?php echo __('Login'); ?></h2>
    <?php
    echo $this->Session->flash('auth');
    echo $this->Form->create('User');
    echo $this->Form->inputs(array(
      'username',
      'password',
      'legend' => __('Login, please')
    ));
    echo $this->Form->end(__('Sign In'));

How it works...

For this recipe, we first included a $validate property in our User model. This array is used to define the validation rules applied when creating or modifying records. Here, we simply defined the username and password fields as required, not allowing an empty value and specifying some custom messages to be returned by the model if the fields fail to validate correctly. There are plenty more validation rules such as alphaNumeric, minLength, between, date, and email. You can also create your own rules for custom validation.

After setting up our validation, we also added a beforeSave() method. This is one of the callback methods available on all models, to hook into a certain point of the process. These are beforeFind(), afterFind(), beforeSave(), afterSave(), beforeDelete(), afterDelete(), beforeValidate(), afterValidate(), and onError(). In our method, we added some logic to process the password and generated a hash value before saving it to our users table. This way, we store a representation of the password, instead of the password itself. This is very important as you don't want anyone viewing the actual passwords in your database.

The SimplePasswordHasher class helps us here by providing an easy API to quickly generate hashes of any value. You will also notice our use of $this->alias. This is the recommended method of referring to the model, to allow for extensions or aliasing of a model without impacting the internal logic.

We then added the Auth component to the $components array of AppController, with some global settings. This controller acts as a base controller for your application, so behavior or functionality that needs to be propagated to your entire application should be added in this class. Here, we defined the loginRedirect and logoutRedirect settings, which define the controller and action to redirect to in each case (after login or after logout). Where the action is not defined, index will be assumed by default.

After that, we proceeded to add some methods to our UsersController. The first of these is the beforeFilter() method. This is one of the callback methods available on all controllers, which include beforeFilter(), afterFilter(), beforeRender(), and beforeRedirect(). Here, we first called parent::beforeFilter() to make sure that we included any logic that has been defined by AppController. We then called the allow() method on the Auth component to allow access to the methods in the controller. Here, you could also pass some method names to only allow certain action, or also use the deny() method to explicitly deny some actions and require login.

After that, we added a register() method, using the same logic to create a record as we did in the ProductsController from a previous recipe, in order to allow the creation of new users. Here, we've only used a simple example, without contemplating any postregistration checks, such as a token via e-mail for confirmation. You can easily include this and many more features using a plugin, such as the CakeDC Users plugin, found at https://github.com/CakeDC/users.

We also included both the login() and logout() methods, which use the API exposed by the Auth component to process the user login. As we're following convention by creating a users table with the username and password fields, we take advantage of the framework's ability to configure most of the sign-in process for us. In our login action, we first called the login() method on the Auth component, which internally checks the data passed in the request. If this is successful, we redirect the user using the redirect() method from the Auth component, which takes the target we previously defined in the loginRedirect setting. If the process were to fail, we stay in the same action but use the setFlash() method from the Session component to display a message to the user that the sign in was unsuccessful. The logout() method is even easier, simply calling the logout() method on the Auth component and redirecting the user to that location. Here, as with the login() method, we use the logoutRedirect setting we had previously defined. As you can see so far, authentication in CakePHP is really a piece of cake.

We then went on to create our views to register a new user and sign in. In our first view, register.ctp, we used the create() method of the Form helper to create a form for the User model. We then used the inputs() methods to render the required inputs and finally called end(), passing our text for the submit button. In our login.ctp view, we did almost the same; except here, we also enlisted required inputs, added custom form legend, and called the flash() method on the Session helper, passing auth as the argument to it. This renders a location to collect the flash messages sent from the Auth component, such as the message we display when the login fails. The use of the auth value allows you to easily manage the messages being collected, in case you'd like them to be displayed differently or in different locations of your view.

If you go to /products in your browser now, you will be automatically redirected to the login page, and the default message for unauthorized access will be displayed, as shown in the following screenshot:

Of course, to log in, you'll have to register a new user account first by going to the /users/register URL in your browser.

See also