【Laravel + Amazon Cognito】で、ユーザー新規登録を試したまとめ

![laravel_cognito(qiita).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415008/ba5c1960-dbd4-5fb6-4e47-6d0c04d9c6e0.png)
##前提条件（環境）
2019年12月28日に試してみたものです。
Laravel 5.8
aws/aws-sdk-php 3.13

##期待する動作
登録フォームにメールとパスワードを入力して送信すると、Cognitoのユーザープールに仮登録される。
そして、その際に届いたメールのリンクを辿ると、「アカウントのステータス」が「CONFIRMED」になる。

##手順
1. IAMで、Cognitoにアクセス可能なアクセスキー作成
2. Cognito側の初期設定
3. AWSのSDKをインストール
4. AWSの情報を.envファイルに追加
5. SDKの中にある、CognitoIdentityProviderClientクラスに用意されているsignUpメソッドを使う

##1. IAMで、Cognitoにアクセスできるアクセスキー作成
割愛

##2. Cognito側の初期設定
1. 「ユーザープールの作成」
2. 「デフォルトを確認する」
3. アプリクライアントの作成

デフォルトと変えた部分は以下4つ

+ ユーザーのサインインに使う項目：Eメールアドレス
+ カスタム属性custom:hogeを追加
+ Eメールの検証タイプを「コード」から「リンク」に変更　→　Cognito ドメインの作成or自己所有ドメインの登録が必要
+ アプリクライアント:「クライアントシークレットを生成」のチェックを外した

注意

+ 「クライアントシークレットを生成」にチェックを入れると、SDKのCognitoIdentityProviderClientクラスの色々なメソッドの引数として、シークレットキーが必須になってくる。
+ デフォルトのパスワードの強度は、強すぎるので注意


###ユーザーの属性について
+ 必須の標準属性と、後から追加できるカスタム属性がある。
+ **標準属性**は、email,genderなど17個の中から選ぶことができるが、ユーザープール作成後は変更できない。
+ **カスタム属性**は、名前を自由に決めることができるが、名前の先頭に勝手にcustom:が付けられる。また、ユーザープール作成後に追加できるが、削除はできない。

####カスタム属性について
* 試しにユーザーをプールに追加した後に、カスタム属性を追加してみたが、その場合でも、カスタム属性追加前のユーザーに、追加したカスタム属性の値を登録することができた。
* **ユーザープール作成後にカスタム属性を追加した場合、デフォルトのアプリクライアントでは、カスタム属性の読み込み権限や書き込み権限が付与されていない**。
アプリクライアント画面の「属性の読み込みおよび書き込みアクセス権限を設定する」で変更できる。

##3. AWSのSDKをインストール
これで、CognitoのAPIにアクセスできるメソッド等をvendor内にインストールできる

```
$ composer require aws/aws-sdk-php
```

##4. AWSの情報を.envファイルに追加
```:.env
#AWS(IAM)
AWS_ACCESS_KEY_ID=＝(IAMユーザーのアクセスキーID)
AWS_SECRET_ACCESS_KEY=(IAMユーザーのシークレットアクセスキー)

#AWS(Cognito)
AWS_DEFAULT_REGION=(ユーザープールを作成したリージョン)
AWS_COGNITO_VERSION=2016-04-18
AWS_COGNITO_USER_POOL_ID=(プールID)
AWS_COGNITO_APP_CLIENT_ID=(アプリクライアントID)
```

##5.CognitoIdentityProviderClientクラスのsignUpメソッドを使う
+ CognitoのAPIにアクセスするためのクライアントを実装
+ 下記URLに、CognitoIdentityProviderClientクラスのメソッドの一覧があり、それぞれ引数や返り値等が書かれている。

https://docs.aws.amazon.com/aws-sdk-php/v3/api/class-Aws.CognitoIdentityProvider.CognitoIdentityProviderClient.html

+ **signUpメソッドの必須の引数は３つ（ClientId、Password、Username）**。必須の標準属性を設定した場合には、そのUserAttributesも必須。「クライアントシークレットを生成」にチェックを入れている場合、SecretHashも必須

```php5:app/Cognito/CognitoClient.php
<?php

namespace App\Cognito;

// SDKの中から使うクラス
use Aws\CognitoIdentityProvider\CognitoIdentityProviderClient;
use Aws\CognitoIdentityProvider\Exception\CognitoIdentityProviderException;

class CognitoClient
{
    protected $client;
    protected $clientId;
    protected $poolId;

    public function __construct(CognitoIdentityProviderClient $client, $clientId, $poolId)
    {
        $this->client = $client;
        $this->clientId = $clientId;
        $this->poolId = $poolId;
    }

    public function register($email, $password, array $attributes = [])
    {
        $attributes['email'] = $email;

        try {
            //CognitoIdentityProviderClientクラスのsignUpメソッド
            $response = $this->client->signUp([
                'ClientId' => $this->clientId,
                'Password' => $password,
                'UserAttributes' => $this->formatAttributes($attributes),
                'Username' => $email,
            ]);

        } catch (CognitoIdentityProviderException $e) {
            throw $e;
        }

        return;
    }

    protected function formatAttributes(array $attributes)
    {
        $userAttributes = [];
        foreach ($attributes as $key => $value) {
            $userAttributes[] = [
                'Name' => $key,
                'Value' => $value,
            ];
        }
        return $userAttributes;
    }
}
```

##６　その他のソースコード
あえてGuardは使わず、できるだけ**シンプル**にしました。

###ルーティング
```php5:routes/web.php
// 登録フォーム
Route::get('/register', "RegisterController@registerForm")->name('authRegister');
Route::post('/register', "RegisterController@temporaryRegister");
```

###ビュー1つ
```html:resources/views/auth/register_form.blade.php
{{-- フラッシュメッセージ --}}
@if (session('temporary_register'))
<div class="alert alert-success">
    {{ session('temporary_register') }}
</div>
@endif

{{-- 登録フォーム --}}
<div>
    <form action="{{ route('authRegister') }}" method="post">
        @csrf
        <input type="email" name="email">
        <input type="password" name="password">
        <input type="submit" value="新規登録する">
    </form>
</div>
```

###Cognito用のServiceProvider

```php5:app/Providers/CognitoAuthServiceProvider.php
<?php

namespace App\Providers;

use App\Cognito\CognitoClient;
use Aws\CognitoIdentityProvider\CognitoIdentityProviderClient;
use Illuminate\Foundation\Application;
use Illuminate\Support\ServiceProvider;

class CognitoAuthServiceProvider extends ServiceProvider
{
    public function boot()
    {
        // .envファイルに書いた認証情報と、CognitoClientの結合
        $this->app->singleton(CognitoClient::class, function (Application $app) {
            
            $config = [
                'region' => env('AWS_DEFAULT_REGION'),
                'version' => env('AWS_COGNITO_VERSION'),
            ];

            return new CognitoClient(
                new CognitoIdentityProviderClient($config),
                env('AWS_COGNITO_APP_CLIENT_ID'),
                env('AWS_COGNITO_USER_POOL_ID')
            );
        });
    }
}
```

作成したServiceProvierの登録を忘れないように！

```php5:app/config/app.php
// 中略

'providers' => [
        // 中略
        
        App\Providers\CognitoAuthServiceProvider::class, // <- 追加
];
```

###Controller

```php5:app/Http/Controllers/RegisterController.php
<?php

namespace App\Http\Controllers;

use App\Cognito\CognitoClient;
use Illuminate\Http\Request;

class RegisterController extends Controller
{
    public function registerForm()
    {
        return view("auth.register_form");
    }

    public function temporaryRegister(Request $request)
    {
        // 属性
        $attributes = ['custom:hoge' => "テスト"];
        // CognitoClientクラスに書いたregisterメソッドを使う
        app()->make(CognitoClient::class)->register($request->email, $request->password, $attributes);

        return redirect()->route('authRegister')->with('temporary_register', 'まだ仮登録です。メールに記載されたリンクにアクセスして下さい');
    }
}
```

##その他：パスワードリセット・変更等のメソッド
|  | メソッド |
|:-:|:-:|
|パスワードリセット| ForgotPassword  |
|パスワード変更| ChangePassword   |
|属性の変更| UpdateUserAttributes |
|退会| DeleteUser  |

**これらは全て、AccessTokenが必須の引数となっている。**
AccessTokenは、InitiateAuth（又はAdminInitiateAuth）メソッドの返り値として取得できる。


参照：https://docs.aws.amazon.com/aws-sdk-php/v3/api/class-Aws.CognitoIdentityProvider.CognitoIdentityProviderClient.html

##参考にさせて頂いた記事

[Laravel + AWS Cognito での認証機能の実装【ユーザー新規登録編】](https://qiita.com/ayasamind/items/9b07c8c2fd433505a0eb#cognito%E3%81%AE%E6%96%B0%E8%A6%8F%E7%99%BB%E9%8C%B2%E6%A9%9F%E8%83%BD%E3%82%92%E5%AE%9F%E8%A3%85)
[Laravelのユーザ認証をAmazon Cognitoで行う方法]
(https://qiita.com/suzumurakk/items/efe4543cf0df2cd31659)