既にアプリケーションを運用していてセキュリティの都合上などユーザーの認証基盤としてCognitoを利用したいことってありますよね

そんな時Cognitoにはユーザー移行の手段として2つの方法があります

CSVによる一括インポートとlambdaを利用した既存ログインシステムを利用したユーザー移行です

CSV取込とlambda取込の大きな違いは既に利用しているパスワードをそのまま利用できるか否かです

CSV取込の場合、ユーザーがログイン時にパスワードを再設定する必要がある一方、lambdaを利用したユーザー移行の場合、パスワードをそのままユーザーを移行することができます

今回はlambdaを利用したユーザー移行についての備忘録です

ユーザー移行用のlambdaの用意

今回はlambdaに利用する関数としてGoを用いた

ログイン時に実行する処理を下記に示します

package mainimport (    "github.com/aws/aws-lambda-go/events"    "github.com/aws/aws-lambda-go/lambda")func main() {    lambda.Start(func(events *events.CognitoEventUserPoolsMigrateUser) (*events.CognitoEventUserPoolsMigrateUser, error) {        // 既存基盤のログイン処理        user, err := login(events.UserName, events.Password)        if err != nil {            // ログイン失敗            return nil, err        }        // Cognitoに設定したいユーザー情報の登録        events.UserAttributes = map[string]string{            "email":          user.Email,            "email_verified": "true", // 検証済みのメールかどうか        }        // ユーザーのステータス        // パスワードの再設定が必要な場合はRESET_REQUIREDにする        events.FinalUserStatus = "CONFIRMED"        // ユーザーインポート後のアクション        // デフォルトだとメールが送られるが、SURPRESSの場合何もしない        events.MessageAction = "SUPPRESS"        return events, nil    })}

また上記lambdaをsamでデプロイするための設定は下記の通り

注意する部分としてはlambdaを起動するための起動するための権限をCognitoに与えなきゃいけない点

AWSTemplateFormatVersion: '2010-09-09'Transform: AWS::Serverless-2016-10-31Description: user_migrationGlobals:  Function:    Timeout: 5    Environment:      Variables:        TZ: Asia/TokyoResources:  UserMigration:    Type: AWS::Serverless::Function    Properties:      FunctionName: UserMigration      CodeUri: ../../dist/event      Handler: user-migration      Runtime: go1.x      Timeout: 300      Description: Cognitoへのユーザー移行用の処理      Policies: [ AWSLambdaVPCAccessExecutionRole ]      Role: lambda-user-migration-role  InvokePermission:    Type: AWS::Lambda::Permission    Properties:      FunctionName: !GetAtt UserMigration.Arn      Action: lambda:InvokeFunction      Principal: cognito-idp.amazonaws.com      SourceArn: arn:aws:cognito-idp:ap-northeast-1:1234567890:userpool/ap-northeast-1_XXXXXXXX

cognito上の設定

実際にログインに使用するcognitoの設定部分に関しては下記の通りである

ここではログインに必要なIDをemailとしている

# ユーザープール
resource "aws_cognito_user_pool" "example" {
  name = "userpool"

  # ログインはemail or 電話番号
  username_attributes = ["email"]
  username_configuration {
    # ユーザー名(Email)で大文字小文字を区別しない。
    case_sensitive = false
  }

  # 「schema」は登録するユーザーに求める属性。(メールアドレスや電話番号など)
  # 「email」はデフォルトで有効になっている属性だが、今回は登録時に必須にしたいため設定。
  schema {
    attribute_data_type = "String"
    developer_only_attribute = false
    mutable = false
    name     = "email"
    required = true

    string_attribute_constraints {
      max_length = "2048"
      min_length = "0"
    }
  }

  # lambdaトリガーの設定
  lambda_config {
    user_migration = "arn:aws:lambda:ap-northeast-1:1234567890:function:UserMigration"
  }
}

# ユーザープールを利用するクライアント
resource "aws_cognito_user_pool_client" "example" {
  name = "userpool-client"

  user_pool_id = aws_cognito_user_pool.example.id

  // cognitoの認証方法の設定
  // lambdaを利用したユーザー移行をする場合、ALLOW_USER_PASSWORD_AUTHを指定する必要あり
  explicit_auth_flows = [
    "ALLOW_REFRESH_TOKEN_AUTH",
    "ALLOW_USER_PASSWORD_AUTH",
  ]

  // cognitoのUIを利用したログインを可能にする
  supported_identity_providers = ["COGNITO"]
  allowed_oauth_flows = ["code", "implicit"]
  allowed_oauth_flows_user_pool_client = true
  allowed_oauth_scopes = ["email", "openid"]
  
  // ログイン・ログアウト時のコールバック先のURL
  callback_urls = ["http://localhost:3000"]
  logout_urls = ["http://localhost:3000"]
}

# cognitが提供するUIを利用するためにドメインの設定
resource "aws_cognito_user_pool_domain" "main" {
  domain       = "example"
  user_pool_id = aws_cognito_user_pool.example.id
}

上記の設定を反映させたらcognitoのUIを開く

cognitoのUIが開かれれば、既存のアプリで利用していたID/PWを入力してSign inをする

cognitoのユーザーディレクトリに対象のユーザーが存在すれば、その中でログイン判定を行い、存在しないユーザーだとlambdaが起動される

lambdaの引数にID/PWが載ってくるので、それを利用し既存システムでログインを試みる

ログインに成功したユーザーの場合、必要な情報をセットすることでcognito上でユーザーが生成される

ハマったポイント

events.MessageActionSUPPRESS を指定していない場合、ユーザーに対して初回ログインを知らせるメールが送信される

ただ、Cognito上でSESなどメールの設定をしていないとログインに失敗してしまう

lambdaを起動するcognitoのログがどこにも出力され無かったので、ログイン処理が成功していてもユーザー移行に失敗する問題が発生し、原因を突き詰めるのに非常に時間がかかった

cognitoのlambda処理の失敗理由をどこかにログとして吐いてくれるともっと嬉しいなと思った...

まとめ

lambda(go)を利用したcognitoのユーザー移行の処理をまとめました

WEB上に関連する情報がほとんどなくて困ったので、ここにまとめる

誰かのためになれば幸いです