Cognito のIDプールでS3のユーザー毎フォルダにアクセスしてみる

# 背景
AWS使ってサーバーレスで自分用の家計簿的なwebサービスを勉強も兼ねて開発中。
[２つ前の記事](https://qiita.com/silverbox/items/b3c8a53aa4baa3d02ff8)
[１つ前の記事](https://qiita.com/silverbox/items/327b940e89c1634d8ff8)
せっかくCognito使ったので、ユーザー毎のS3フォルダにアクセスする事をしてみたい。
### 構成
フロント：Vue.js ＋ Element.ui
サーバーサイド：CloudFront ＋ S3 ＋ APIGateway ＋ Lambda ＋ DynamoDB ＋ Cognito

### やってみた
今回、様々なページを参考にさせてもらい、その情報を組み合わせました。もはやどの部分がどのページの参考なのかこんがらがっています。結構トライ＆エラー発生し、各種設定行ったり戻ったりしました。今回は結果のみ共有させてもらいます。

まず、ブラウザからS3にアクセスする為、CORSの設定をします。

```xml:S3バケットのアクセス権限=>CORSの設定
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>Authorization</AllowedHeader>
    <AllowedHeader>Content-*</AllowedHeader>
    <AllowedHeader>Host</AllowedHeader>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
```

ログインユーザー毎にS3へのアクセスを許可する為、CognitoIDプール、認証済みユーザー用のIAMポリシーに以下設定。※以下の指定で**hogehoge-bucket**と指定していますが既に存在するようです。が、そのバケットを意味してないです。実際には自分作成のバケット名です。

```json:認証済みユーザー用のIAMポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "cognito-identity:*",
                "mobileanalytics:PutEvents",
                "cognito-sync:*"
            ],
            "Resource": "*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:ListBucket",
                "s3:DeleteObject",
                "s3:GetBucketLocation"
            ],
            "Resource": [
                "arn:aws:s3:::hogehoge-bucket/cognito/myhome-account/${cognito-identity.amazonaws.com:sub}",
                "arn:aws:s3:::hogehoge-bucket/cognito/myhome-account/${cognito-identity.amazonaws.com:sub}/*"
            ]
        }
    ]
}
```

そして処理本体、VueファイルのjavaScript部分。※ログイン処理が済んでいる事が前提です。ログイン部分は[２つ前の記事](https://qiita.com/silverbox/items/b3c8a53aa4baa3d02ff8)を参照してみて下さい。

```javascript:テスト本体
import AWS from 'aws-sdk'
import awsconfig from '../cognito/config'
import cognito from '@/cognito'

const S3_USERBACKETNAME = 'hogehoge-bucket'
const PROVIDER_KEY = 'cognito-idp.' + awsconfig.Region + '.amazonaws.com/' + awsconfig.UserPoolId

/* ・・中略・・ */

    s3test: function () {
      const cognitoUser = this.$cognito.userPool.getCurrentUser()
      cognitoUser.getSession((err, session) => {
        if (!err && session.isValid()) {
          const itoken = session.getIdToken().getJwtToken()

          // Initialize the Amazon Cognito credentials provider
          AWS.config.region = awsconfig.Region
          AWS.config.credentials = new AWS.CognitoIdentityCredentials({
            IdentityPoolId: awsconfig.IdentityPoolId,
            Logins: {
              [PROVIDER_KEY]: itoken
            }
          })
          var identityId = AWS.config.credentials.identityId
          var s3key = 'cognito/myhome-account/' + identityId + '/testfile.txt'

          // refresh AWS credentials
          AWS.config.credentials.refresh((err) => {
            if (err) {
              console.log(err)
            } else {
              var s3 = new AWS.S3({
                params: { Bucket: S3_USERBACKETNAME }
              })

              const updateParams = {
                Key: s3key,
                Body: 'teststring',
                ContentType: 'application/text'
              }

              s3.upload(updateParams, function (err, data) {
                if (err) {
                  console.log('error : ', err)
                } else {
                  console.log('success : ' + S3_USERBACKETNAME + '/' + s3key)
                }
              })
            }
          })
        }
      })
    }
```

これでテスト用の適当な文字列のファイルをS3のユーザー毎フォルダにアップ出来ました。間違ったフォルダを指定した時は **AccessDenied: Access Denied** で失敗する事も確認できました。

### 特にはまったポイント
- credentialを取得する時に、一時トークンを渡すが、認証プロバイダーとしてcognitoユーザープールを使う場合にどんなキー（上記ソースのPROVIDER_KEY部分）を指定するのか情報を見つけるのに非常に時間がかかった。[参考](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/amazon-cognito-integrating-user-pools-with-identity-pools.html)
- ポリシーで指定する**cognito-identity.amazonaws.com:sub**が実際には何の値を示すのか見つけるのに非常に時間がかかった。[参考](https://eikatou.net/blog/20171124.html)

# 参考にさせてもらったページ

[ブラウザから Amazon S3 への写真のアップロード](https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/s3-example-photo-album.html)
[サインイン後に ID プールを使用して AWS サービスへアクセスする](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/amazon-cognito-integrating-user-pools-with-identity-pools.html)
[[ Serverless ] Cognito、S3、Lambdaで認証機能付きのWebサイトを作ってみました](https://qiita.com/jobbin/items/d2fc0f714eb1f1cfc965)
[【Cognito】Amazon Cognito Identity SDK for JavaScriptで動くサンプルを作ってみた #2/2【JavaScript】](https://qiita.com/tmiki/items/4e7d5f12e5a044f3837e)
[WebブラウザからAmazon S3に直接ファイルをアップロードする](https://dev.classmethod.jp/cloud/aws/s3-cors-upload/)
[AWS Cognitoを使ってサーバレスアプリケーションに認証をつける](https://eikatou.net/blog/20171124.html)