CakePHP3: User Authentication

Create users controller to manage, you guess it! users. Login, logout, signup, forgot password, change password, change email, update profile etc

add this line

namespace App\Model\Entity;

use Cake\ORM\Entity;
use Cake\Auth\DefaultPasswordHasher;

and add this method to the class

protected function _setPassword($value)
{
    $hasher = new DefaultPasswordHasher();
    return $hasher->hash($value);
}

Add some groups:

http://example.com/groups/add
  1. Admin
  2. Manager
  3. User

Now add some users, one for each groups

http://example.com/users/add

load the auth component, checking username and password field.

put it on initialize method

$this->loadComponent('Auth', [
        		'authenticate' => [
        				'Form' => [
        						'fields' => [
        								'username' => 'email',
        								'password' => 'password'
        						]
        				]
        		],
        		'loginAction' => [
        				'controller' => 'Users',
        				'action' => 'login'
        		],
        		'unauthorizedRedirect' => $this->referer() // If unauthorized, return them to page they were just on
        ]);

All page now can’t be accessed unless you are logged in. Add this below the code above so we can still visit homepage and other /pages/*

$this->Auth->allow(['display']);

I want users to be logged in for 30 days unless they logout

Update /config/app.php from

'Session' => [
        'defaults' => 'php',
    ],

to

'Session' => [
        'defaults' => 'php',
		'timeout' => 60*60*24*30,
		'ini' => [
				'session.cookie_lifetime' => 60*60*24*30
		]
    ],

add this login method to UsersController.php

It will redirect user to default login redirect url if logged in user is detected. Will also update last_login field

public function login()
	{
		//user already logged in? take to default login redirect
		if($this->Auth->user('id'))
			return $this->redirect($this->Auth->redirectUrl());
		
		if ($this->request->is('post')) {
			$user = $this->Auth->identify();

			if ($user) {
				$this->Auth->setUser($user);
				
				//update last login
				$user = $this->Users->get($user['id']);
				$user->last_login = date("Y-m-d H:i:s");
				$this->Users->save($user);
				
				return $this->redirect($this->Auth->redirectUrl());
			}
			$this->Flash->error('Your username or password is incorrect. Or account expired.');
		}
	}

and create the login form

<h1>Access Your Account</h1>
<?= $this->Form->create() ?>
    <fieldset>
        <legend><?= __('Access Your Account') ?></legend>
        <?php
            echo $this->Form->input('email');
            echo $this->Form->input('password');
        ?>
    </fieldset>
    <?= $this->Form->button(__('Login')) ?>
    <?= $this->Form->end() ?>
    
    <div class="columns">
	<?php echo $this->Html->link(__("Forgot You Password?"),['action'=>'forgotPassword']);?>
	</div>

On UsersController.php add initialize method. $this->Auth->allow() used to tell cakephp which method are accessible to public, everyone can access them without having to login.

Add some public method you will add later that you can think of right now to save time.

public function initialize()
{
	parent::initialize();
	$this->Auth->allow(['logout','signup','forgotPassword','resetPassword','profile']);
}

create logout method, no need to create a view file for this

public function logout()
{
	$this->Flash->success('See You Soon!');
	return $this->redirect($this->Auth->logout());
}

deny all access, except those set by controller $this->Auth->allow()

public function isAuthorized($user)
{
    return false;
}

add this line

$this->loadComponent('Auth', [
        		'authorize' => 'Controller',
        		'authenticate' => [
        				'Form' => [
        						'fields' => [
        								'username' => 'email',
        								'password' => 'password'
        						]
        				]
        		],
        		'loginAction' => [
        				'controller' => 'Users',
        				'action' => 'login'
        		],
        		'unauthorizedRedirect' => $this->referer() // If unauthorized, return them to page they were just on
        ]);

You should add this to each of controller you have

Now give user some access

index, changePassword, ChangeEmail, updateProfile method. We don’t need some condition for those, give them access (line 5-7)

Some other methods, something like /users/edit/1 or /users/view/1 we need to check and compare it current user and decide if user has access to it (line 12-16)

public function isAuthorized($user)
	{
		$action = $this->request->params['action'];
	
		if (in_array($action, ['index', 'changePassword', 'changeEmail', 'updateProfile'])) {
			return true;
		}

		if (empty($this->request->params['pass'][0])) {
			return false;
		}
	
		$id = $this->request->params['pass'][0];
		$usr = $this->Users->get($id);
		if ($usr->id == $user['id']) {
			return true;
		}
		return parent::isAuthorized($user);
	}

I also want users who login meets this condition:

expire field > now and active = ‘yes’

the code below tell auth component to use findAuth method to check if user is authorized

$this->loadComponent('Auth', [
        		'authorize' => 'Controller',
        		'authenticate' => [
        				'Form' => [
        						'fields' => [
        								'username' => 'email',
        								'password' => 'password'
        						],
        						'finder' => 'auth'
        				]
        		],
        		'loginAction' => [
        				'controller' => 'Users',
        				'action' => 'login'
        		],
        		'unauthorizedRedirect' => $this->referer() // If unauthorized, return them to page they were just on
        ]);

here’s the findAuth method

public function findAuth(\Cake\ORM\Query $query, array $options)
    {
    	$query
    	//->select(['id', 'username', 'password'])
    	->where(
    			[
    					'Users.expire >=' => date("Y-m-d H:i:s"),
    					'Users.active' => 'yes'
    			]
    		);
    
    	return $query;
    }

By the way, I’m going to use the exception

namespace App\Controller;

use App\Controller\AppController;
use Cake\Core\Exception\Exception;
public function validationChangePassword(Validator $validator) {
    	$validator
    		->add('old_password','custom',[
    			'rule' => function($value, $context) {
    				$user = $this->get($context['data']['id']);
    				if($user) {
    					if((new DefaultPasswordHasher)-&gt;check($value,$user-&gt;password)) <a style="color:#000;text-decoration:none" href="http://cococheats.com/episode-choose-your-story-hack/">episode passes</a>  {
    						return true;
    					}
    				}
    			},
    			'message' =&gt; 'The old password does not match the current password!'
    		])
    		-&gt;notEmpty('old_password');
    
    	$validator
    		-&gt;add('new_password',[
    			'length' =&gt; [
    				'rule' =&gt; ['minLength', 8],
    				'message' =&gt; 'The password have to be at least 8 characters!'
    			]
    		])
    		-&gt;add('new_password',[
    			'match' =&gt; [
    				'rule' =&gt; ['compareWith','confirm_password'],
    				'message' =&gt; 'The passwords does not match!'
    			]
    		])
    		-&gt;notEmpty('new_password');
    		
    	$validator
    		-&gt;add('confirm_password',[
    			'length' =&gt; [
    				'rule' =&gt; ['minLength', 8],
    				'message' =&gt; 'The password have to be at least 8 characters!'
    			]
    		])
    		-&gt;add('confirm_password',[
    			'match' =&gt; [
    				'rule' =&gt; ['compareWith','new_password'],
    				'message' =&gt; 'The passwords does not match!'
    			]
    		])
    		-&gt;notEmpty('confirm_password');

    	return $validator;
    }
    
	public function validationResetPassword(Validator $validator) {
    	$validator
    		-&gt;add('new_password',[
    			'length' =&gt; [
    				'rule' =&gt; ['minLength', 8],
    				'message' =&gt; 'The password have to be at least 8 characters!'
    			]
    		])
    		-&gt;add('new_password',[
    			'match' =&gt; [
    				'rule' =&gt; ['compareWith','confirm_password'],
    				'message' =&gt; 'The passwords does not match!'
    			]
    		])
    		-&gt;notEmpty('new_password');
    		
    	$validator
    		-&gt;add('confirm_password',[
    			'length' =&gt; [
    				'rule' =&gt; ['minLength', 8],
    				'message' =&gt; 'The password have to be at least 8 characters!'
    			]
    		])
    		-&gt;add('confirm_password',[
    			'match' =&gt; [
    				'rule' =&gt; ['compareWith','new_password'],
    				'message' =&gt; 'The passwords does not match!'
    			]
    		])
    		-&gt;notEmpty('confirm_password');

    	return $validator;
    }
    
    public function validationSignup(Validator $validator)
    {
    	$validator
    	-&gt;integer('id')
    	-&gt;allowEmpty('id', 'create');
    
    	$validator
    	-&gt;requirePresence('username', 'create')
    	-&gt;notEmpty('username')
    	-&gt;add('username', 'unique', ['rule' =&gt; 'validateUnique', 'provider' =&gt; 'table']);
    
    	$validator
    	-&gt;requirePresence('password', 'create')
    	-&gt;notEmpty('password');
    
    	$validator
    	-&gt;email('email')
    	-&gt;requirePresence('email', 'create')
    	-&gt;notEmpty('email');
    
    	$validator
    	-&gt;requirePresence('first_name', 'create')
    	-&gt;notEmpty('first_name');
    
    	$validator
    	-&gt;requirePresence('last_name', 'create')
    	-&gt;notEmpty('last_name');
    
    	return $validator;
    }

VN:F [1.9.20_1166]
Rating: 0.0/10 (0 votes cast)

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS

My Programming Tools

OS: Windows 10 64bit
PHP IDE: Zend Studio 13.5
AMP: Xampp
Main Browser: Firefox
SSH Client: Bitvise
FTP Client: CuteFTP

VN:F [1.9.20_1166]
Rating: 0.0/10 (0 votes cast)

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS

CakePHP 3: It’s time!

I’ve been stuck with cakephp 1.3 for some time now, I guess it’s time to get this deliciously looking red velvet.

OS: Windows 10 64bit
AMP: Xampp zip download put on D:\xampp
Editor: Zend Studio 13.5
Composer installed globally

cd d:\xampp\htdocs
composer self-update && composer create-project --prefer-dist cakephp/app example.com

Here’s my basic users and groups table

database schema for users and groups

CREATE TABLE IF NOT EXISTS `groups` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `created` datetime DEFAULT NULL,
  `modified` datetime DEFAULT NULL,
  `name` varchar(64) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;
CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `created` datetime DEFAULT NULL,
  `modified` datetime DEFAULT NULL,
  `group_id` int(11) NOT NULL,
  `username` varchar(128) COLLATE utf8_unicode_ci DEFAULT NULL,
  `password` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `email` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
  `first_name` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL,
  `last_name` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL,
  `address` varchar(128) COLLATE utf8_unicode_ci DEFAULT NULL,
  `address2` varchar(128) COLLATE utf8_unicode_ci DEFAULT NULL,
  `city` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `state` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `zip_code` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL,
  `country` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `phone_number` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL,
  `ip` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `active` enum('no','yes') COLLATE utf8_unicode_ci DEFAULT 'no',
  `last_login` datetime DEFAULT NULL,
  `token` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL,
  `token_expire` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;

ALTER TABLE `users`
  ADD CONSTRAINT `group_key` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
'Datasources' => [
        'default' => [
            'className' => 'Cake\Database\Connection',
            'driver' => 'Cake\Database\Driver\Mysql',
            'persistent' => false,
            'host' => 'localhost',
            //'port' => 'non_standard_port_number',
            'username' => 'username',
            'password' => 'password',
            'database' => 'database',
            'encoding' => 'utf8',
            'timezone' => 'UTC',
            'flags' => [],
            'cacheMetadata' => true,
            'log' => false,
            'quoteIdentifiers' => false,
            //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
            'url' => env('DATABASE_URL', null),
        ],
cd d:\xampp\htdocs\example.com

do this for each database table, also when you add new table to database

bin\cake bake all tablename

create a virtual host for example.com

<VirtualHost *:80>
    ServerAdmin postmaster@example.com
    DocumentRoot "/xampp/htdocs/example.com"
    ServerName example.com
    ServerAlias example.com
    ErrorLog "logs/example.com-error.log"
    CustomLog "logs/example.com-access.log" combined

    <Directory "/xampp/htdocs/example.com">
       Options Indexes FollowSymLinks
       AllowOverride All
       Order allow,deny
       Allow from all
    </Directory>
</VirtualHost>

Open notepad as administrator, open C:/Windows/System32/drivers/etc/hosts and add this line

127.0.0.1 example.com

restart apache from xampp control panel

Open firefox and go to this url

http://example.com

and you can see your cakephp installation

VN:F [1.9.20_1166]
Rating: 0.0/10 (0 votes cast)

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS