A Complete Login and Authentication Application Tutorial for CakePHP 4 Part-4

Cake offers a very good tutorial on using the Auth component here, but it’s not complete. Here’s what we’re going to build: A web application that uses CakePHP’s Auth component to login, logout, and access certain pages when the user is not authorized. After the user is logged in, they can go to the dashboard and edit the user.

Create Mysql Database and Table

In the first step we need to create a database and tables, so here I have created a database of tables “webscodex” and ”users” with identifiers and columns with names. You simply create a Users table as described in the SQL query.

Let’s start by creating a user database table. The user table contains email, password. Additional fields are meta field fields such as created and updated fields. Here is what the final users table looks like.

-- Database: `webscodex`

-- Table structure for table `users`

CREATE TABLE `users` (
  `id` int(100) NOT NULL PRIMARY KEY AUTO_INCREAMENT,
  `name` varchar(100) NOT NULL,
  `email` varchar(100) NOT NULL,
  `username` varchar(100) NOT NULL,
  `password` varchar(100) NOT NULL,
  `created` datetime NOT NULL,
  `updated` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Routes

Now let’s modify CakePHP’s main component to support our login module. First we need to change routes.php so we can have a custom login, logout and dashboard. This step is optional, but I’ll do it to keep the URL looking clean.

config/routes.php

<?php

use Cake\Routing\Route\DashedRoute;
use Cake\Routing\RouteBuilder;


$routes->setRouteClass(DashedRoute::class);

$routes->scope('/', function (RouteBuilder $builder) {
    
    $builder->connect('/', ['controller' => 'Users', 'action' => 'login']);
    $builder->fallbacks();
});

Installing Authentication Plugin

Go to project directory and run this command on your command prompt.

composer require cakephp/authentication:^2.0

Adding Password Hashing

src/Model/Entity/User.php

<?php
namespace App\Model\Entity;
use Authentication\PasswordHasher\DefaultPasswordHasher;
use Cake\ORM\Entity;

class User extends Entity
{
    
    protected $_accessible = [
        '*' => true,
        'id' => false
    ];

    protected function _setPassword(string $password) : ?string
    {   
        if (strlen($password) > 0) {
            return (new DefaultPasswordHasher())->hash($password);
        }
    }
}

Adding Login

In src/Application.php, add the following imports:

// In src/Application.php add the following imports
use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Psr\Http\Message\ServerRequestInterface;

Then implement the authentication interface on your Application class:

// in src/Application.php
class Application extends BaseApplication
    implements AuthenticationServiceProviderInterface
{

Then add the following:

src/Application.php

public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
    $middlewareQueue
        
        ->add(new ErrorHandlerMiddleware(Configure::read('Error')))
        ->add(new RoutingMiddleware($this))
        ->add(new AssetMiddleware([
            'cacheTime' => Configure::read('Asset.cacheTime'),
        ]))

        ->add(new RoutingMiddleware($this))
        ->add(new AuthenticationMiddleware($this))
        ->add(new BodyParserMiddleware())
        ->add(new CsrfProtectionMiddleware([
            'httponly' => true,
        ]));

    return $middlewareQueue;
}

public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
    $authenticationService = new AuthenticationService([
    'unauthenticatedRedirect' => Router::url(['controller'=> 'Users', 'action'=> 'login']),
    ]);

    // Load identifiers, ensure we check email and password fields
    $authenticationService->loadIdentifier('Authentication.Password', [
        'fields' => [
            'username' => 'email',
            'password' => 'password',
        ]
    ]);

    // Load the authenticators, you want session first
    $authenticationService->loadAuthenticator('Authentication.Session');
    // Configure form data check to pick email and password
    $authenticationService->loadAuthenticator('Authentication.Form', [
        'fields' => [
            'username' => 'email',
            'password' => 'password',
        ],

        'loginUrl' => Router::url(['controller'=> 'Users', 'action'=> 'login']),
    ]);    
    return $authenticationService;
}

In your AppController class add the following code

src/Controller/AppController.php

<?php
// src/Controller/AppController.php
declare(strict_types=1);

namespace App\Controller;

use Cake\Controller\Controller;

class AppController extends Controller
{
    
    public function initialize(): void
    {
        parent::initialize();

        $this->loadComponent('RequestHandler');
        $this->loadComponent('Flash');
        $this->loadComponent('FormProtection');
        $this->loadComponent('Authentication.Authentication');
        
    }
}

In your UsersController, add the following code

src/Controller/UsersController.php

<?php
// in src/Controller/UsersController.php
namespace App\Controller;
use App\Controller\AppController;
use Cake\Event\EventInterface;
use Cake\Datasource\FactoryLocator;

class UsersController extends AppController{

	public $usersTableObj;

	public function beforefilter(EventInterface $event){
	    parent::beforefilter($event);
	    $this->usersTableObj = FactoryLocator::get('Table')->get('Users');
      $this->Authentication->addUnauthenticatedActions(['login', 'add']);
	}


  public function index(){
    	$this->loadComponent('Paginator');
	    $users = $this->Paginator->paginate($this->usersTableObj->find());
	    $this->set(compact('users'));
  }

  public function login(){
    	$data = $this->request->allowMethod(['get', 'post']);
	    $result = $this->Authentication->getResult();
	    // regardless of POST or GET, redirect if user is logged in
	    if ($result->isValid()) {
	        return $this->redirect(['controller'=>'Posts', 'action'=>'index']);
	    }
	    // display error if user submitted and authentication failed
	    if ($this->request->is('post') && !$result->isValid()) {
	        $this->Flash->error(__('Invalid Email or Password'));
	    }
  }

  public function logout(){
	    $result = $this->Authentication->getResult();
	    // regardless of POST or GET, redirect if user is logged in
	    if ($result->isValid()) {
	        $this->Authentication->logout();
	        return $this->redirect(['controller' => 'Users', 'action' => 'login']);
	    }
	}


  public function add(){

    	$userEnt = $this->usersTableObj->newEmptyEntity();

    	if ($this->request->is('post')) {
    	    $userPost = $this->request->getData();
    	    $users = $this->usersTableObj->patchEntity($userEnt, $userPost);
    	if ($this->usersTableObj->save($userEnt)) {
    	    $this->Flash->success(__('Your user has been saved.'));
          return $this->redirect(['controller'=>'Users','action' => 'index']);
    	}else{
    	    $this->Flash->error(__('Unable to add your user.'));
          return $this->redirect(['controller'=>'Users','action' => 'add']);
    	  }
    	}
    	$this->set(compact('userEnt', $userEnt));
  }


  public function edit($id=null){

    $userEnt = $this->usersTableObj->get($id);

    if ($this->request->is(['post', 'put'])) {
    	  $editPost = $this->request->getData();
    	  $editUser = $this->usersTableObj->patchEntity($userEnt, $editPost);
    if ($this->usersTableObj->save($userEnt)) {
    		$this->Flash->success(__('Your user has been updated.'));
    		return $this->redirect(['controller'=>'Users','action' => 'index']);
    }else{
    		$this->Flash->error(__('Unable to update your user.'));
          return $this->redirect(['controller'=>'Users','action' => 'add']);
    	  }
    }
    $this->set(compact('userEnt', $userEnt));
	}


	public function delete($id=null){

	$userEnt = $this->usersTableObj->get($id);
	if ($this->usersTableObj->delete($userEnt)) {
		    $this->Flash->success(__('User has been deleted.'));
    		return $this->redirect(['controller'=>'Users','action' => 'index']);
	}else{
		    $this->Flash->error(__('Unable to delete your user.'));
        return $this->redirect(['controller'=>'Users','action' => 'index']);
	    }
	}

}

Add the template for your add, edit and login action:

In /templates/Users/add.php

<div class="text-center" style="margin-top: 50px;">
  <h4>User Registration Form</h4>
</div>
<div class="container">
  <div class="row">
    <div class="col-sm-3"></div>
    <div class="col-sm-6">
      <?php echo $this->Form->create($userEnt, ['name'=>'add_users', 'class'=>'was-validated']) ?>
      <div class="form-group">
        <?php echo $this->Form->control('name', ['type'=>'text', 'class'=>'form-control','placeholder'=>'Enter name','required'=>true]);?>
      </div>
      <div class="form-group">
        <?php echo $this->Form->control('email', ['type'=>'email', 'class'=>'form-control','placeholder'=>'Enter email','required'=>true]);?>
      </div>
      <div class="form-group">
        <?php echo $this->Form->control('username', ['type'=>'text', 'class'=>'form-control','placeholder'=>'Enter username','required'=>true]);?>
      </div>
      <div class="form-group">
        <?php echo $this->Form->control('password', ['type'=>'password', 'class'=>'form-control','placeholder'=>'Enter  password','required'=>true]);?>
      </div>
      <button type="submit" class="btn btn-primary" style="float: right;">Sign Up</button>
      <?php echo $this->Form->end() ?>
    </div>
  </div>
</div>

Output

A Complete Login and Authentication Application Tutorial for CakePHP 4 Part-4

In /templates/Users/index.php

<div class="container">
  <div class="row">
    <div class="col-md-12">
      <h2 class="text-center" style="margin-top:50px;">Users View</h2>
      <table class="table">
        <thead>
          <tr>
            <th>Id</th>
            <th>Name</th>
            <th>Username</th>
            <th>Email</th>
            <th>Created</th>
            <th>Updated</th>
            <th>Action</th>
          </tr>
        </thead>
        <tbody>
          <?php foreach ($users as  $user) { ?>
            <tr>
              <td><?php echo $user->id ?></td>
              <td><?php echo $user->name ?></td>
              <td><?php echo $user->username ?></td>
              <td><?php echo $user->email ?></td>
              <td><?php echo date('d-M-Y', strtotime($user->created)) ?></td>
              <td><?php echo date('d-M-Y', strtotime($user->updated)) ?></td>
              <td>
                <a href="<?php echo $this->Url->build(['controller'=>'Users', 'action'=>'edit', $user->id]) ?>">Edit</a>
                <a href="<?php echo $this->Url->build(['controller'=>'Users', 'action'=>'delete', $user->id]) ?>" 
                  onclick="return confirm('Are you sure you want to delete this item?');">Delete</a>
              </td>
            </tr>
          <?php } ?>
        </tbody>
      </table>
    </div>
  </div>
</div>

In /templates/Users/edit.php

<div class="text-center" style="margin-top: 50px;">
  <h4>Edit User in Database using CakePHP 4</h4>
</div>
<div class="container">
  <div class="row">
    <div class="col-sm-3"></div>
    <div class="col-sm-6">
      <?php echo $this->Form->create($userEnt, ['name'=>'edit_users', 'class'=>'was-validated']) ?>
      <div class="form-group">
        <?php echo $this->Form->control('name', ['type'=>'text', 'class'=>'form-control','placeholder'=>'Enter name','required'=>true]);?>
      </div>
      <div class="form-group">
        <?php echo $this->Form->control('email', ['type'=>'email', 'class'=>'form-control','placeholder'=>'Enter email','required'=>true]);?>
      </div>
      <div class="form-group">
        <?php echo $this->Form->control('username', ['type'=>'text', 'class'=>'form-control','placeholder'=>'Enter username','required'=>true]);?>
      </div>
      <div class="form-group">
        <?php echo $this->Form->control('password', ['type'=>'password', 'class'=>'form-control','placeholder'=>'Enter  password','required'=>true]);?>
      </div>
      <button type="submit" class="btn btn-success" style="float: right;">Update</button>
      <?php echo $this->Form->end() ?>
    </div>
  </div>
</div>

In /templates/Users/login.php

<div class="text-center" style="margin-top: 50px;">
  <h4>User Login</h4>
</div>
<div class="container">
  <div class="row">
    <div class="col-sm-3"></div>
    <div class="col-sm-6">
      <?php echo $this->Form->create(null, ['class'=>'was-validated']) ?>
      <div class="form-group">
        <?php echo $this->Form->control('email', ['type'=>'email', 'class'=>'form-control','placeholder'=>'Enter Email','required'=>true]);?>
      </div>
      <div class="form-group">
        <?php echo $this->Form->control('password', ['type'=>'password', 'class'=>'form-control','placeholder'=>'Enter Password','required'=>true]);?>
      </div>
      <button type="submit" class="btn btn-primary" style="float: right;">Login</button>
      <?php echo $this->Form->end() ?>
    </div>
  </div>
</div>

Output

A Complete Login and Authentication Application Tutorial for CakePHP 4 Part-4

In your AppView 

src/View/AppView.php

$this->loadHelper('Authentication.Identity');

For very simple checking whether the user is logged in you can use:

if ($this->Identity->isLoggedIn()) {
    ...
}

Getting user data can be done with:

$username = $this->Identity->get('username');

You can always support by sharing on social media or recommending my blog to your friends and colleagues. If you have any suggestions / problems about this tutorial, please comment on the  form below.😊

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *