AWS Cognito + S3でパスワード認証付きのWebページを作る。


# 目標

管理者(私)とそのメンバーだけが閲覧できる会員専用ページ的なものを作る。

## 参考ページ

めちゃくちゃお世話になったサイト

・http://penguinitis.g1.xrea.com/computer/programming/AWS/S3_web/S3_web.html

基本的にはここを参照すればいい。足りなかった部分は補足する。

# 目次

1. webページを公開する
2. Cognitoを用いて認証機能をつける
3. 認証されないユーザーからのアクセスをはじく
4. 認証時に管理者に通知を送る
5. 後片付け
 
# 1. webページを公開する

ここは公式のtutorialを追うだけなので割愛。

https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/s3-example-photos-view.html

バケットを作成する。バケット名はのちにドメインのプレフィックスに使う部分と一致させる必要がある。
いずれのオブジェクトもパブリック読み取りアクセスを付与しておく。

<img width="1118" alt="qiita1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/4a5b222a-d65e-86c1-c6d3-2066ecd7e3dc.png">

また、「パブリックアクセスをすべてブロック」のチェックは外しておく。

S3 > Static website hosting > エンドポイントのURLにアクセスしてページが表示されればOK。

# 2. Cognitoを用いて認証機能をつける

### ユーザープールの作成

Cognitoを開く。

1. プール名を決めてステップに従って設定。
2. 「どの標準属性が必要ですか?」項目でe-mailのチェックを外す。[次のステップ]
3. 「ユーザーに自己サインアップを許可しますか?」項目の「管理者のみにユーザーの作成を許可する」にチェックする。[次のステップ]
4. 全て[次のステップ]で[プールの作成]
5. 右のサイドバーから[アプリクライアント]を選択。[アプリクライアントを追加]→「クライアントシークレットを生成」のチェックを外して作成。
6. アプリクライアントIDを控えておく。
7. 右のサイドバーから[アプリクライアントの設定]を選択。有効なIDプロバイダの下の「Cognito User Pool」にチェック。
8. コールバックURLにindex.htmlのオブジェクトURLを指定する。サインアウトURLはサインアウト後に遷移するページなので適当に[google](https://www.google.com)あたりのURLを指定しておく。
9. 「OAuth 2.0」は[OAuthフロー]にImplicit grantを、[OAuth スコープ]にopenidを指定する。
10. 右のサイドバーから[ドメイン名]を選択。「プレフィックス」にはs3のバケット名を埋め、[使用可能かチェック]→[変更の保存]

以下からログインのテスト。

>https://＜プレフィックス＞.auth.ap-northeast-1.amazoncognito.com/login?response_type=token&client_id=＜クライアントID＞&redirect_uri=＜コールバックURL＞

>https://＜プレフィックス＞.auth.ap-northeast-1.amazoncognito.com/logout?client_id=＜クライアントID＞&logout_uri=＜サインアウトURL＞

とりあえず、以下の画面まで行ければOK

<img width="541" alt="qiita2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/29cbadb9-d071-5e4b-aa1a-7ad536f159e8.png">

### ユーザーの追加

[ユーザーとグループ]からユーザーを追加する。このWebページは管理者が作成するユーザしかアクセスできないので、作成する。パスワードは一定以上の強度がないと登録不可らしい。
以下のチェックは外す。

* この新規ユーザーに招待を送信しますか?
* 電話番号を検証済みにしますか?
* E メールを検証済みにしますか?

作成したユーザー名とパスワードで先ほどのログイン画面からログイン。パスワード変更を求められる。同じものでもいい。

# 3. 認証されないユーザーからのアクセスをはじく

今の状態では以下のいずれからも画像にアクセスできてしまう。

* 認証されないユーザーのindex.htmlへのアクセス
* オブジェクトURLから画像に直接アクセス

## 認証されないユーザーのアクセスをはじく

PhotoViewer.jsにlogonの処理をいれるために冒頭を書き換え。

```
var albumBucketName = '＜バケット名＞';

const params = new URLSearchParams(location.hash.slice(1));

AWS.config.region = '＜リージョン＞';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: '＜IDプールのID＞',
    Logins : {
        'cognito-idp.ap-northeast-1.amazonaws.com/＜ユーザープールID＞' : params.get("id_token")
      },
});

```

アクセスポリシーから
認証されないユーザーのポリシー「Cognito_testPoolUnauth_Role」のJSONのEffectをDenyに書き換える。

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Deny",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::＜バケット名＞"
            ]
        }
    ]
}
```

認証したユーザーのポリシーに下記を適応。

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::＜バケット名＞"
        },
        {
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::＜バケット名＞/*"
        }
    ]
}
```

##### ユーザープールをIDプールと統合する

参考サイトにはここが抜けていて少し戸惑ったところ。

・https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/amazon-cognito-integrating-user-pools-with-identity-pools.html

この設定をしないと`Missing credentials in config`エラーとなる。

### テスト

* 上記の設定を一通り終えてもう一度ログインへ→OK。
* コールバックURL`https://＜バケット名＞.s3-ap-northeast-1.amazonaws.com/index.html`から直接アクセスすると今度は`Access Denied`ではじかれる。
* Static website hostingのエンドポイントURLからアクセス→`Access Denied`

これで認証がないとアクセスできない仕組みを構築できた。次はオブジェクトへの直接のアクセス。

## オブジェクトへの直接アクセスをはじく

__うまくいったと思ったがブラウザのキャッシュが残っているために表示できているような気がする。試しにブラウザを変えてみると画像にアクセスできない。この辺りはCloudFrontなど別サービスも視野に入れてみる。__

~~[参考ページ](http://penguinitis.g1.xrea.com/computer/programming/AWS/S3_web/S3_web.html)のコードを丸パクリします！~~

~~バケットのjsファイルを更新する。~~

~~オブジェクトへのURLを用いずに直接アクセスするため、設定からパブリックアクセスを閉じる。対象のオブジェクトのパブリックアクセスの[Everyone]を選択し、「オブジェクトの読み取り」のチェックを外す。~~

<img width="1356" alt="qiita3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/d29430dc-d3e5-e51a-d25b-5f449cdcfa79.png">

### ~~テスト~~

* ~~上記の設定を一通り終えてもう一度ログインへ→OK（なぜかアルバムを開いた1回目は画像が表示されない、原因探査中）。~~
* ~~コールバックURL`https://＜バケット名＞.s3-ap-northeast-1.amazonaws.com/index.html`から直接アクセスすると今度は`Access Denied`ではじかれる。~~
* ~~Static website hostingのエンドポイントURLからアクセス→`Access Denied`~~
* ~~画像のオブジェクトURLから直接アクセス→以下のページが出て読み取れない。~~

>This XML file does not appear to have any style information associated with it. The document tree is shown below.

#4. 認証時に管理者に通知を送る

Cognitoのユーザープールのサイドバーにある[トリガー]では認証時（前後など細かく分かれてる）に指定するlambdaをコールすることができる。

今回は誰かがアクセスしようとする度に管理者（私）のslackに通知が来るようにしてみた。
ググればでるので通知部分の処理は省略。
重要なのはコールバック関数でeventをCognitoに渡してあげるようにしないと認証時にエラーとなる。

```
exports.handler = (event, context, callback) => {

    // 処理

    // Return to Cognito
    callback(null, event);  
};
```

#5. 後片付け

ユーザープールだけは削除手順があり、まず[アプリの統合]→[ドメイン名]からドメインを削除してからプールの削除をしないとエラーで怒られる。
>User pool cannot be deleted. It has a domain configured that should be deleted first.