概要
CakePHP5がリリースされて約1年になりますが、ログインの実装に関する日本語版公式マニュアルにおそらく内容がアップデートされていないと思われる箇所があり、マニュアル通りにログインフォームを作成するとエラーになります。
※2024年9月23日現在
※英語版のCMS Tutorialでは修正されていましたので、おそらく日本語版が追いついていないと思われます。
メモとして、エラーの発生しないログイン実装手順を記載しておきます。
CakePHP4との変更点(マニュアルだと古いままになっている点)
①authenticationのバージョンが3.0になった
画像のように、マニュアルだとバージョンが2.0になってますが、CakePHP5で使うのは3.0です。
以下のようにインストールします。
composer require "cakephp/authentication:^3.0"
②DefaultPasswordHasherのパスが変わった
マニュアルには画像のように記載されてますが
use Cake\Auth\DefaultPasswordHasher だとユーザ追加時にエラーになります。
正しくは下記です。
use Authentication\PasswordHasher\DefaultPasswordHasher;
③src/AppControllerのinitializeでRequestHandlerのロードが不要になった。
マニュアルには画像のように記載されてますが、RequestHandlerを読み込むとエラーになります。
読み込みを止めましょう。
public function initialize(): void
{
parent::initialize();
// マニュアルには記載されているが不要
// $this->loadComponent('RequestHandler');
$this->loadComponent('Flash');
ログインの実装手順
テーブルを作る
下記のようなテーブルを作りましょう
CREATE TABLE users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255),
password VARCHAR(255),
created DATETIME DEFAULT NULL,
modified DATETIME DEFAULT NULL
);
認証の追加
sshアクセスして、以下のコマンドを実行します
composer require "cakephp/authentication:^3.0"
User周りのファイルを新規作成(bakeして作って編集しても可)
src/Model/Table/UsersTable.php
<?
// src/Model/Table/UsersTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class UsersTable extends Table
{
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('users');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->hasMany('Articles', [
'foreignKey' => 'user_id',
]);
}
public function validationDefault(Validator $validator): Validator
{
$validator
->integer('id')
->allowEmptyString('id', null, 'create');
$validator
->email('email')
->requirePresence('email', 'create')
->notEmptyString('email');
$validator
->scalar('password')
->maxLength('password', 255)
->requirePresence('password', 'create')
->notEmptyString('password');
return $validator;
}
public function buildRules(RulesChecker $rules): RulesChecker
{
$rules->add($rules->isUnique(['email']), ['errorField' => 'email']);
return $rules;
}
protected array $_hidden = ['password'];
}
src/Model/Entity/User.php
<?php
namespace App\Model\Entity;
use Authentication\PasswordHasher\DefaultPasswordHasher;
use Cake\ORM\Entity;
class User extends Entity
{
protected array $_accessible = [
'email' => true,
'password' => true,
'created' => true,
'modified' => true,
];
protected array $_hidden = [
'password',
];
protected function _setPassword($value)
{
if (strlen($value)) {
$hasher = new DefaultPasswordHasher();
return $hasher->hash($value);
}
}
}
src/Controller/UsersController.php
<?php
// src/Controller/UsersController.php
namespace App\Controller;
use App\Controller\AppController;
use Cake\Event\EventInterface;
class UsersController extends AppController
{
public function beforeFilter(\Cake\Event\EventInterface $event)
{
parent::beforeFilter($event);
//以下を記載しないと未ログイン時に無限にリダイレクトする
$this->Authentication->addUnauthenticatedActions(['login','add']);
}
public function add()
{
$user = $this->Users->newEmptyEntity();
if ($this->request->is('post')) {
$user = $this->Users->patchEntity($user, $this->request->getData());
if ($this->Users->save($user)) {
$this->Flash->success(__('The user has been saved.'));
return $this->redirect(['action' => 'add']);
}
$this->Flash->error(__('Unable to add the user.'));
}
$this->set('user', $user);
}
public function login()
{
$this->request->allowMethod(['get', 'post']);
$result = $this->Authentication->getResult();
if ($result->isValid()) {
// ログイン成功後に /article にリダイレクト。任意の値に書き換える
$redirect = $this->request->getQuery('redirect', [
'controller' => 'Articles',
'action' => 'index',
]);
return $this->redirect($redirect);
}
if ($this->request->is('post') && !$result->isValid()) {
$this->Flash->error(__('Invalid email or password'));
}
}
public function logout()
{
$result = $this->Authentication->getResult();
if ($result->isValid()) {
$this->Authentication->logout();
return $this->redirect(['controller' => 'Users', 'action' => 'login']);
}
}
}
templates/Users/login.php
<!-- in /templates/Users/login.php -->
<div class="users form">
<?= $this->Flash->render() ?>
<h3>Login</h3>
<?= $this->Form->create() ?>
<fieldset>
<legend><?= __('ユーザー名とパスワードを入力してください') ?></legend>
<?= $this->Form->control('email', ['required' => true]) ?>
<?= $this->Form->control('password', ['required' => true]) ?>
</fieldset>
<?= $this->Form->submit(__('Login')); ?>
<?= $this->Form->end() ?>
<?= $this->Html->link("Add User", ['action' => 'add']) ?>
</div>
templates/Users/add.php
<!-- templates/Users/add.php -->
<div class="users form">
<?= $this->Form->create($user) ?>
<fieldset>
<legend><?= __('Add User') ?></legend>
<?= $this->Form->control('email') ?>
<?= $this->Form->control('password') ?>
</fieldset>
<?= $this->Form->button(__('Submit')); ?>
<?= $this->Form->end() ?>
</div>
その他設定(既存のファイルを編集)
src/Application.php
<?php
declare(strict_types=1);
// ...
use Cake\Routing\Middleware\RoutingMiddleware;
//以下を追記
use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Cake\Routing\Router;
use Psr\Http\Message\ServerRequestInterface;
// ...implements を追記
class Application extends BaseApplication
implements AuthenticationServiceProviderInterface
{
// ...
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$middlewareQueue
// ...
->add(new RoutingMiddleware($this))
//以下を追記
->add(new AuthenticationMiddleware($this))
// ...
return $middlewareQueue;
}
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$authenticationService = new AuthenticationService([
'unauthenticatedRedirect' => Router::url('/users/login'),
'queryParam' => 'redirect',
]);
$authenticationService->loadIdentifier('Authentication.Password', [
'fields' => [
'username' => 'email',
'password' => 'password',
],
]);
$authenticationService->loadAuthenticator('Authentication.Session');
$authenticationService->loadAuthenticator('Authentication.Form', [
'fields' => [
'username' => 'email',
'password' => 'password',
],
'loginUrl' => Router::url('/users/login'),
]);
return $authenticationService;
}
/...
}
src/Controller/AppController.php
<?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('Authentication.Authentication');
}
}
参考
⚫︎CakePHP公式(日本語)
・シンプルな認証と認可のアプリケーション
※2024年9月23日現在、コードが古い
・CMS チュートリアル – 認証
※同上
⚫︎CakePHP公式(英語)
・CMS Tutorial – Authentication
※2024年9月23日現在、最新のコードと思われる
コメント