Authentication
Authentication is checking who the visitor is: the visitor enters an email and a password, and the site remembers them in the session. In CakePHP this is done by the official Authentication plugin. A plugin is a separate package that extends the framework.
Installation
composer require "cakephp/authentication:^3.0"
bin/cake plugin load AuthenticationThe first command downloads the package and the second one enables it in the project.
The users table
bin/cake bake migration CreateUsers email:string:unique password:string created modified
bin/cake migrations migrate
bin/cake bake all UsersThe result is the users table, a model, UsersController and templates, including the registration form at /users/add.
Password hash
A password must not be stored as plain text. Add a method to the entity class that hashes it on every assignment:
// src/Model/Entity/User.php
use Authentication\PasswordHasher\DefaultPasswordHasher;
protected function _setPassword(string $password): ?string
{
return $password !== '' ? (new DefaultPasswordHasher())->hash($password) : null;
}A method with the _set prefix is called when the field is written. Register a user at /users/add and the database holds a long hash string.
Setup
The plugin is set up in src/Application.php. The class must implement an interface and get a new method:
// src/Application.php
use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Psr\Http\Message\ServerRequestInterface;
class Application extends BaseApplication implements AuthenticationServiceProviderInterface
{
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$service = new AuthenticationService([
'unauthenticatedRedirect' => '/users/login',
'queryParam' => 'redirect',
]);
$fields = ['username' => 'email', 'password' => 'password'];
$service->loadAuthenticator('Authentication.Session');
$service->loadAuthenticator('Authentication.Form', [
'fields' => $fields,
'loginUrl' => '/users/login',
]);
$service->loadIdentifier('Authentication.Password', ['fields' => $fields]);
return $service;
}
}Session recognises a user who is already logged in and Form checks the submitted login form. In the middleware() method of the same file add a line after RoutingMiddleware:
->add(new AuthenticationMiddleware($this))And load the component in AppController::initialize():
$this->loadComponent('Authentication.Authentication');From this moment all pages require login: a guest is redirected to /users/login.
Login and logout
// src/Controller/UsersController.php
public function beforeFilter(\Cake\Event\EventInterface $event): void
{
parent::beforeFilter($event);
$this->Authentication->allowUnauthenticated(['login', 'add']);
}
public function login()
{
$result = $this->Authentication->getResult();
if ($result && $result->isValid()) {
return $this->redirect($this->Authentication->getLoginRedirect() ?? '/');
}
if ($this->request->is('post')) {
$this->Flash->error('Wrong email or password.');
}
}
public function logout()
{
$this->Authentication->logout();
return $this->redirect(['action' => 'login']);
}The plugin checks the email and password itself; the action only looks at the result. allowUnauthenticated() opens the listed actions to guests.
The login form
Create templates/Users/login.php:
<h1>Log in</h1>
<?= $this->Form->create() ?>
<?= $this->Form->control('email', ['label' => 'Email']) ?>
<?= $this->Form->control('password', ['label' => 'Password']) ?>
<?= $this->Form->button('Log in') ?>
<?= $this->Form->end() ?>Open /articles: you are redirected to the login form and returned back after logging in.
The current user
$user = $this->request->getAttribute('identity');
$userId = $user->getIdentifier();
$email = $user->email;The identity attribute holds the logged-in user, or null for a guest.
Common mistake
After the plugin is enabled all pages become closed, including the login page itself. If it is not opened with allowUnauthenticated(['login']), a guest gets into an endless redirect. Public pages, such as the list and view of articles, are opened the same way in the beforeFilter() of their controller.