aws cdkでcognitoのユーザープール(NextAuth向け)を作成する

# 動機

[以前書いた記事](https://qiita.com/junkor-1011/items/035f41aae0c25bad4483)で、[NextAuth](https://next-auth.js.org/)の検証のためにCognitoのユーザープールを作成していた。が、その時は手動で作成しており、色々と面倒だと感じていた。

そこで、cdkを使ってIaC的に、また、試行錯誤しやすい形で作ろうという動機で調査を行った。

# 前提条件

- 検証環境:
    - Ubuntu 22.04LTS(AMD64)
    - cdk: version 2.34.2(新しいv2系を使用)
        * TypeScriptで記述
    - nodejs: v16.16.0
- AWS環境: リージョンはap-northeast-1を使用

# 検証

## プロジェクト作成

詳細はAWSの[Developer Guide](https://docs.aws.amazon.com/cdk/v2/guide/hello_world.html)を参照のこと。
Getting Startの部分にTypeScriptでのプロジェクトの立ち上げ方の例がある。

今回だと、確か

```sh
mkdir test-app && cd test-app
cdk init app --language typescript
```

のような感じで立ち上げている。


## 処理の記述

`bin/test-app.ts`(※`test-app`部分は、以降も同様だが作成環境次第で変化する)については、例えば以下のような感じ:

```typescript:bin/test-app.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { TestAppStack } from '../lib/test-app-stack';

const app = new cdk.App();

new TestAppStack(app, 'TestCognitoAppStack', {
});
```

- Stack作成クラスコンストラクタの第2引数はスタック名を指しており、
  今回だと、CloudFormationの画面から`TestCognitoAppStack`というスタックが作成されることが確認できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680173/4a43eca7-9b21-561a-463f-ea668c021761.png)
（↑実際には後の手順で`cdk deploy`した後に作成される）

一番肝心なcognitoのリソースを作成する部分は`lib/test-app-stack.ts`で記述している。
作り方にはバリエーションがあるが、[以前書いた記事](https://qiita.com/junkor-1011/items/035f41aae0c25bad4483)に適用できるようにする（NextAuthと連携できるようにする）例としては、

- OpenID Connectに対応する
- アプリクライアントを作成する
- アプリクライアントのシークレットを設定する
- ドメインを設定し、ホストされたUIを使えるようにする

あたりの設定を行う必要がある。（この辺はユースケースに応じて、[API reference](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cognito-readme.html)を参考に適宜設定・調整すれば良い）

とりあえず今回のケースでは以下のような感じでstack用のコードを作成した。

```typescript:lib/test-app-stack.ts
import {
  Duration,
  Stack,
  StackProps,
  aws_cognito as cognito,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class TestAppStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const pool = new cognito.UserPool(this, 'Pool');
    pool.addClient('app-client', {
      oAuth: {
        flows: {
          authorizationCodeGrant: true,
        },
        scopes: [cognito.OAuthScope.OPENID],
        callbackUrls: ['http://localhost:3000/api/auth/callback/cognito'], // TMP ローカル開発時においてNextAuthと連携するためのTemporaryな設定
      },
      generateSecret: true,
      refreshTokenValidity: Duration.hours(12), // トークンの有効期限を12hにしている(デフォルト: 30日)
    });
    pool.addDomain('CognitoDomain', {
      cognitoDomain: {
        domainPrefix: 'custom-domain-prefix-xxxxxx', // TMP 実際はパラメータストアから値を読み出すなど動的に設定できるようにする
      },
    });
  }
}
```

なお、ちゃんとした形で作成する際は少なくとも以下2点は直す必要がある:

- `callbackUrls`: `localhost:3000`を含んでおり、ローカルで開発・検証をしているときしか現状では動かないようになっているので、実行環境に合わせて要修正・追加
- ドメインの`domainPrefix`に関して、上記のコードだとハードコードしている感じになっているが、実際はパラメータストアから値を読むなどして動的に設定出来る工夫が必要だと考えられる：
    * ハードコードしていると、ドメイン名が既に作成済みのものと衝突し、作成に失敗して困るケースが考えられる。
    * 特に、Pipelinesの複数ステージや、あるいは複数アカウントなどで共通して使おうとすると確実に衝突が起きると思われる。


## デプロイ

```sh
npm ci # npmをパッケージマネージャーに使う場合
cdk synth
cdk deploy --profile <使用するプロファイル名>
```

のようにしてスタックの作成とデプロイが出来る

作成例について、主要なものを抜粋

<details><summary>
（スクリーンショット）
</summary><div>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680173/3003da30-fd24-9a6c-5c23-3e6e35cdbf7f.png)
デフォルトだと管理者のみがユーザーの作成が出来る（ユースケースに応じて適宜調整する）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680173/ad0ca119-5e84-f620-8dcb-6bc371d57aa4.png)
属性はデフォルトではチェックがほぼ入らない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680173/c81b7648-e01a-8a00-d1a7-2bfd7a9d5c58.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680173/41dc270e-e2d6-081c-f878-e48cd2c1a8e9.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680173/41a3646e-21f0-856e-ed9d-410cd9247c0a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680173/eb528085-dbef-e656-761a-3f05347ffb95.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680173/3a4d1ddf-2bd0-20ef-70f6-233d57c7f427.png)

</div></details>

[以前書いた記事](https://qiita.com/junkor-1011/items/035f41aae0c25bad4483)で作ったユーザープールと全く設定という訳ではないが、このくらいまで設定しておくと同じような形で使うことが出来る。
あとはユースケースに応じて適宜調整して再度`cdk deploy`すれば、設定の変更や他のケースへの対応が出来る。

NextAuthなどでの実行例は[以前書いた記事](https://qiita.com/junkor-1011/items/035f41aae0c25bad4483)と同じなので省略。

## クリーンアップ

例えば
```sh
cdk destroy --all --profile <プロファイル名>
```

で消せる。
が、Stackが削除されてもユーザープールは残る様子？なのでこれは手動で消さないと行けないかもしれない。
（なお、アプリクライアントやドメインは消える）

# 感想

（細かい設定をしなければ）思ったよりも少ない行数で記述出来たので便利

また、cdkは全般的に公式リファレンスの例が充実している印象で、典型的なケースではexampleのコピペ＋αでもそこそこ作れそうな感じで良い。