ユーザー認証付きの静的サイトをCognito+CloudFront+Lambda@Edgeで構築する

# 背景
CloudFrontのLambda@Edgeを使うとS3のコンテンツにCognitoユーザープールを使用した認証を手軽に実装できるということで, 試してみました.

基本的にはこちらのページ([Cognito認証を導入した振り返り](https://zenn.dev/cer0un0/articles/8411774976273c))を参考にさせていただきました. 
進めていく中で以下のような問題が発生したので, 解消方法を整理しておきます.
1. Lambda@Edgeのサイズ制限について
2. Lambda@Edgeのログが出力されない
3. cognito-at-edgeの実行時エラー

# 1. Lambda@Edgeのサイズ制限について
Viewer Request時に実行するLambda@Edgeには1MBという非常に厳しいサイズ制限があります([参考](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/edge-functions-choosing.html)). 
普通に実装するとこの制限に引っかかってしまいました.

## 解消方法
以下のようにバンドルすることでサイズを削減することができました. これをZIP化してLambdaにデプロイすることで, CloudFrontのViewer Requestに紐づけることができました.
```bash:bash
$ npx esbuild --bundle index.ts --minify --outfile=index.js --platform=node
$ zip upload.zip index.js 
```

# 2. Lambda@Edgeのログが出力されない
Lambda@EdgeのデプロイとCloudFrontの紐づけまでを完了しドメインにアクセスしたところ, 503エラーが発生しました(このエラーの原因については後述).

Lambdaの実行時エラーが原因ということがわかったのでCloudWatchからログを確認しようとしたのですが, Lambdaの"モニタリング"からアクセスできるus-east-1のCloudWatchには実行された形跡がなく, エラーを確認することができませんでした.

## 解消方法
こちらの記事([Lambda@Edge の設置によって出力されるログは2種類ある](https://dev.classmethod.jp/articles/lambda-edge-logs-role/))にあるように, CloudFrontにデプロイしたLambda@Edgeのログは**Lambda@Edgeを実行したエッジロケーションのCloudWatchに書き込まれる**ようです.

テスト実行時のログはLambdaをデプロイしたリージョンと同じリージョン(us-east-1)のCloudWatchに書き込まれますが, **実行時のエラーを確認するにはエッジロケーションのCloudFrontを参照する**必要があります.

||リージョン|ロググループ|説明|
|---|---|---|---|
|1|us-east-1|/aws/lambda/<関数名>|テスト実行時のログが書き込まれる|
|2|ap-northeast-1<br/>(エッジロケーション)|/aws/lambda/us-east-1.<関数名>|CloudFront実行時のログが書き込まれる|


# 3. cognito-at-edgeの実行時エラー
[cognito-at-edge](https://github.com/awslabs/cognito-at-edge)をインストールし, READMEに従って下記のようなindex.tsを作りました.
```ts:index.ts
const { Authenticator } = require("cognito-at-edge");

const authenticator = new Authenticator({
  // Replace these parameter values with those of your own environment
  region: "ap-northeast-1", // user pool region
  userPoolId: "ap-northeast-1_XXXXXX", // user pool ID
  userPoolAppId: "XXXXXX", // user pool app client ID
  userPoolDomain: "XXXXXX" // user pool domain
});

exports.handler = async (request) => authenticator.handle(request);
```

これをバンドルしてZIP化し, デプロイした後にCloudFrontに紐づけたのですが, ドメインにアクセスすると503エラーが発生しました.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584131/da32461a-f349-e9a4-69f7-9741dabfc7dd.png)

> The Lambda function associated with the CloudFront distribution is invalid or doesn't have the required permissions. We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.

## 解消方法
Authenticatorの引数 `userPoolAppSecret` にクライアントシークレットを指定することで解消しました.
クライアントシークレットは ユーザープール > クライアントアプリケーション から確認することができます.

:::note warn
README では `userPoolAppSecret` はOptionalとなっていましたが, これを指定しないと `invalid_client` エラーが出てしまいました.
:::

```ts:index_mod.ts
const { Authenticator } = require("cognito-at-edge");

const authenticator = new Authenticator({
  // Replace these parameter values with those of your own environment
  region: "ap-northeast-1", // user pool region
  userPoolId: "ap-northeast-1_XXXXXX", // user pool ID
  userPoolAppId: "XXXXXX", // user pool app client ID
  userPoolDomain: "XXXXXX" // user pool domain
  userPoolAppSecret: "XXXXXX", // 追加
});

exports.handler = async (request) => authenticator.handle(request);
```