User authentication systems are a common part of many web applications. In CakePHP there are several systems for authenticating users, each of which provides different options. At its core the authentication component will check to see if a user has an account with a site. If they do, the component will give access to that user to the complete site.
This component can be combined with the ACL (access control lists) component to create more complex levels of access within a site. The ACL Component, for example, could allow you to grant one user access to public site areas, while granting another user access to protected administrative portions of the site.
CakePHP’s AuthComponent can be used to create such a system easily and quickly. Let’s take a look at how you would build a very simple authentication system.
Like all components, you use it by adding ‘Auth’ to the list of components in your controller:
class FooController extends AppController {
var $components = array('Auth');
Or add it to your AppController so all of your controllers will use it:
class AppController extends Controller {
// AppController's components are NOT merged with defaults,
// so session component is lost if it's not included here!
var $components = array('Auth', 'Session');
Now, there are a few conventions to think about when using AuthComponent. By default, the AuthComponent expects you to have a table called ‘users’ with fields called ‘username’ and ‘password’ to be used.
In some situations, databases don’t let you use ‘password’ as a column name. See Authentication for an example how to change the default field names to work with your own environment.
Let’s set up our users table using the following SQL:
CREATE TABLE users (
id integer auto_increment,
username char(50),
password char(40),
PRIMARY KEY (id)
);
Something to keep in mind when creating a table to store all your user authentication data is that the AuthComponent expects the password value stored in the database to be hashed instead of being stored in plaintext. Make sure that the field you will be using to store passwords is long enough to store the hash (40 characters for SHA1, for example).
If you want to add a user manually to the database, the simplest method to get the right data is to attempt to log in and look at the SQL log.
For the most basic setup, you’ll only need to create two actions in your controller:
class UsersController extends AppController {
var $name = 'Users';
var $components = array('Auth'); // Not necessary if declared in your app controller
/**
* The AuthComponent provides the needed functionality
* for login, so you can leave this function blank.
*/
function login() {
}
function logout() {
$this->redirect($this->Auth->logout());
}
}
While you can leave the login() function blank, you do need to create the login view template (saved in app/views/users/login.ctp). This is the only UsersController view template you need to create, however. The example below assumes you are already using the Form helper:
<?php
echo $this->Session->flash('auth');
echo $this->Form->create('User');
echo $this->Form->input('username');
echo $this->Form->input('password');
echo $this->Form->end('Login');
?>
This view creates a simple login form where you enter a username and password. Once you submit this form, the AuthComponent takes care of the rest for you. The session flash message will display any notices generated by the AuthComponent. Upon successful login the database record of the current logged in user is saved to session.
Believe it or not, we’re done! That’s how to implement an incredibly simple, database-driven authentication system using the Auth component. However, there is a lot more we can do. Let’s take a look at some more advanced usage of the component.
Whenever you want to alter a default option for AuthComponent, you do that by creating a beforeFilter() method for your controller, and then calling various built-in methods or setting component variables.
For example, to change the field name used for passwords from ‘password’ to ‘secretword’, you would do the following:
class UsersController extends AppController {
var $components = array('Auth');
function beforeFilter() {
$this->Auth->fields = array(
'username' => 'username',
'password' => 'secretword'
);
}
}
In this particular situation, you would also need to remember to change the field name in the view template!
Alternately, you can specify settings for Auth by placing them inside the controller’s $components property.
class AppController extends Controller {
var $components = array(
'Auth' => array(
'authorize' => 'actions',
'actionPath' => 'controllers/',
'loginAction' => array(
'controller' => 'users',
'action' => 'login',
'plugin' => false,
'admin' => false,
),
),
'Acl',
'Session',
);
}
Another common use of Auth component variables is to allow access to certain methods without the user being logged in (by default Auth restricts access to every action except the login and logout methods).
For example if we want to allow all users access to the index and view methods ( but not any other), we would do the following:
function beforeFilter() {
$this->Auth->allow('index','view');
}
In order to display the error messages that Auth spits out you need to add the following code to your view. In this case, the message will appear below the regular flash messages:
In order to show all normal flash messages and auth flash messages for all views add the following two lines to the views/layouts/default.ctp file in the body section preferable before the content_for_layout line.
<?php
echo $this->Session->flash();
echo $this->Session->flash('auth');
?>
To customize the Auth error messages, place the following code in the AppController or wherever you have placed Auth’s settings:
<?php
$this->Auth->loginError = "This message shows up when the wrong credentials are used";
$this->Auth->authError = "This error shows up with the user tries to access a part of the website that is protected.";
?>
It can sometimes be quite difficult to diagnose problems when it’s not behaving as expected, so here are a few pointers to remember.
The automatic hashing of your password input field happens only if posted data also contains username and password fields
When posting information to an action via a form, the Auth component automatically hashes the contents of your password input field if posted data also contains username field. So, if you are trying to create some sort of registration page, make sure to have the user fill out a ‘confirm password’ field so that you can compare the two. Here’s some sample code:
<?php
function register() {
if ($this->data) {
if ($this->data['User']['password'] == $this->Auth->password($this->data['User']['password_confirm'])) {
$this->User->create();
$this->User->save($this->data);
}
}
}
?>
The AuthComponent uses the Security class to hash a password. The
Security class uses the SHA1 scheme by default. To change another hash
function used by the Auth component, use the setHash
method passing
it md5
, sha1
or sha256
as its first and only parameter.
Security::setHash('md5'); // or sha1 or sha256.
The Security class uses a salt value (set in /app/config/core.php) to hash the password.
If you want to use different password hashing logic beyond md5/sha1 with
the application salt, you will need to override the standard
hashPassword mechanism - You may need to do this if for example you have
an existing database that previously used a hashing scheme without a
salt. To do this, create the method hashPasswords
in the class you
want to be responsible for hashing your passwords (usually the User
model) and set authenticate
to the object you’re authenticating
against (usually this is User) like so:
class AppController extends Controller {
var $components = array('Session', 'Auth');
function beforeFilter() {
$this->Auth->authenticate = ClassRegistry::init('User');
}
}
With the above code, the User model hashPasswords() method will be called each time Cake calls AuthComponent::hashPasswords(). Here’s an example hashPassword function, appropriate if you’ve already got a users table full of plain md5-hashed passwords:
class User extends AppModel {
function hashPasswords($data) {
if (isset($data['User']['password'])) {
$data['User']['password'] = md5($data['User']['password']);
return $data;
}
return $data;
}
}
action (string $action = ':controller/:action')
If you are using ACO’s as part of your ACL structure, you can get the path to the ACO node bound to a particular controller/action pair:
$acoNode = $this->Auth->action('users/delete');
If you don’t pass in any values, it uses the current controller / action pair
If you have some actions in your controller that you don’t have to authenticate against (such as a user registration action), you can add methods that the AuthComponent should ignore. The following example shows how to allow an action named ‘register’.
function beforeFilter() {
...
$this->Auth->allow('register');
}
If you wish to allow multiple actions to skip authentication, you supply them as parameters to the allow() method:
function beforeFilter() {
...
$this->Auth->allow('foo', 'bar', 'baz');
}
Shortcut: you may also allow all the actions in a controller by using ‘*’.
function beforeFilter() {
...
$this->Auth->allow('*');
}
If you are using requestAction in your layout or elements you should allow those actions in order to be able to open login page properly.
The auth component assumes that your actions names CakePHP Conventions and are underscored.
There may be times where you will want to remove actions from the list
of allowed actions (set using $this->Auth->allow()
). Here’s an
example:
function beforeFilter() {
$this->Auth->authorize = 'controller';
$this->Auth->allow('delete');
}
function isAuthorized() {
if ($this->Auth->user('role') != 'admin') {
$this->Auth->deny('delete');
}
...
}
hashPasswords ($data)
This method checks if the $data
contains the username and password
fields as specified by the variable $fields
indexed by the model
name as specified by $userModel
. If the $data
array contains
both the username and password, it hashes the password field in the
array and returns the data
array in the same format. This function
should be used prior to insert or update calls of the user when the
password field is affected.
$data['User']['username'] = '[email protected]';
$data['User']['password'] = 'changeme';
$hashedPasswords = $this->Auth->hashPasswords($data);
pr($hashedPasswords);
/* returns:
Array
(
[User] => Array
(
[username] => [email protected]
[password] => 8ed3b7e8ced419a679a7df93eff22fae
)
)
*/
The $hashedPasswords[‘User’][‘password’] field would now be hashed
using the password
function of the component.
If your controller uses the Auth component and posted data contains the fields as explained above, it will automatically hash the password field using this function.
If you are using Acl in CRUD mode, you may want to assign certain non-default actions to each part of CRUD.
$this->Auth->mapActions(
array(
'create' => array('someAction'),
'read' => array('someAction', 'someAction2'),
'update' => array('someAction'),
'delete' => array('someAction')
)
);
login($data = null)
If you are doing some sort of Ajax-based login, you can use this method
to manually log someone into the system. If you don’t pass any value for
$data
, it will automatically use POST data passed into the
controller.
for example, in an application you may wish to assign a user a password and auto log them in after registration. In an over simplified example:
View:
echo $this->Form->create('User',array('action'=>'register'));
echo $this->Form->input('username');
echo $this->Form->end('Register');
Controller:
function register() {
if(!empty($this->data)) {
$this->User->create();
$assigned_password = 'password';
$this->data['User']['password'] = $assigned_password;
if($this->User->save($this->data)) {
// send signup email containing password to the user
$this->Auth->login($this->data);
$this->redirect('home');
}
}
One thing to note is that you must manually redirect the user after login as loginRedirect is not called.
$this->Auth->login($data)
returns 1 on successful login, 0 on a
failure
Provides a quick way to de-authenticate someone, and redirect them to where they need to go. This method is also useful if you want to provide a ‘Log me out’ link inside a members’ area of your application.
Example:
$this->redirect($this->Auth->logout());
password (string $password)
Pass in a string, and you can get what the hashed password would look like. This is an essential functionality if you are creating a user registration screen where you have users enter their password a second time to confirm it.
if ($this->data['User']['password'] ==
$this->Auth->password($this->data['User']['password2'])) {
// Passwords match, continue processing
...
} else {
$this->flash('Typed passwords did not match', 'users/register');
}
The auth component will automatically hash the password field if the username field is also present in the submitted data
Cake appends your password string to a salt value and then hashes it.
The hashing function used depends on the one set by the core utility
class Security
(sha1 by default). You can use the
Security::setHash
function to change the hashing method. The salt
value is used from your application’s configuration defined in your
core.php
user(string $key = null)
This method provides information about the currently authenticated user. The information is taken from the session. For example:
if ($this->Auth->user('role') == 'admin') {
// Do something
}
It can also be used to return the whole user session data like so:
$data['User'] = $this->Auth->user();
If this method returns null, the user is not logged in.
In the view you can use the Session helper to retrieve the currently authenticated user’s information:
$this->Session->read('Auth.User'); // returns complete user record
$this->Session->read('Auth.User.first_name') //returns particular field value
The session key can be different depending on which model Auth is
configured to use. Eg. If you use model Account
instead of User
,
then the session key would be Auth.Account
Now, there are several Auth-related variables that you can use as well. Usually you add these settings in your Controller’s beforeFilter() method. Or, if you need to apply such settings site-wide, you would add them to App Controller’s beforeFilter()
Don’t want to use a User model to authenticate against? No problem, just change it by setting this value to the name of the model you want to use.
<?php
$this->Auth->userModel = 'Member';
?>
Overrides the default username and password fields used for authentication.
<?php
$this->Auth->fields = array('username' => 'email', 'password' => 'passwd');
?>
Use this to provide additional requirements for authentication to succeed.
<?php
$this->Auth->userScope = array('User.active' => true);
?>
You can change the default login from /users/login to be any action of your choice.
<?php
$this->Auth->loginAction = array('admin' => false, 'controller' => 'members', 'action' => 'login');
?>
The AuthComponent remembers what controller/action pair you were trying
to get to before you were asked to authenticate yourself by storing this
value in the Session, under the Auth.redirect
key. However, if this
session value is not set (if you’re coming to the login page from an
external link, for example), then the user will be redirected to the URL
specified in loginRedirect.
Example:
<?php
$this->Auth->loginRedirect = array('controller' => 'members', 'action' => 'home');
?>
You can also specify where you want the user to go after they are logged out, with the default being the login action.
<?php
$this->Auth->logoutRedirect = array(Configure::read('Routing.admin') => false, 'controller' => 'members', 'action' => 'logout');
?>
Change the default error message displayed when someone does not successfully log in.
<?php
$this->Auth->loginError = "No, you fool! That's not the right password!";
?>
Change the default error message displayed when someone attempts to access an object or action to which they do not have access.
<?php
$this->Auth->authError = "Sorry, you are lacking access.";
?>
Normally, the AuthComponent will automatically redirect you as soon as it authenticates. Sometimes you want to do some more checking before you redirect users:
<?php
function beforeFilter() {
...
$this->Auth->autoRedirect = false;
}
...
function login() {
//-- code inside this function will execute only when autoRedirect was set to false (i.e. in a beforeFilter).
if ($this->Auth->user()) {
if (!empty($this->data['User']['remember_me'])) {
$cookie = array();
$cookie['username'] = $this->data['User']['username'];
$cookie['password'] = $this->data['User']['password'];
$this->Cookie->write('Auth.User', $cookie, true, '+2 weeks');
unset($this->data['User']['remember_me']);
}
$this->redirect($this->Auth->redirect());
}
if (empty($this->data)) {
$cookie = $this->Cookie->read('Auth.User');
if (!is_null($cookie)) {
if ($this->Auth->login($cookie)) {
// Clear auth message, just in case we use it.
$this->Session->delete('Message.auth');
$this->redirect($this->Auth->redirect());
}
}
}
}
?>
The code in the login function will not execute unless you set $autoRedirect to false in a beforeFilter. The code present in the login function will only execute after authentication was attempted. This is the best place to determine whether or not a successful login occurred by the AuthComponent (should you desire to log the last successful login timestamp, etc.).
With autoRedirect set to false, you can also inject additional code such as keeping track of the last successful login timestamp
<?php
function login() {
if( !(empty($this->data)) && $this->Auth->user() ){
$this->User->id = $this->Auth->user('id');
$this->User->saveField('last_login', date('Y-m-d H:i:s') );
$this->redirect($this->Auth->redirect());
}
}
?>
Name of the session array key where the record of the current authed user is stored.
Defaults to “Auth”, so if unspecified, the record is stored in “Auth.{$userModel name}”.
<?php
$this->Auth->sessionKey = 'Authorized';
?>
If you are doing Ajax or Javascript based requests that require authenticated sessions, set this variable to the name of a view element you would like to be rendered and returned when you have an invalid or expired session.
As with any part of CakePHP, be sure to take a look at AuthComponent class for a more in-depth look at the AuthComponent.
This variable holds a reference to the object responsible for hashing passwords if it is necessary to change/override the default password hashing mechanism. See Changing the Encryption Type for more info.
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/’.
In case you want to have another layout for your Authentication error message you can define with the flashElement variable that another element will be used for display.
<?php
$this->Auth->flashElement = "message_error";
?>
In this newly defined element to ensure your authError
and
loginError
messages are displayed ensure you echo $message
.
Here’s an example:
// Code in /app/views/elements/message_error.ctp
<div class="ui-state-error">
<?php echo $message; ?>
</div>
Now authError
& loginError
messages will be displayed using
jQuery UI’s custom theme. Obviously, you can change the HTML element to
fit whatever need you have. The important thing here being that the
$message
variable was echo’d and the user will see the appropriate
information…instead of a blank div
.
Set the default allowed actions to allow if setting the component to ‘authorize’ => ‘controller’
var $components = array(
'Auth' => array(
'authorize' => 'controller',
'allowedActions' => array('index','view','display');
)
);
index, view, and display actions are now allowed by default.