CakePHPで２種類（会員/管理者）の認証を実装 [ Authentication ]


会員ページと管理者ページの認証を分ける実装を、Authenticationプラグインを使って行ってみました。

・会員ページは、Membersテーブルを参照し、emailとpasswordでログイン
・管理者ページは、Usersテーブルを参照し、usernameとpasswordでログイン

##１.teble作成

Membersテーブル
![スクリーンショット 2021-02-09 11.23.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52355/617915b2-d14e-dba8-6880-ee429294e1b2.png)

Usersテーブル
![スクリーンショット 2021-02-09 11.23.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52355/57252d47-64bb-d5cd-e7e3-b954036b3d46.png)


##2.Authenticationプラグイン 導入
参考：
CakePHP Authentication 2.x Cookbook
https://book.cakephp.org/authentication/2/ja/index.html
CakePHP 4 で Authentication を Application.php の肥大化を防ぎつつ導入する方法
https://tt-computing.com/cake4-app-authc-svc-provider

###2-1.インストール
```
php composer.phar require "cakephp/authentication:^2.0"
```

###2-2.独自のクラス AppAuthenticationServiceProviderの実装
srcにAuthenticationフォルダを作成.

```php:app/src/Authentication/AppAuthenticationServiceProvider.php
declare(strict_types=1);

namespace App\Authentication;

use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Identifier\IdentifierInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Cake\Http\MiddlewareQueue;
use Cake\Routing\Router;
use Psr\Http\Message\ServerRequestInterface;

class AppAuthenticationServiceProvider implements AuthenticationServiceProviderInterface
{
    public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
    {
        $service = new AuthenticationService();

        // 認証されていない場合にユーザーがどこにリダイレクトするかを定義します。
        $service->setConfig([
           'unauthenticatedRedirect' => '/users/login',
           'queryParam' => 'redirect',
         ]);

        $fields = [
           IdentifierInterface::CREDENTIAL_USERNAME => 'email',
           IdentifierInterface::CREDENTIAL_PASSWORD => 'password'
        ];

        // 認証者を読み込みます。セッションを優先してください。
        $service->loadAuthenticator('Authentication.Session');
        $service->loadAuthenticator('Authentication.Form', [
           'fields' => $fields,
           'loginUrl' => '/users/login'
        ]);

        // 識別子を読み込みます。
        $service->loadIdentifier('Authentication.Password', compact('fields'));

        return $service;
    }
}
```

###2-3.src/Application.phpにAuthenticationMiddleware を追加

インポート:

```php:src/Application.php
use App\Authentication\AppAuthenticationServiceProvider;
use Authentication\Middleware\AuthenticationMiddleware;
```
AuthenticationMiddleware を追加:

```php:src/Application.php
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
    {
        $middlewareQueue
        ...
        ->add(new AuthenticationMiddleware(
                new AppAuthenticationServiceProvider()
            ));

        return $middlewareQueue;
    }
```

###2-4.AppControllerにて、Componentをロード

```php:src/Controller/AppController.php
public function initialize(): void
{
....
 $this->loadComponent('Authentication.Authentication');
}

public function beforeFilter(EventInterface $event)
{
   $this->Authentication->allowUnauthenticated(['login']);        
}

```

##3.ログインアクションの構成

###3-1.login/logoutを実装 

```php:MembersController.php
    public function login(){

        $result = $this->Authentication->getResult();
        if ($result->isValid()) {
            $target = $this->Authentication->getLoginRedirect() ?? '/members';
            return $this->redirect($target);
        }
        if ($this->request->is('post') && !$result->isValid()) {
             $this->Flash->error(__('メールアドレスとパスワードが無効です'));
        }
        
    }

    public function logout()
    {
        $this->Authentication->logout();
        return $this->redirect(['controller' => 'Members', 'action' => 'login']);
    }

```

```php:UsersController.php
    public function login(){

        $result = $this->Authentication->getResult();
        if ($result->isValid()) {
            $target = $this->Authentication->getLoginRedirect() ?? '/users';
            return $this->redirect($target);
        }
        if ($this->request->is('post') && !$result->isValid()) {
             $this->Flash->error(__('ユーザー名とパスワードが無効です'));
        }
        
    }

    public function logout()
    {
        $this->Authentication->logout();
        return $this->redirect(['controller' => 'users', 'action' => 'login']);
    }

```

ログインするために、パスワードのハッシュ化。
Model/Entity/Users.php
Model/Entity/Members.php

```php:
use Cake\Auth\DefaultPasswordHasher;
protected function _setPassword($password)
{
   if (strlen($password) > 0) {
      return (new DefaultPasswordHasher)->hash($password);
   }
}
```

###3-2.ログインページの作成
メンバーページは、メールアドレスとパスワードでフォームを作成

```php:Template/Members/login.php
<form method="post">
    <fieldset>
        <label>Email</label>
        <input type="text" name="email">
        <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">ログイン</button>
    </fieldset>
</form>

```
管理者ページは、ユーザネームとパスワードでフォームを作成

```php:Template/Users/login.php
<form method="post">
    <fieldset>
        <label>Email</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">ログイン</button>
    </fieldset>
</form>

```



##4.認証の分岐
2-2.で実装した、AppAuthenticationServiceProviderにて認証処理を分けます。

```php:app/src/Authentication/AppAuthenticationServiceProvider.php

class AppAuthenticationServiceProvider implements AuthenticationServiceProviderInterface
{
    public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
    {
        //ServerRequestInterfaceでリクエストされたコントローラーを取得します.
        $controller = $request->getParam('controller');
        
        $service = new AuthenticationService();
        
        //リクエストコントローラで分岐.
        if($controller === 'Members'){
            $service = $this->set_member($service);
        }
        
        if($controller === 'Users'){
            $service = $this->set_users($service);
        }
        return $service;
    }

    private function set_member($service){

        $service->setConfig([
            'unauthenticatedRedirect' => Router::url([
                'prefix' => false,
                'plugin' => null,
                'controller' => 'Members',
                'action' => 'login']),
            'queryParam' => 'redirect',
        ]);
  
        
        $fields = [
            IdentifierInterface::CREDENTIAL_USERNAME => 'email',
            IdentifierInterface::CREDENTIAL_PASSWORD => 'password'
        ];

        $service->loadAuthenticator('Authentication.Session');

        $service->loadAuthenticator('Authentication.Form',[
                'fields' => $fields,
                'loginUrl' => Router::url([
                    'prefix' => false,
                    'plugin' => null,
                    'controller' => 'Members',
                     'action' => 'login'
                ]),
        ]);
        
        
        $service->loadIdentifier('Authentication.Password',[
            'resolver'=>[                               
                'className' => 'Authentication.Orm',    
                'userModel' => 'Members' //ここで Members を指定
            ],               
            //認証に使用するフィールド          
            'fields' => [
                'username' => 'email',
                'password' => 'password',
            ]
        ]);

        
        return $service;
    }

    private function set_users($service){
        
        
        $service->setConfig([
            'unauthenticatedRedirect' => Router::url([
                'prefix' => false,
                'plugin' => null,
                'controller' => 'Users',
                'action' => 'login']),
                'queryParam' => 'redirect',
        ]);
  
        
        $fields = [
            IdentifierInterface::CREDENTIAL_USERNAME => 'username',
            IdentifierInterface::CREDENTIAL_PASSWORD => 'password'
        ];

        /*
        ユーザーデータのセッションキー、 デフォルトは Auth.
        AuthはMemberでのログインで使用している為、セッションキーを変更する
        */
        $service->loadAuthenticator('Authentication.Session',[
            'sessionKey' => 'AdminAuth',
        ]);
        
        $service->loadAuthenticator('Authentication.Form',[
                'fields' => $fields,
                'loginUrl' => Router::url([
                    'prefix' => false,
                    'plugin' => null,
                    'controller' => 'Users',
                     'action' => 'login'
                ]),
        ]);
        
        $service->loadIdentifier('Authentication.Password',[
            'resolver'=>[                               
                'className' => 'Authentication.Orm',    
                'userModel' => 'Users'                   
            ],                                          
            'fields' => [
                'username' => 'username',
                'password' => 'password',
            ]
        ]);

        
        return $service;
    }
}
```