AWS Cognito 既存のユーザープールから段階的に新規ユーザープールへ移行（マイグレーション）する

# 🚀 Cognito User Migration Lambda 関数の実装ガイド

Amazon Cognito を使っていると、「既存のユーザーを新しいユーザープールにどうやって移行するのか？」という課題に直面することがあります。この記事では、**初回ログイン時に旧ユーザープールから新ユーザープールへシームレスに移行する Lambda 関数**の構築方法について、詳細に解説します。

---

## 🧠 背景と目的

* Cognitoのユーザープールを再構成する必要がある
* 既存ユーザーのパスワードを保持したまま移行したい
* 移行のUXを損なわず、自動化したい

これを実現するのが、**Cognito User Migration Trigger** に対応する Lambda 関数です。

---

## 🛠 機能概要

この Lambda 関数では、次のような処理を行います。

* ✅ 旧ユーザープールで認証を実行
* 🔄 ユーザー属性（メールやカスタム属性など）を新ユーザープールに移行
* 🔒 ユーザーを「確認済み（CONFIRMED）」として登録し、確認メールは送信しない

---

## 🌍 環境変数の設定

| 変数名                | 説明                  |
| ------------------ | ------------------- |
| `OLD_USER_POOL_ID` | 移行元の旧ユーザープールID      |
| `OLD_CLIENT_ID`    | 移行元ユーザープールのクライアントID |

---

## 🔐 必要なIAMポリシー

Lambda にアタッチする IAM ポリシー例は以下の通りです：

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "logs:CreateLogGroup",
      "Resource": "arn:aws:logs:{REGION}:{ACCOUNT_ID}:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": [
        "arn:aws:logs:{REGION}:{ACCOUNT_ID}:log-group:/aws/lambda/CognitoUserMigration:*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "cognito-idp:AdminInitiateAuth",
        "cognito-idp:AdminGetUser"
      ],
      "Resource": "arn:aws:cognito-idp:{REGION}:{ACCOUNT_ID}:userpool/{OLD_USER_POOL_ID}"
    }
  ]
}
```

※ `{REGION}`, `{ACCOUNT_ID}`, `{OLD_USER_POOL_ID}` は自身の環境に応じて置き換えてください。

---

## ⚙ トリガー設定

Cognito ユーザープールのトリガーで以下を設定：

* `UserMigration_Authentication`：ユーザー認証時に発火
* `UserMigration_ForgotPassword`：パスワードリセット時（※未サポート）

---

## 🔁 認証フローの流れ

1. クライアントが `USER_PASSWORD_AUTH` でログインを試行
2. 新ユーザープールにユーザーが存在しない
3. `UserMigration_Authentication` トリガーで Lambda が起動
4. Lambda で旧ユーザープールに対し認証実行
5. 認証成功なら、新ユーザープールに属性を登録し `CONFIRMED` 状態に
6. 認証失敗なら、そのまま失敗を返す

---

## 🧪 実装例（Python）

```python
import boto3
import os

client = boto3.client("cognito-idp")

def lambda_handler(event, context):
    username = event["userName"]
    password = event["request"]["password"]

    try:
        auth_result = client.admin_initiate_auth(
            UserPoolId=os.environ["OLD_USER_POOL_ID"],
            ClientId=os.environ["OLD_CLIENT_ID"],
            AuthFlow="ADMIN_NO_SRP_AUTH",
            AuthParameters={
                "USERNAME": username,
                "PASSWORD": password
            }
        )

        # 認証成功 → 属性取得
        user = client.admin_get_user(
            UserPoolId=os.environ["OLD_USER_POOL_ID"],
            Username=username
        )

        attributes = {attr["Name"]: attr["Value"] for attr in user["UserAttributes"]}

        # 属性の移行（必要なものだけ抽出も可）
        event["response"]["userAttributes"] = {
            "email": attributes.get("email", ""),
            "email_verified": "true",
            # "custom:your_attr": attributes.get("custom:your_attr", "")
        }
        event["response"]["finalUserStatus"] = "CONFIRMED"
        event["response"]["messageAction"] = "SUPPRESS"
        event["response"]["forceAliasCreation"] = False

    except client.exceptions.NotAuthorizedException:
        raise Exception("認証失敗：パスワードが正しくありません")
    except client.exceptions.UserNotFoundException:
        raise Exception("認証失敗：旧ユーザープールにユーザーが存在しません")
    
    return event
```

---

## 🧬 移行される属性

* **基本属性**: `email`, `email_verified`
* **カスタム属性**: `custom:` プレフィックスを持つすべての属性

---

## ✅ 使用方法まとめ

1. Lambda に上記コードをデプロイ
2. 環境変数を設定
3. トリガーに `UserMigration_Authentication` を設定
4. 新ユーザープールでユーザーがログインを試みると、旧ユーザープールから自動で移行される

---

## 🧪 AWS CLIでのテスト

```bash
AWS_PROFILE=your-profile aws cognito-idp initiate-auth \
  --auth-flow USER_PASSWORD_AUTH \
  --client-id {NEW_CLIENT_ID} \
  --auth-parameters USERNAME=user@example.com,PASSWORD=your-password
```

---

## 🧯 よくあるエラーと対処

| エラー                      | 原因                   |
| ------------------------ | -------------------- |
| `NotAuthorizedException` | 認証情報が誤っている           |
| `UserNotFoundException`  | ユーザーが旧ユーザープールに存在しない  |
| `Lambdaエラー`              | IAMポリシー不足、環境変数設定ミスなど |

---

## 🔍 注意点・ベストプラクティス

* パスワードリセットはサポートしていません（2025年6月現在）
* `messageAction: "SUPPRESS"` を指定しないと確認メールが送信されます
* `event["response"]["finalUserStatus"] = "CONFIRMED"` を忘れないこと
* **移行処理は初回ログイン時のみ実行されます**
* **ログイン画面は `USER_PASSWORD_AUTH` を使う必要があります**

  > ❗**重要**：ログイン時に「ユーザーネーム＋パスワード」をクライアント（フロントエンド）から Cognito に\*\*直接渡す形式（`USER_PASSWORD_AUTH`）\*\*でなければ、User Migration Lambda は発火しません。

  たとえば以下のような形式が該当します：

  ```bash
  aws cognito-idp initiate-auth \
    --auth-flow USER_PASSWORD_AUTH \
    --client-id YOUR_CLIENT_ID \
    --auth-parameters USERNAME=your_user,PASSWORD=your_pass
  ```

  一方、`SRP`（Secure Remote Password）フローや、ホスト型UI（Hosted UI）経由の認証では、`UserMigration_Authentication` はトリガーされません。

---

## 📌 まとめ

この Lambda 関数を活用することで、Cognito ユーザーを**安全かつシームレスに移行**できます。Cognitoの標準機能とAWS SDKの併用により、シンプルかつ堅牢な移行プロセスを実現可能です。

---

## 🔗 参考リンク

* [AWS公式：User Migration Lambda Trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-user-migration.html)
* [Boto3 Documentation – Cognito](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cognito-idp.html)