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:
- Add the validation rules to the
User
model inapp/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' ) ) );
- 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; }
- Locate the
AppController.php
file inapp/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' ) ) );
- 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()); }
- Locate the
register.ctp
file inapp/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'));
- 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
- Read more on data validation in CakePHP at http://book.cakephp.org/2.0/en/models/data-validation.html
- More details on callback methods in Models can be found at http://book.cakephp.org/2.0/en/models/callback-methods.html
- More settings available for the Auth component can be found at http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html
- The Authentication API recipe in Chapter 4, API Strategies
- Chapter 5, Using Authentication