CognitoでGoogleアカウントとFacebookアカウントを紐づける

# 目的
CognitoでGoogleアカウントとFacebookアカウントを紐づけることで、メールアドレスが同一なら同一ユーザであると識別する。

# ユーザプール設定

まずはAWSコンソール上で全ての設定をデフォルトでユーザプールを作成します。
その後、IDプロバイダーの設定でFacebook・Googleを追加します。
追加の方法は[こちら](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pools-social-idp.html)のページで詳しく説明されています。
設定し終わると以下のような設定になります。
![ユーザープール_-_Amazon_Cognito.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74899/8675157c-3c86-623c-2f96-c0cc95ca3e32.png)
![ユーザープール_-_Amazon_Cognito.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74899/44f8a69b-b0c4-ce62-9db7-9e362d8d2779.png)

その後、**アプリクライアントの設定**タブで有効なIDプロバイダにチェックを入れ、コールバックURLの設定をします。
![ユーザープール_-_Amazon_Cognito.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74899/1556b511-960d-f7c9-83f6-5f3682310515.png)
一番下にある**ホストされたUIを起動**をクリックすると以下の画面が現れます。
![Signin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74899/c004fb50-1d0c-8d63-1ea0-fd328ad413d7.png)
Google・Facebookそれぞれのアカウントでログインして以下のことが確認できれば設定は完了です。

 - ちゃんとコールバックする
 - **ユーザとグループ**タグでユーザが作成されている

![ユーザープール_-_Amazon_Cognito.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74899/8a98bbbb-777e-11d8-1260-314ebf462ca1.png)


# 実装
以下が実際に試したコードの一部になります。

```
    public function snsSignIn(string $authorizationCode) // コールバックで受け取った code
    {
        $signInResponse = $this->cognito->snsSignIn($authorizationCode); // ※1
        try {
            // DB の中に Cognito のユーザネームが保存されていればログイン、なければ 404 になります
            $this->authentication->login($signInResponse->getCognitoTokens());
        } catch (\Exception $exception) {
            if ($exception->getCode() !== 404) {
                throw $exception;
            }

            $accessToken = $signInResponse->getAccessToken();
            $socialUser = $this->cognito->fetchUserInfo($accessToken); // ※2
            $cognitoUsername = $socialUser['email'];
            $socialAccountUsername = $socialUser['username'];

            try {
                $cognitoUser = $this->cognito->adminGetUser($cognitoUsername); // ※3
                $cognitoId = $cognitoUser['Username'];
            } catch (Exceptions\UserNotFoundException $exception) {
                // Cognitoアカウントを作成し、ステータスを CONFIRM にする。
                $createResponse = $this->cognito->createUser($cognitoUsername);
                $this->cognito->adminSetUserPassword($cognitoUsername);
                $cognitoId = $createResponse->getCognitoId();
                // TODO DB に Cognito ユーザネームを登録
            }

            // Status が EXTERNAL_PROVIDERであるソーシャルアカウントを削除
            $this->cognito->adminDeleteUser($socialAccountUsername); // ※4

            // Cognitoアカウントとソーシャルアカウントをリンク
            $this->cognito->linkProviderForUser($cognitoId, $socialAccountUsername); // ※5
        }
        $this->accessTokenHandler->setUserPool($this->cognito);
        return route('HomeIndex');
    }
```
※1 [トークンエンドポイント](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/token-endpoint.html)
※2 [USERINFO エンドポイント](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/userinfo-endpoint.html)
※3 [AdminGetUser](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminGetUser.html)
※4 [AdminDeleteUser](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminDeleteUser.html)
※5 [AdminLinkProviderForUser](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminLinkProviderForUser.html)

注意すべき点は※4の部分です。
`linkProviderForUser`はソーシャルアカウントと既存のユーザを紐づけることができますが、紐づけたいソーシャルアカウントが既に`EXTERNAL_PROVIDER`でアカウントが生成されている場合は以下のエラーが出てしまいます。
`Merging is not currently supported, provide a SourceUser that has not been signed up in order to link`

そのためリンクする前に`EXTERNAL_PROVIDER`のアカウントを削除しています。

上記のコードを使用し、FacebookとGoogleアカウントでログインしたあとのCognitoの状態が以下の画面です。

アカウントが一つだけ存在します。
![ユーザープール_-_Amazon_Cognito.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74899/a19054a1-05aa-9b9f-afe1-f417b66ae9ed.png)

中身を見てみると`Identities`にGoogleとFacebookのアカウントが登録されていることがわかります。
![ユーザープール_-_Amazon_Cognito.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74899/e120aa50-5226-2083-3433-0c3f4aeac6e5.png)