【PHP・AWS】AWS Cognitoを通してS3にアップロードする方法

# はじめに
PHPでAWS Cgnito認証を通してAWS認証情報を所得してS3を使用する記事が少なかったので、自分の備忘録としても記事に残したいと思います。
※あると程度AWSやCognitoを知っている程でいきます😅

# 環境
下記のものをcomposerでインストールしておきます。

```
aws/aws-sdk-php: 3.26.3
pmill/aws-cognito: ^0.2.11
```
PHPのバージョンは 7.4.1 です。
## Cognito
### ユーザプールの作成
![スクリーンショット 2020-08-27 23.42.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649661/e6038939-29ce-1ea6-825f-0d26b2643498.png)
適当にプール名を入れて、「ステップに従って設定する」を選択しましょう。

![スクリーンショット 2020-08-29 13.27.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649661/a369ba34-bffa-47ce-ed33-62b36b6f18b1.png)
属性を設定し、ポリシーでパスワードの強度を設定したら今回は凝った設定はしませんので、アプリクライアントの設定に進みましょう。

![スクリーンショット 2020-08-27 23.48.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649661/db7bc365-76c2-d564-df77-296dd0ad1dfa.png)
アプリクライアント名をいれて、
![スクリーンショット 2020-08-29 14.04.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649661/41576b09-a68d-c48f-d71e-010c701bd0fe.png)
「認証用の管理APIのユーザー名パスワード認証、、、」にチェックをしておきましょう。
![スクリーンショット 2020-08-29 14.08.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649661/30e57d99-d7b3-e796-b899-bbf3516b9fad.png)
確認まで進んで、プールの作成をします。

### IDプール（フェデレーティッドアイデンティティ）の作成
![スクリーンショット 2020-08-28 22.22.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649661/a233c2fa-7a3d-b3e7-62af-1e559b43e474.png)
適当なIDプール名を入れて、
![スクリーンショット 2020-08-28 22.22.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649661/ba02d53f-b105-6669-2a63-f4c6dc9a6428.png)
Cognitoのユーザーで認証を通すため、先ほど作成したユーザープールのIDとアプリクライアントのIDを入力し、プールの作成をします。

ユーザープールのIDは「全般設定」から見れます。
![スクリーンショット 2020-08-28 22.21.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649661/0b951609-3f3a-0a00-0a07-408f45bbf18f.png)
クライアントIDは「全般設定のアプリクライアント」から見れます。
![スクリーンショット 2020-08-28 22.22.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649661/de735619-d6de-a805-3c6a-cd35497ff1c4.png)

![スクリーンショット 2020-08-28 22.23.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649661/f27ee021-4333-2ff9-f2a1-6d8f1b39887d.png)
ここで認証を通したユーザーに対して適応するIAM ロールを作成しろと言われるので作成します。
ついでにCognitoのIAMユーザーも作っておきましょう。

### ユーザープールでユーザーの作成
Cognitoにログインできるユーザーを作成しておきましょう。
![スクリーンショット 2020-08-28 22.51.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649661/dda1034a-c914-d4a7-8df8-c310305175a5.png)
「ユーザー＆グループ」を選択してユーザーの作成を選択しましょう。
![スクリーンショット 2020-08-28 23.08.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649661/b1f6e8fc-6a92-6a3e-bae8-01211bf9d036.png)
使用するユーザーのメールアドレスを入力しユーザーの作成をします。
作成できたら、
![スクリーンショット 2020-08-29 16.02.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649661/bbc9b7de-16de-173c-924f-699725873144.png)
こんな感じで確認できると思います。

### Congitoで通してS3を使うための認証情報を取得する
動き的には、![スクリーンショット 2020-08-29 16.11.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649661/08d10836-6081-d751-7582-57e67a05282f.png)
1、Cognitoにログインして「アクセスキー・IDトークン」を取得する
2、IDトークンを使用してAWSを使用する認証情報を取得する
3、取得した認証情報でAWSサービスを使用する

#### Cognitoにログイン
```php:main.php
<?php
  require_once 'vendor/autoload.php';
  use Cognito\cognitoClass;
  use Aws\CognitoIdentity\CognitoIdentityProvider;

  $cc = new cognitoClass();
  $username = 'example@email.com'; //Cognitoユーザーのメールアドレス
  $password = '123456'; //パスワード
  $config = require('config.php');
  
  list($access_token, $id_token) = $cc->login($username, $passwrod);
```
```php:cognitoClass.php
<?php
  namespace Cognito;
  use pmill\AwsCognito\CognitoClient;
  use pmill\AwsCognito\Exception\ChallengeException;
  use pmill\AwsCognito\Exception\PasswordResetRequiredException;
  use Aws\Sdk;
  use Aws\CognitoIdentityProvider\CognitoIdentityProviderClient;

  class cognitoClass{
    protected $client;
    protected $config;

    public function  __construct(){
      $config = require(__DIR__ . '/../config.php');
      $config = $config['cognito'];
      $this->config = $config;
      $aws = new Sdk($config);
      $cognitoClient = $aws->createCognitoIdentityProvider();

      $client = new CognitoClient($cognitoClient);
      $client->setAppClientId($config['app_client_id']);
      $client->setAppClientSecret($config['app_client_secret']);
      $client->setRegion($config['region']);
      $client->setUserPoolId($config['user_pool_id']);
      $this->client = $client;
    }

    public function login($username, $password){
      $client = $this->client;
      $authenticationResponse = $client->authenticate($username,$password);

      if (isset($authenticationResponse['AuthenticationResult'])){
        $authenticationResponse = $authenticationResponse['AuthenticationResult'];
      }
      else if($authenticationResponse['ChallengeName'] == 'NEW_PASSWORD_REQUIRED'){
        $authenticationResponse = $client->respondToNewPasswordRequiredChallenge($username, $password, $authenticationResponse['Session']);
      }
      
      return [$authenticationResponse['AccessToken'], $authenticationResponse['IdToken']];
    }
  }
?>
```
```php:config.php
<?php
return [
 'cognito' =>[
      'credentials' => [
        'key' => $_ENV['AWS_KEY'],
        'secret' => $_ENV['AWS_SECRET_KEY'],
      ],
      'region' => $_ENV['AWS_REGION'],
      'version' => 'latest',
      'app_client_id' => $_ENV['AWS_APP_CLIENT_ID'], //ユーザープールのアプリクライアントID
      'app_client_secret' => $_ENV['AWS_APP_CLIENT_SECRET'], //アプリクライアントIDのシークレット
      'user_pool_id' => $_ENV['AWS_USER_POOL_ID'], //ユーザープールのID
      'fed_pool_id' => $_ENV['AWS_FED_POOL_ID'],  //IDプールのID
    ] 
 ];
```
vendor/pmill/aws-cognitoのexampleフォルダに入っているコードを参考に独自クラス作り、login関数をつくりました。

Cognitoユーザのステータスを「FORCE_CHANGE_PASSWORD」から「CONFIRMED」にしないと、「アクセスキー・IDトークン」が返却されないようになっていますので、その変更する処理を加えています。
初期のままでも変更してくれようですが、上手く動いていなかったので手を加えています。。。

```php:CognitClient.php
<?php
namespace pmill\AwsCognito;
...
class CognitoClient
{
...
    public function authenticate($username, $password)
    {
        try {
            $response = $this->client->adminInitiateAuth([
                'AuthFlow' => 'ADMIN_NO_SRP_AUTH',
                'AuthParameters' => [
                    'USERNAME' => $username,
                    'PASSWORD' => $password,
                    'SECRET_HASH' => $this->cognitoSecretHash($username),
                ],
                'ClientId' => $this->appClientId,
                'UserPoolId' => $this->userPoolId,
                ]);
            return $response;   
            #return $this->handleAuthenticateResponse($response->toArray());
        } catch (CognitoIdentityProviderException $e) {
            throw CognitoResponseException::createFromCognitoException($e);
        }
    }
...
```

main.phpを実行すると「アクセスキー・IDトークン」が帰ってきているかのを確認できるかと思います。

#### S3を使うための設定
CognitoユーザーがS3を使えるようにするには、フェデレーティッドアイデンティティの作成のさいに作成したIAMロールにポリシーをアタッチする必要があります。

![スクリーンショット 2020-08-29 19.23.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649661/f0505fcf-59b0-f978-7d90-87726fbcb908.png)
「〜Auth_Role」にポリシーをアタッチしましょう。

![スクリーンショット 2020-08-31 10.20.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649661/9790508a-5258-e77c-1c68-655a437e5f29.png)
ポリシーで扱うバケットを制限できるますが、今は「FullAccess」でいいでしょう。

#### S3にアップロード
```php:main.php
<?php
  require_once 'vendor/autoload.php';
  use Cognito\cognitoClass;
  use Aws\CognitoIdentity\CognitoIdentityProvider;
  use Aws\S3\S3Client;
  use GuzzleHttp\Promise;

  $cc = new cognitoClass();
  $username = 'example@email.com'; //Cognitoユーザーのメールアドレス
  $password = '123456'; //パスワード
  $config = require('config.php');
  
  list($access_token, $id_token) = $cc->login($username, $passwrod);
  $ci = new CognitoIdentityProvider(
          $config['cognito']['fed_pool_id'],
          ['region'=>$config['cognito']['region'], 'version'=>'latest'],
          ['cognito-idp.'.$config['cognito']['region'].'.amazonaws.com/'.$config['cognito']['user_pool_id']=>$id_token]
          );
  $promise = $ci();
  $credentails = $promise->wait();

  $s3 = new S3Client(array(
    'version'=>'latest',
    'credentials'=>$credentails->toArray(),
    'region'=>'ap-northeast-1'
  ));

  $file = 'test.txt';
  $result = $s3->putObject([
    'Bucket'        => 'pool-example',
    'Key'           => 't.txt',
    'SourceFile'    => $file,
    'ContentType'   => mime_content_type($file),
  ]);
```
CognitoIdentityProviderにCognitoの情報を引数に渡したら、promise関数で帰ってくるのでそれに対応した取得の仕方をすると認証情報を取得できます。

```php:main.php
list($access_token, $id_token) = $cc->login($username, $passwrod);
  $ci = new CognitoIdentityProvider(
          $config['cognito']['fed_pool_id'],
          ['region'=>$config['cognito']['region'], 'version'=>'latest'],
          ['cognito-idp.'.$config['cognito']['region'].'.amazonaws.com/'.$config['cognito']['user_pool_id']=>$id_token]
          );
```
CognitoIdentityProviderには第1引数にIDプールのID、第2引数にIDプールのリージョン・バージョン、第3引数にログイン方法を渡します。

このまま、mainを実行するとs3にtest.txtがアップロードされていることが確認できると思います。
![スクリーンショット 2020-08-31 12.12.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649661/8419ac9f-0ccf-3d0a-113c-fb89c3033c1b.png)

### さいごに
最後まで見ていただきありがとうございます。
今回はCognitoを通してに認証でしたが、他にもAmazon・Facebook・Apple・Twitterなどがあります。