CakePHP 4 で Authentication プラグインを使ってユーザー認証を実装

はじめに

昨年12月に投稿した「CakePHP 4.0.0-RC2 でユーザー認証を実装」では CakePHP Authentication を composer でインストールすることができず、従来からの Auth コンポーネントを使って実装しました。

あれから月日が流れてインストールが可能になりましたので、今回は CakePHP 4 で CakePHP Authentication プラグインを使ってユーザー認証するコードをご紹介します。

CakePHP
4.0.4, 4.1.1, 4.1.2
CakePHP Authentication
2.0.0
目次
  1. インストール
  2. users テーブル
  3. フォーム と ホーム
  4. 認証ロジック
  5. ログインユーザーの取得
  6. ログアウト
  7. おわりに

1. インストール

CakePHP Authentication の Cookbook にある通り composer でインストールします。

> cd \path\to\cakephp4
> composer require cakephp/authentication:^2.0

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. フォーム と ホーム

ログインフォームを作成します。下記のようにまだ処理は入れず、とりあえず形だけです。

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

namespace App\Controller;

class UsersController extends AppController
{
    public function login()
    {
    }
}

テンプレートは CakePHP 4 標準搭載の milligram 用にしています。
(label タグの for などは省略しています)

/templates/Users/login.php
<?= $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 画面も簡単に作っておきます。

/src/Controller/HomeController.php
<?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 カラムで認証しています。その結果、公式のサンプルコードと比べて、コード量を少し減らすことができました。

/src/Application.php
<?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;
    }
}
/src/Controller/AppController.php
public function initialize(): void
{
    ...
    $this->loadComponent('Authentication.Authentication'); // ← 追加
}
/src/Controller/UsersController.php
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種類があります。

コードは下記のような感じで、どちらも同じオブジェクトを返すようです。

/src/Controller/HomeController.php
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;
}

6. ログアウト

ログアウトは下記のように実装できます。

/src/Controller/UsersController.php
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 プラグインへの置換作業を行う必要がありそうです。