CakePHP 4 で Authentication プラグインを使ってユーザー認証を実装
はじめに
昨年12月に投稿した「CakePHP 4.0.0-RC2 でユーザー認証を実装」では CakePHP Authentication を composer でインストールすることができず、従来からの Auth コンポーネントを使って実装しました。
あれから月日が流れてインストールが可能になりましたので、今回は CakePHP 4 で CakePHP Authentication プラグインを使ってユーザー認証するコードをご紹介します。
- GitHub - cakephp/authentication: Authentication plugin for CakePHP. Can also be used in PSR7 based applications.
- https://github.com/cakephp/authentication
- CakePHP
- 4.0.4, 4.1.1, 4.1.2
- CakePHP Authentication
- 2.0.0
1. インストール
CakePHP Authentication の Cookbook にある通り composer でインストールします。
> cd \path\to\cakephp4
> composer require cakephp/authentication:^2.0
- CakePHP Authentication 2.x Cookbook
- https://book.cakephp.org/authentication/2/en/index.html
2. users テーブル
今回使用した users テーブルの SQL です。
ユーザー名: member
パスワード: p@ssw0rd
CREATE TABLE users (
id int(10) UNSIGNED NOT NULL,
username varchar(255) NOT NULL,
password varchar(255) NOT NULL,
created datetime NOT NULL,
modified datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE users
ADD PRIMARY KEY (id),
MODIFY id int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
INSERT INTO users
(id, username, password, created, modified)
VALUES
(1, 'member', '$2y$10$chRR/dnRQgyJ4gVlscsIc.aiDsFs1QUT/.AiCfPf.Rru5LixtAfP6', NOW(), NOW());
3. フォーム と ホーム
ログインフォームを作成します。下記のようにまだ処理は入れず、とりあえず形だけです。
<?php
declare(strict_types=1);
namespace App\Controller;
class UsersController extends AppController
{
public function login()
{
}
}
テンプレートは CakePHP 4 標準搭載の milligram 用にしています。
(label タグの for などは省略しています)
- Forms (Milligram - A minimalist CSS framework.)
- https://milligram.io/#forms
<?= $this->Flash->render() ?><!-- ← レイアウトになければ追加 -->
<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>
ついでに、ログイン後に表示する Home 画面も簡単に作っておきます。
<?php
declare(strict_types=1);
namespace App\Controller;
class HomeController extends AppController
{
public function index()
{
die('ログイン成功!');
}
}
4. 認証ロジック
2020年11月20日 追記
下記では主に Application.php に追記をしていますが、新たに
「CakePHP 4 で Authentication を Application.php の肥大化を防ぎつつ導入する方法」を公開しました。
Cookbook を参考に認証ロジックを実装します。Cookbook では email カラムを使っていますが、今回は username カラムで認証しています。その結果、公式のサンプルコードと比べて、コード量を少し減らすことができました。
- Getting Started (CakePHP Authentication 2.x Cookbook)
- https://book.cakephp.org/authentication/2/en/#getting-started
<?php
...
// ↓下記を追加
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 bootstrap(): void
{
...
// Load more plugins here
$this->addPlugin('Authentication'); // ← 追加
}
...
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$middlewareQueue
...
// ↓これを追加(直前の ->add(~); のセミコロン削除を忘れずに)
->add(new AuthenticationMiddleware($this));
return $middlewareQueue;
}
...
// ↓追加
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$service = new AuthenticationService();
$service->setConfig([
'unauthenticatedRedirect' => Router::url('/users/login'),
'queryParam' => 'redirect',
]);
$service->loadAuthenticator('Authentication.Session');
$service->loadAuthenticator('Authentication.Form');
$service->loadIdentifier('Authentication.Password');
return $service;
}
}
public function initialize(): void
{
...
$this->loadComponent('Authentication.Authentication'); // ← 追加
}
class UsersController extends AppController
{
public function beforeFilter(\Cake\Event\EventInterface $event)
{
parent::beforeFilter($event);
$this->Authentication->allowUnauthenticated(['login']);
}
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');
}
}
}
以上で完了です。
動作確認として、ブラウザで /users/login にアクセスして、Username「member」、Password「p@ssw0rd」でログインしてください。
成功すれば /home に遷移し、ユーザー名やパスワードが異なる場合は「Invalid username or password」が表示されます。
もし CakePHP 4 をサブディレクトリの中に設置している場合、うまく動かないかもしれません。
まだ詳細は分かっていませんが、未ログイン状態で認証が必要な画面にアクセスすると、GET パラメータの「redirect」にその URL が乗るのですが、そこにサブディレクトリ名も含まれてしまいます。
そのため、ログイン後のリダイレクトで /subdir/subdir/●●● のように重複した URL になってしまい正常にアクセスできませんでした。
5. ログインユーザーの取得
ログインユーザーの情報を取得する方法は Authenticationコンポーネントを使う方法と、requestを使う方法の2種類があります。
コードは下記のような感じで、どちらも同じオブジェクトを返すようです。
public function index()
{
echo '<h1>1. Authentication コンポーネントで取得</h1>';
$user = $this->Authentication->getIdentity();
debug($user);
debug($user->username);
echo '<h1>2. request で取得</h1>';
$user = $this->request->getAttribute('identity');
debug($user);
debug($user->username);
exit;
}
- Accessing the logged in user ( CakePHP Authentication 2.x Cookbook)
- https://book.cakephp.org/authentication/2/en/authentication-component.html#accessing-the-logged-in-user
6. ログアウト
ログアウトは下記のように実装できます。
public function beforeFilter(\Cake\Event\EventInterface $event)
{
parent::beforeFilter($event);
// ↓ logout を追加
$this->Authentication->allowUnauthenticated(['login', 'logout']);
}
...
// ↓ 追加
public function logout()
{
$this->Authentication->logout();
return $this->redirect(['action' => 'login']);
}
7. おわりに
正直なところ AuthComponent の方が実装が楽だったなという印象を受けました。リクエスト処理機能はすべてミドルウェアに集約するんですね。
直す箇所が多くて面倒くさいですが、作業自体は簡単でした。
具体的な期限は見当たりませんでしたが、いずれ AuthComponent は使えなくなるようなので、どこかのタイミングで CakePHP Authentication プラグインへの置換作業を行う必要がありそうです。
- AuthComponent (CakePHP 4.x Cookbook)
- https://book.cakephp.org/4/en/controllers/components/authentication.html