CakePHP3でユーザ認証を設定する

##備考
- postgresqlをDBとして利用
- emailをログインIDとして使う
- CakePHP 3.4を利用

## ユーザ用のテーブルを作成

```create_users.sql
CREATE TABLE users (
    id        SERIAL       PRIMARY KEY,
    email     VARCHAR(255) NOT NULL, 
    password  VARCHAR(255) NOT NULL,
    lastname  VARCHAR(255) NOT NULL,
    firstname VARCHAR(255) NOT NULL, 
    role      VARCHAR(20)  NOT NULL,
    created   TIMESTAMP    NOT NULL DEFAULT NOW(), 
    modified  TIMESTAMP
);
```

## bakeでコード生成

```
cd /var/www/html/app/bin
./cake bake all Users
```

## bakeで生成されたコードを修正
### エンティティ

パスワードを保存する際、生の値ではなくハッシュを保存するように変更：

```Model/Entity/User.php
use Cake\Auth\DefaultPasswordHasher; // 追加

    // （他のコード）

    /**
     * パスワードのhashを保存する
     */
    protected function _setPassword($password)
    {
        if (strlen($password) > 0) {
            return (new DefaultPasswordHasher)->hash($password);
        }
    }
```
### AppController.php

Authコンポーネントの読み込みを実施。
その際、ログインIDにUsers.emailを用いることを指定。
またisAuthorizedメソッドを追加し、管理者ロールのユーザに全機能のアクセスを開放、
その他のロールの場合はデフォルトで拒否とする（許可したい場合は各コントローラで個別に設定）

```Controller/AppController.php
    public function initialize()
    {
        parent::initialize();

        $this->loadComponent('RequestHandler');
        $this->loadComponent('Flash');

        /*
         * Enable the following components for recommended CakePHP security settings.
         * see http://book.cakephp.org/3.0/en/controllers/components/security.html
         */
        $this->loadComponent('Security');
        $this->loadComponent('Csrf');

        $this->loadComponent('Auth', [
            'authorize'      => [ 'Controller'],
            'authenticate'   => [ 'Form' => [ 'fields' => ['username' => 'email', 'password' => 'password' ] ] ],
            'loginRedirect'  => [ 'controller' => 'Articles' , 'action' => 'index' ],
            'logoutRedirect' => [ 'controller' => 'Users'    , 'action' => 'login' ],
            'loginAction'    => '/users/login',
            'authError'      => 'Login required'
        ]);

    }

    public function isAuthorized($user)
    {
        // Admin can access every action
        if (isset($user['role']) && $user['role'] === 'admin') {
            return true;
        }

        // Default deny
        return false;
    }
```

### コントローラ

ログイン、ログアウト用のアクションを追加。
また認証不要なアクションを$this->Auth->allowで定義

```Controller/UsersController.php

use Cake\Event\Event; // 追加

    /**
     * 認証不要なアクションを定義
     */
    public function beforeFilter(Event $event)
    {
        parent::beforeFilter($event);
        // ここにloginを追加してはならない
        // ソース：https://book.cakephp.org/3.0/en/tutorials-and-examples/blog-auth-example/auth.html
        $this->Auth->allow(['add', 'logout']);
    }

    /**
     * role別にアクセスを制御したい場合はここに記述。全ロールに許可する場合はreturn trueとだけ書く
     */
    public function isAuthorized($user)
    {
        return true;
    }

    /**
     * ログインアクション
     */
    public function login()
    {
        if ($this->request->is('post')) {
            $user = $this->Auth->identify();
            if ($user) {
                $this->Auth->setUser($user);
                return $this->redirect($this->Auth->redirectUrl());
            }
            $this->Flash->error(__('Invalid username or password, try again'));
        }
    }

    /**
     * ログアウトアクション
     */
    public function logout()
    {
        return $this->redirect($this->Auth->logout());
    }
```

### ログイン用のテンプレート作成

```Template/Users/login.ctp

<div class="users form">
<?= $this->Flash->render() ?>
<?= $this->Form->create() ?>
    <fieldset>
        <legend><?= __('Please enter your username and password') ?></legend>
        <?= $this->Form->control('email') ?>
        <?= $this->Form->control('password') ?>
    </fieldset>
<?= $this->Form->button(__('Login')); ?>
<?= $this->Form->end() ?>
</div>

```