CakePHP 3に標準で用意されているForm認証モジュールは、ユーザIDとパスワード以外の値を入力して認証することを考慮していません。
組織などのグループに所属するユーザを認証する際にグループIDを入力して認証するには、カスタム認証モジュールを利用することになります。
Form認証の基本
まず、標準のForm認証モジュールで認証する方法です。
基本のUsersテーブルだけで認証する場合は、次のようなコードを使用します。
Controller/AppController.php
public function initialize() { : /* * ユーザ認証機能を登録 */ $this->loadComponent('Auth', [ 'authenticate' => [ 'Form' => [ 'fields' => [ 'username' => 'email', 'password' => 'password', ], ], ], 'loginAction' => [ 'controller' => 'Users', 'action' => 'login', ], : ]);
Template/Users/login.ctp
<h1><?= __('Sign In') ?></h1> <?= $this->Form->create() ?> <?= $this->Form->control('email') ?> <?= $this->Form->control('password') ?> <?= $this->Form->button(__('Sign In')) ?> <?= $this->Form->end() ?>
この記述でUsersテーブルにあるemailカラムとpasswordカラムを使って、認証することができるようになります。
Form認証を拡張する
次に、以下のような2つのテーブルを用いて認証することを検討してみたいと思います。
create table `groups` ( `id` INT not null AUTO_INCREMENT , `code` VARCHAR(16) not null , `name` VARCHAR(32) not null , `expiration` DATETIME not null , `status` CHAR(1) not null ); create unique index `groups_IX1` on `groups`(`code`); create table `users` ( `id` CHAR(36) not null , `group_id` INT not null , `code` VARCHAR(32) not null , `name` VARCHAR(250) not null , `password` VARCHAR(250) not null , `status` CHAR(1) not null ); create unique index `users_IX1` on `users`(`group_id`,`code`);
〇〇グループに所属する□□さんを認証することを目論んでいます。
そのため、テンプレートは、
Template/Users/login.ctp
<h1><?= __('Sign In') ?></h1> <?= $this->Form->create() ?> <?= $this->Form->control('group') ?> <?= $this->Form->control('code') ?> <?= $this->Form->control('password') ?> <?= $this->Form->button(__('Sign In')) ?> <?= $this->Form->end() ?>
のようにします。
Authコンポーネントの登録は、
Controller/AppController.php
public function initialize() : /* * ユーザ認証機能を登録 */ $this->loadComponent('Auth', [ 'authenticate' => [ 'Myform' => [ 'fields' => [ 'username' => 'code', 'password' => 'password', ], 'finder' => 'auth', 'contain' => [ 'Groups' => [ 'fields' => [ 'code' => 'group', ], 'conditions' => [ 'Groups.status <>' => 'D', 'Groups.expiration >= NOW()' ], ] ], ], ], 'loginAction' => [ 'controller' => 'Users', 'action' => 'login', ], : ]);
のようにします。
標準の認証モジュールからはcontain
の設定方法を拡張しています。
拡張に伴ってcontain
の設定は、
'contain' => [ '<モデルのクラス名>' => [ 'fields' => [ '<カラム名>' => '<フィールド名>', ... ], 'conditions' => [ '<検索条件>', ... ], ] ],
の形式で登録します。
次にこの設定に合わせ、Form認証モジュールを参考にしてカスタム認証モジュールを作成します。
Auth/MyformAuthenticate.php
<?php namespace App\Auth; use Cake\Http\Response; use Cake\Http\ServerRequest; use Cake\Auth\BaseAuthenticate; use Cake\ORM\TableRegistry; class MyformAuthenticate extends BaseAuthenticate { protected $_defaultConfig = [ 'fields' => [ 'username' => 'username', 'password' => 'password', ], 'userModel' => 'Users', 'scope' => [], 'finder' => 'all', 'contain' => null, 'passwordHasher' => 'Default', ]; protected function _checkFields(ServerRequest $request, array $fields) { foreach ([$fields['username'], $fields['password']] as $field) { $value = $request->getData($field); if (empty($value) || !is_string($value)) { return false; } } return true; } protected function _checkContainFields(ServerRequest $request, array $contains) { foreach ($contains as $contain) { $fields = $contain['fields']; foreach ($fields as $column => $field) { $value = $request->getData($field); if (empty($value) || !is_string($value)) { return false; } } } return true; } protected function _query($username, array $params = null) { $config = $this->_config; $table = TableRegistry::get($config['userModel']); $options = [ 'conditions' => [$table->aliasField($config['fields']['username']) => $username] ]; if (!empty($config['scope'])) { $options['conditions'] = array_merge($options['conditions'], $config['scope']); } if (!empty($config['contain'])) { if (is_array($config['contain'])) { foreach ($config['contain'] as $key => $val) { $conditions = []; foreach ($val['fields'] as $column => $name) { $conditions += [$key.'.'.$column => $params[$key][$column]]; } if (!empty($val['conditions'])) { $conditions = array_merge($val['conditions'], $conditions); } $options['contain'][] = $key; $options['conditions'] = array_merge($options['conditions'], $conditions); } } else { $options['contain'] = $config['contain']; } } $finder = $config['finder']; if (is_array($finder)) { $options += current($finder); $finder = key($finder); } if (!isset($options['username'])) { $options['username'] = $username; } return $table->find($finder, $options); } protected function _findUser($username, $password = null, array $params = null) { $query = $this->_query($username, $params); $result = $query->first(); if (empty($result)) { $hasher = $this->passwordHasher(); $hasher->hash((string)$password); return false; } $passwordField = $this->_config['fields']['password']; if ($password !== null) { $hasher = $this->passwordHasher(); $hashedPassword = $result->get($passwordField); if (!$hasher->check($password, $hashedPassword)) { return false; } $this->_needsPasswordRehash = $hasher->needsRehash($hashedPassword); $result->unsetProperty($passwordField); } $hidden = $result->getHidden(); if ($password === null && in_array($passwordField, $hidden)) { $key = array_search($passwordField, $hidden); unset($hidden[$key]); $result->setHidden($hidden); } return $result->toArray(); } public function authenticate(ServerRequest $request, Response $response) { $fields = $this->_config['fields']; if (!$this->_checkFields($request, $fields)) { return false; } $contains = $this->_config['contain']; if (!$this->_checkContainFields($request, $contains)) { return false; } $options = []; foreach ($contains as $table => $val) { $option = []; foreach ($val['fields'] as $column => $name) { $option[$column] = $request->getData($name); } $options[$table] = $option; } return $this->_findUser( $request->getData($fields['username']), $request->getData($fields['password']), $options ); } }
また、'finder' => 'auth'
を設定しましたので、
Model/Table/UsersTable.php
public function findAuth(\Cake\ORM\Query $query, array $options) { $query->where(['Users.status <>' => 'D']); return $query; }
のようにfindAuth
を追加します。
以上で、複数のテーブルをつないで認証を行うことができるようになりました。