CakePHP 4 で Authentication を Application.php の肥大化を防ぎつつ導入する方法

はじめに
以前「CakePHP 4 で Authentication プラグインを使ってユーザー認証を実装」で、CakePHP 4 でのユーザー認証の実装方法をご紹介しました。
その際は公式ドキュメントを参考にしつつ実装を進めたのですが、
Application.php が use や implements などの追加で、ちょっと大きくなっています。
そこで今日は CakePHP 4 で Application.php の肥大化を防ぎながら Authentication プラグインを導入する方法をご紹介します。
- cakephp/app
- 4.1.2
- cakephp/cakephp
- 4.1.5
- cakephp/authentication
- 2.0.0
1. 下準備
動作確認に使用した users テーブルの SQL です。
パスワード: p@ssw0rd
CREATE TABLE users (
id int(10) UNSIGNED NOT NULL,
username varchar(255) NOT NULL,
password varchar(255) NOT NULL
);
ALTER TABLE users
ADD PRIMARY KEY (id),
MODIFY id int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
INSERT INTO users
(id, username, password)
VALUES
(1, 'member', '$2y$10$chRR/dnRQgyJ4gVlscsIc.aiDsFs1QUT/.AiCfPf.Rru5LixtAfP6');
2. Authentication プラグイン導入
下記 composer コマンドで CakePHP Authentication プラグインをインストールします。
$ cd /path/to/cakephp4
$ composer require cakephp/authentication:^2.0
- Quick Start - CakePHP Authentication 2.x Cookbook
- https://book.cakephp.org/authentication/2/en/index.html
そしてここからがドキュメントの手順と異なる部分です。
まず独自のクラス AppAuthenticationServiceProvider を実装します。
<?php
declare(strict_types=1);
namespace App\Authentication;
use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Cake\Routing\Router;
use Psr\Http\Message\ServerRequestInterface;
class AppAuthenticationServiceProvider implements AuthenticationServiceProviderInterface
{
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$service = new AuthenticationService();
$service->setConfig([
'unauthenticatedRedirect' => Router::url(['controller' => 'users', 'action' => 'login']),
'queryParam' => 'redirect',
]);
$service->loadAuthenticator('Authentication.Session');
$service->loadAuthenticator('Authentication.Form');
$service->loadIdentifier('Authentication.Password');
return $service;
}
}
次に Application.php で Authentication のプラグインとミドルウェアを有効化します。そのミドルウェア追加の際に AppAuthenticationServiceProvider クラスのインスタンスを指定します。
...
// ↓ 追加
use App\Authentication\AppAuthenticationServiceProvider;
use Authentication\Middleware\AuthenticationMiddleware;
...
public function bootstrap(): void
{
...
// ↓ 追加
$this->addPlugin('Authentication');
...
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$middlewareQueue
...
// ↓これを追加(直前の ->add(~); のセミコロン削除を忘れずに)
->add(new AuthenticationMiddleware(
new AppAuthenticationServiceProvider()
));
...
最後に AppController.php に1行追記します。
public function initialize(): void
{
...
// ↓ これを追加
$this->loadComponent('Authentication.Authentication');
}
ここまでについて補足です。
Application.php の new AuthenticationMiddleware() の引数には、下記いずれかの型のオブジェクトを入れます。
\Authentication\AuthenticationServiceProviderInterface
これは下記ファイルのコンストラクタを見るとわかります。
/vendor/cakephp/authentication/src/Middleware/AuthenticationMiddleware.php
公式ドキュメントのコードでは引数に $this を入れていますが、
その Application クラスを見ると imprements で
\Authentication\AuthenticationServiceProviderInterface が実装されているので、引数に入れる型と一致しています。
AuthenticationServiceProviderInterface について下記ソースコードをみると、
getAuthenticationService() 関数だけが必要であることが分かります。
そこで今回は Application.php への追記を減らすために、
AuthenticationServiceProviderInterface を implements した
AppAuthenticationServiceProvider クラスを作り、
そこに getAuthenticationService() を実装しました。
3. ログイン と ログアウト
UsersController に ログイン と ログアウト の機能を追加します。
<?php
declare(strict_types=1);
namespace App\Controller;
class UsersController extends AppController
{
public function beforeFilter(\Cake\Event\EventInterface $event)
{
parent::beforeFilter($event);
$this->Authentication->allowUnauthenticated(['login', 'logout']);
}
public function login()
{
$result = $this->Authentication->getResult();
// 認証成功
if ($result->isValid()) {
$target = $this->Authentication->getLoginRedirect() ?? '/home';
return $this->redirect($target);
}
// ログインできなかった場合
if ($this->request->is('post') && !$result->isValid()) {
$this->Flash->error('Invalid username or password');
}
}
public function logout()
{
$this->Authentication->logout();
return $this->redirect(['action' => 'login']);
}
}
テンプレは CakePHP 4 に採用されている CSS フレームワークの Milligram に対応しています。
<form method="post">
<fieldset>
<label>Username</label>
<input type="text" name="username">
<label>Password</label>
<input type="password" name="password">
<input
type="hidden" name="_csrfToken" autocomplete="off"
value="<?= $this->request->getAttribute('csrfToken') ?>">
<button class="button-primary" type="submit">Submit</button>
</fieldset>
</form>
- Milligram - A minimalist CSS framework
- https://milligram.io/
4. ホームにユーザー情報表示
ログイン後の遷移先として、ユーザー情報をダンプする画面を作りました。
<?php
declare(strict_types=1);
namespace App\Controller;
class HomeController extends AppController
{
public function index()
{
// ログインユーザー
$user = $this->Authentication->getIdentity();
debug($user);
exit;
}
}
これで完了です。
あとは /users/login にアクセスして動作を確認してみてください。
5. おわりに
個人的には /src/Application.php はなるべくコンパクトにしたいと思っています。
Application.php の役割はプラグインやミドルウェアなどの登録(有効化)に限定し、各機能の詳細は別ファイルに持たせることで、保守性がよくなると感じています。
今回は AppAuthenticationServiceProvider というクラス名で、
/src/Authentication に置いていますが、クラスやフォルダの名前は別のものでも差支えありません。
(その際は namespace の変更をお忘れなく)
様々な機能を追加すると Application.php は肥大化してきます。
肥大化してから分割するとコストが増えてしまいがちなので、早い段階から分割しておくのがオススメです。