【ITニュース解説】Per-user two-factor auth in CakePHP with CakeDC/Users (opt-in, one method)
2026年08月25日に「Dev.to」が公開したITニュース「Per-user two-factor auth in CakePHP with CakeDC/Users (opt-in, one method)」について初心者にもわかりやすく解説しています。
ITニュース概要
CakePHPのCakeDC/Usersプラグインで、全ユーザー必須だった二段階認証(2FA)を、ユーザーが自由にオン/オフできる選択制にする方法を解説。ユーザーテーブルにフラグを追加し、認証チェッカーをカスタマイズすることで、柔軟な2FA実装が可能になる。
ITニュース解説
Webアプリケーションのセキュリティを強化する「2段階認証(Two-Factor Authentication、略して2FA)」は、現代の多くのサービスで導入されており、システムエンジニアを目指す上で理解しておくべき重要な技術だ。これは、通常のパスワードだけでなく、スマートフォンアプリで生成される6桁の数字(ワンタイムパスワード、TOTP)など、別の情報も組み合わせて本人確認を行うことで、不正なログインをより強力に防ぐ仕組みを指す。
CakePHPというWebアプリケーション開発フレームワークには、ユーザー認証や管理を簡単にするための「CakeDC/Users」という便利なプラグインが存在する。このプラグインは、もともと2段階認証の機能を含んでいる。しかし、その標準的な使い方では、アプリケーションの「すべてのユーザーに2段階認証を強制的に適用する」か、あるいは「すべてのユーザーに2段階認証を適用しない」か、という二つの選択肢しか提供されていなかった。現代のWebアプリケーションで広く求められているのは、個々のユーザーが自分のアカウント設定画面から、自由に2段階認証を有効にしたり無効にしたりできる「選択制(オプトイン)」の機能である。この記事では、この「ユーザーごとに選択可能な2段階認証」を、CakeDC/Usersプラグインの機能を活用しつつ、最小限の変更で実装する方法を解説する。
この実装の核心は、Webアプリケーションが「このログイン試行に対して2段階認証が必要か否か」を判断するisRequired()というメソッドの挙動をカスタマイズすることにある。CakeDC/Usersは、OneTimePasswordAuthenticationCheckerInterfaceというインターフェース(クラスが持つべき機能の設計図)を通じてこの判断を行っており、デフォルトではDefaultOneTimePasswordAuthenticationCheckerというクラスがその役割を担っている。このデフォルトの実装では、もし2段階認証機能が全体で有効に設定されている場合、すべてのログイン試行に対して「はい、2段階認証が必要です」と答えてしまう。
そこで、私たちはDefaultOneTimePasswordAuthenticationCheckerを継承したPerUserOneTimePasswordCheckerという新しいクラスを作成し、その中でisRequired()メソッドを「オーバーライド」(親クラスのメソッドを子クラスで上書きすること)する。このオーバーライドされたメソッドの中では、まず親クラスのisRequired()メソッド(これがCakeDC/Usersの標準的な判断ロジックを担う)を実行し、その結果が「2段階認証が必要である」と判断した場合に、さらに「現在ログインしようとしているこのユーザーが、自身の意思で2段階認証を有効にしているか」という独自の条件を追加でチェックする。
具体的なコードは以下のようになる。
1declare(strict_types=1); 2 3namespace App\Authentication; 4 5use CakeDC\Auth\Authentication\DefaultOneTimePasswordAuthenticationChecker; 6 7class PerUserOneTimePasswordChecker extends DefaultOneTimePasswordAuthenticationChecker 8{ 9 public function isRequired(?array $user = null): bool 10 { 11 // 親クラスのルール(authenticatorが有効かなど) AND ユーザーが2段階認証を有効にしているか 12 return parent::isRequired($user) && !empty($user['two_steps']); 13 } 14}
このコードの中で特に重要なのは!empty($user['two_steps'])という部分だ。これは、ログインしようとしているユーザーのデータにtwo_stepsという名前のフラグ(真偽値、つまりtrueかfalseのどちらかの値を持つデータ)が存在し、その値がtrue(有効な状態)である場合に、という意味になる。このtwo_stepsフラグは、ユーザーが「自分は2段階認証を利用したい」と明示的に設定したことを示すために、ユーザー情報に追加する新しい項目だ。この論理積(&&、AND条件)によって、たとえ親クラスが「2段階認証が必要」と判断したとしても、two_stepsがfalseのユーザーは最終的に「2段階認証は不要」と判断され、通常のパスワード認証だけでログインできる。一方、two_stepsがtrueのユーザーは、パスワード認証後に6桁のコード入力を求められることになる。
次に、このtwo_stepsフラグをどこに保存するかだが、ユーザー情報を管理するデータベースのusersテーブルに新しいカラムを追加する必要がある。CakeDC/Usersプラグインは、2段階認証の運用に必要な秘密鍵(secret)や、秘密鍵が検証済みであるかを示すフラグ(secret_verified)といったカラムは既に用意しているが、「このユーザーが2段階認証を希望しているか」を示す直接的なフラグは標準では持っていない。
そこで、データベースのスキーマ変更を管理する「マイグレーション」という機能を利用して、usersテーブルにtwo_stepsという真偽値型のカラムを追加する。
1declare(strict_types=1); 2 3use Migrations\BaseMigration; 4 5class AddTwoStepsToUsers extends BaseMigration 6{ 7 public function up(): void 8 { 9 $this->table('users') 10 ->addColumn('two_steps', 'boolean', ['default' => false, 'null' => false]) 11 ->update(); 12 } 13 14 public function down(): void 15 { 16 $this->table('users')->removeColumn('two_steps')->update(); 17 } 18}
このマイグレーションコードの中で特に重要な設定は'default' => falseだ。これは、新しく追加されるtwo_stepsカラムの初期値がfalseになることを意味する。これにより、既にデータベースに存在するユーザーデータには影響がなく、また新しく登録されるユーザーもデフォルトでは2段階認証がオフの状態から始まるため、どのユーザーも強制的に2段階認証を使わされる心配がなくなる。
作成したカスタムチェッカーをCakePHPアプリケーションに組み込むためには、アプリケーションの設定ファイルであるconfig/users.phpに記述を追加する必要がある。ここで、OneTimePasswordAuthenticatorの設定セクションに、checkerとして先ほど作成したPerUserOneTimePasswordChecker::classを指定し、同時にloginオプションをtrueに設定して2段階認証機能を有効にする。
1use App\Authentication\PerUserOneTimePasswordChecker; 2use RobThree\Auth\Providers\Qr\BaconQrCodeProvider; 3 4$config['OneTimePasswordAuthenticator'] = [ 5 'checker' => PerUserOneTimePasswordChecker::class, 6 'login' => true, 7 'issuer' => 'My App', // 認証アプリに表示されるラベル 8 'qrcodeprovider' => new BaconQrCodeProvider(format: 'svg'), 9];
ここで一つ重要な注意点がある。CakeDC/UsersのデフォルトのQRコード生成プロバイダ(EndroidQrCodeProvider)を使うと、比較的新しいバージョンのendroid/qr-codeライブラリ環境ではエラーが発生する可能性があるのだ。具体的には、endroid/qr-codeライブラリのバージョン6でsetSize()というメソッドが削除されたため、このメソッドを呼び出している部分でエラーが発生する。この問題を回避するために、RobThree\Auth\Providers\Qr\BaconQrCodeProviderという別のQRコードプロバイダを使用し、format: 'svg'を指定することを推奨する。BaconQrCodeProviderは純粋なPHPで実装されており、生成されるSVG形式のQRコードはHTMLテンプレートに「データURI」として直接埋め込むことができるため、非常に扱いやすいという利点もある。
次に、ユーザーが自分で2段階認証を有効にするための画面(自己登録画面)を実装する。これは、ユーザーがログイン後に自分のアカウント設定ページなどで操作できるWebページを指す。この画面での処理は、Webアプリケーションの「コントローラー」と呼ばれる部分に実装される。例えば、twoFactor()という名前のメソッドがその役割を果たす。
この自己登録画面では、主に二つのステップが実行される。
- GETリクエスト(画面表示時): ユーザーが初めてこの画面にアクセスすると、アプリケーションは未検証の秘密鍵(
secret)を生成する。そして、この秘密鍵を基にしたQRコード画像、手動入力用の文字列、そしてユーザーがスマートフォンアプリで生成された6桁のコードを入力するためのフォームを表示する。この時点ではまだ検証が済んでいないため、ユーザー情報のsecret_verifiedとtwo_stepsフラグはfalseのままにしておく。こうすることで、もしユーザーが登録途中で画面を閉じてしまったり、操作を中断したりした場合でも、2段階認証が中途半端に有効になってしまい、次回ログイン時にロックアウトされる(ログインできなくなる)という事態を防ぐことができる。 - POSTリクエスト(フォーム送信時): ユーザーがスマートフォンアプリで生成した6桁のコードを入力してフォームを送信すると、アプリケーションはそのコードが正しいかを検証する。コードが正しければ、ユーザー情報の
secret_verifiedとtwo_stepsの両方のフラグをtrueに更新し、データベースに保存する。さらに、現在のセッション(ユーザーがログインしている状態)のユーザー情報(「アイデンティティ」と呼ぶ)もすぐに更新することで、ユーザーは再ログインすることなく、そのセッションから直ちに2段階認証が有効になった状態となる。もし入力されたコードが間違っていれば、アプリケーションはエラーメッセージを表示し、ユーザーに再入力を促す。 CakeDC/Authプラグインが提供するOneTimePasswordAuthenticatorコンポーネントを利用することで、createSecret()(秘密鍵の生成)、verifyCode()(コードの検証)、getQRCodeImageAsDataUri()(QRコード画像の生成)といった便利な機能を簡単に呼び出すことができる。
2段階認証は有効にできるだけでなく、ユーザーが何らかの理由で不要になったときに、いつでも無効にできるようにしておく必要がある。これもコントローラーアクションとして実装する。例えばdisableTwoFactor()というメソッドだ。
この処理では、ユーザーの秘密鍵(secret)をクリアし、secret_verifiedとtwo_stepsのフラグを両方ともfalseに戻してデータベースに保存する。有効化の時と同様に、現在のセッションのアイデンティティを更新することで、ユーザーは即座に2段階認証が無効になった状態となる。セキュリティの観点から、この無効化アクションは、単にWebページを開くだけでなく、POSTリクエスト(フォーム送信など)でのみ実行されるように制限することが推奨される。
最後に、アプリケーション全体で2段階認証機能を使うかどうかを簡単に切り替えられるように、機能全体をスイッチで制御する方法を説明する。これは、CakePHPのConfigureクラスを利用して、例えばMyApp.TwoFactor.enableといった設定フラグを用意し、その値がtrueの場合にのみ、先ほど設定したconfig/users.php内でOneTimePasswordAuthenticatorを登録するようにする。また、ユーザーが自己登録/無効化を行うためのコントローラーアクションも、この設定フラグがfalseであれば、ページが見つからないことを示す404エラーを返すようにすることで、アプリケーションが2段階認証機能を搭載しない場合でも、関連機能が誤って表示されたり実行されたりするのを防ぐことができる。
まとめると、CakeDC/Usersプラグインを使用してユーザーごとの2段階認証を実装するには、以下の4つの主要な要素が必要となる。
usersテーブルに、初期値がfalseであるtwo_stepsという真偽値型のカラムを追加する。DefaultOneTimePasswordAuthenticationCheckerを継承し、isRequired()メソッドをオーバーライドして、親クラスのルールに加え、two_stepsフラグがtrueであるかを条件とするカスタムチェッカーを作成する。- ユーザーが自分で2段階認証を有効にしたり無効にしたりできるWebページと、それに対応するコントローラーアクション(有効化の際は6桁のコード検証が必要)を実装する。
- QRコードの生成には、ライブラリのバージョンアップによる問題を回避するため、
BaconQrCodeProvider(format: 'svg')を使用する。
これらの変更を行うことで、CakeDC/Usersが提供する優れた認証・ユーザー管理機能を維持しつつ、「2段階認証は常にオン」ではなく「2段階認証はユーザーが利用可能」という、多くのWebアプリケーションが求める柔軟なセキュリティ機能を実現することができる。これにより、ユーザーは自身の判断でセキュリティレベルを高めることができ、アプリケーション開発者もより現代的でユーザーフレンドリーな認証システムを提供することが可能となる。