CakePHP4でクッキーを使用して、自動ログインを実装する

CakePHP4でクッキーを使用して、自動ログインを実装しようと思ったところ、CakePHP3.5でクッキーの仕様が大きく変わっていましたので、紹介します。

CakePHP3.4まではCookieComponentを使用していましたが、CakePHP3.5から非推奨になっていました。
公式ドキュメントには以下の記述があるのですが、これがよく分からなかったので、紐解いてみます。

クッキーは、 ServerRequest で利用できます。 クッキー をご覧ください。 クッキーの暗号化は クッキー暗号化ミドルウェア をご覧ください。

https://book.cakephp.org/4/ja/controllers/components/cookie.html

本記事ではCakePHP4.1.5を使用しています。

自動ログイン機能の流れ

ログイン時に「ログインしたままにする」というチェックボックスにチェックを付けて、ログインすると、一定期間はずっとログインしたままにして、ログイン画面を開いても自動でログインするようにする、ということを実現してみます。

ログイン時に「ログインしたままにする」というチェックボックスにチェックを付けて、ログインした場合に、「自動ログインをする」という情報をクッキーに保存しておきます。

ログイン画面を開いた際にクッキーが保存されていれば、自動でログインを行う、という仕組みです。

ログイン時にクッキーを保存する

ログイン時にクッキーを保存する処理は以下のようになります。

use Cake\Http\Cookie\Cookie;
use DateTime;
public function login()
{
    $result = $this->Authentication->getResult();
    if ($result->isValid()) {
        if ($this->request->is('post') && $this->request->getData('autologin') === '1') {
            // ログイン情報をDBとクッキーに保存する
            $user = $result->getData();
            $db_user = $this->Users->get($user['id']);
            $set_key = hash('sha256', (uniqid() . mt_rand(1, 999999999) . '_auto_logins'));
            $db_user->login_key = $set_key;
            $this->Users->save($db_user);

            $this->response = $this->response->withCookie(Cookie::create(
                User::KEY_AUTO_LOGIN,
                $set_key,
                [
                    'expires' => new DateTime('+1 month'),
                ]
            ));
        }

        // ログイン済みの場合はリダイレクト
        $target = $this->Authentication->getLoginRedirect() ?? '/mypage';
        return $this->redirect($target);
    }
    ....
}

5行目で「ログインしたままにする」のチェックボックスにチェックが入っているかを判定します。
チェックが入っている場合は、9行目でユニークなIDを生成し、sha256で暗号化してログインキーとします。

10、11行目でログインキーをDBに保存しておきます。

13行目でログインキーをクッキーに保存します。
14行目がクッキーの名前になります。本記事では User::KEY_AUTO_LOGIN とUserエンティティに定数を用意しましたが、「AUTO_LOGIN」という名前でクッキーを保存します。

15行目が保存するログインキーです。
16行目からはオプションで、本記事ではクッキーの有効期限を指定しています。
「+1 month」としているので、1ヶ月後がクッキーの有効期限となります。

クッキーが保存されると、以下のように確認できると思います。
(Chromeで確認した例です)

ログイン画面を開いた際に、クッキーを取得する

クッキーを取得して自動ログインする処理は以下のようになります。

public function login()
{
    ......
    if ($this->request->is('get')) {
        // クッキーにログインキーが保存されていて、DBと一致する場合、自動ログインする
        $autoLoginKey = $this->request->getCookie(User::KEY_AUTO_LOGIN);
        if ($autoLoginKey != null) {
            $user = $this->Users->find()->where(['login_key' => $autoLoginKey])->first();
            if ($user != null) {
                $this->Authentication->setIdentity($user);
                return $this->redirect(['controller' => 'Mypage']);
            }
        }
    }
}

getで画面を開いた際にクッキーを取得します。

6行目がクッキーを取得する処理です。
保存時と同様に、名前は定数で指定しています。

7行目で判定を入れて、クッキーが取得できていれば、8行目で対象のログインキーを条件にDBから対象ユーザーを検索します。

ログインキーが一致するユーザーがいれば、10行目で自動ログイン処理を行って、11行目でログイン後の画面にリダイレクトしています。

ログアウト時にクッキーを削除する

ログアウトする場合は、自動ログインの情報を全て削除します。
ログアウト時にクッキーを削除する処理は以下のようになります。

public function logout()
{
    $result = $this->Authentication->getResult();
    if ($result->isValid()) {
        $user = $this->request->getAttribute('authentication')->getIdentity();
        $db_user = $this->Users->get($user['id']);
        if ($db_user->login_key != null) {
            $db_user->login_key = null;
            $this->Users->save($db_user);

            $this->response = $this->response->withExpiredCookie(new Cookie(User::KEY_AUTO_LOGIN));
        }

        $this->Authentication->logout();
    }
    $this->Flash->success('ログアウトしました。');
    return $this->redirect(['controller' => 'Users', 'action' => 'login']);
}

6行目でログイン中のユーザー情報を元に、DBから対象ユーザーを検索します。
8、9行目でDBのログインキーをnullに更新します。

そして、11行目でクッキーを削除します。
同じ名前でクッキーを生成し、有効期限のない(すぐに無効になる)状態で保存し直すことで消える仕組みのようです。

▼公式ドキュメント
https://book.cakephp.org/4/ja/controllers/request-response.html#response-cookies
https://book.cakephp.org/4/ja/controllers/request-response.html#request-cookies