CognitoのユーザーをTerraformで事前定義する
TerraformでCognitoユーザーを登録し、初期パスワードを設定する方法を解説。 Pythonでパスワード変更とサインインを行う具体的な手順を紹介します。
目次
はじめに
- 一番簡単なCognitoの例を試すでCognitoを使ってユーザー登録から、アドレス認証、サインインまでをPythonベースで行った
- 今回はもう少し具体的な例で、Terraformでユーザーのメールアドレスと初期パスワードを設定する。そのあとにパスワードの変更と、サインインまでを同じくPythonベースで行う
- 前回との違いは以下の通り。今回は運用側で決められたユーザーに対してサービス提供するということを想定したケース
前回 | 今回 | |
---|---|---|
ユーザー登録 | ユーザー側(signup.py) | 運用側(Terraform) |
メールアドレス認証 | ユーザー側(confirn_user.py) | 認証済みと自動設定(Terraform) |
パスワード変更 | ☓ | ユーザー側(password_reset.py) |
サインイン | ユーザー側(signin.py) | ユーザー側(signin.py) |
- 今回の流れは以下の通り
- Terraformでユーザーと初期パスワードを事前定義しておく(メールアドレスとパスワードの組み合わせ)
terraform apply
するとそのユーザーに対して初期パスワードのメール送信が走る(AWSで自動的に行なってくれる)- その初期パスワードを使って、ユーザーがパスワード変更を行う(password_reset.py)。初期パスワード、新しいパスワードを入力して変更する。このパスワード変更をしないと次のサインインには進めない
- パスワードを変更後、サインインを行う(signin.py)。これによりアクセスキーなどの一時認証情報が払い出される。
ディレクトリ構成
├── main.tf # メインのTerraform設定ファイル
├── cognito.tf # AWS Cognito関連のTerraform設定ファイル
├── change_password.py # パスワード変更処理を行うPythonスクリプト
└── signin.py # サインイン処理を行うPythonスクリプト
Terraform部分
main.tf
で以下のようにユーザー定義しておく。プロジェクトが大きくなったらterraform.tfvars
で行うのがいいだろう。初期パスワードハードコーディングもセキュリティ的にはよろしくなく、random_password
などを使うのが(参考)良いが、今回は説明用にハードコードした例で簡略化して説明する。
variable "users" {
description = "Cognitoユーザープールに追加するユーザーのマップ。キーがメールアドレス、値が一時パスワード"
type = map(string)
default = {
"user1@example.com" = "TemporaryPassword1!" # 実際にメールが届くアドレスを設定
"user2@example.com" = "TemporaryPassword2!"
}
}
ユーザープールを以下のように定義する(cognito.tf
)
# Cognitoユーザープールの作成
resource "aws_cognito_user_pool" "user_pool" {
name = "my-first-user-pool"
# パスワードポリシーの設定(必要に応じて調整)
password_policy {
minimum_length = 8
require_uppercase = true
require_lowercase = true
require_numbers = true
require_symbols = true
temporary_password_validity_days = 30
}
account_recovery_setting {
recovery_mechanism {
name = "verified_email"
priority = 1
}
}
}
# ユーザープールクライアントの作成
resource "aws_cognito_user_pool_client" "user_pool_client" {
name = "my-first-user-pool-client"
user_pool_id = aws_cognito_user_pool.user_pool.id
# 必要に応じて他のパラメータを設定
explicit_auth_flows = [
"ALLOW_USER_PASSWORD_AUTH",
"ALLOW_REFRESH_TOKEN_AUTH"
]
# リフレッシュトークンの有効期間(秒)
refresh_token_validity = 30
# クライアントシークレットの無効化(シンプルな例のため)
generate_secret = false
prevent_user_existence_errors = "ENABLED"
}
# users.tf
resource "aws_cognito_user" "users" {
for_each = var.users
user_pool_id = aws_cognito_user_pool.user_pool.id
username = each.key
attributes = {
email = each.key
email_verified = "true" # メールを自動的に検証済みとして設定
}
temporary_password = each.value
}
# 出力変数の定義
output "user_pool_id" {
description = "Cognito User Pool ID"
value = aws_cognito_user_pool.user_pool.id
}
output "user_pool_client_id" {
description = "Cognito User Pool Client ID"
value = aws_cognito_user_pool_client.user_pool_client.id
}
新しい部分はaws_cognito_user.users
で、ここでユーザーをTerraform側で定義する。また、email_varified
をtrue
にすると、自動的にメールアドレスが認証済みとして扱われる。
実際にterraform apply
を行うと、以下のように対象のメールアドレスに対して初期パスワードが通知される。
同様にユーザープールのIDとユーザープールのクライアントIDが出てくるのでメモする。
user_pool_client_id = "1example23456789"
user_pool_id = "ap-northeast-1_ExaMPle"
Pythonコード部分
パスワード変更(change_password.py)
初期パスワードで後述のサインインを行おうとすると以下のようなエラーが出るはずだ。強制的にパスワードリセットの状態になっている。
{‘ChallengeName’: ‘NEW_PASSWORD_REQUIRED’, ‘Session’: …
# change_password.py
import boto3
from botocore.exceptions import ClientError
CLIENT_ID = 'YOUR_APP_CLIENT_ID' # Terraformの出力から取得
PROFILE_NAME = 'hogehoge'
session = boto3.Session(profile_name=PROFILE_NAME)
client = session.client('cognito-idp')
def change_password(username, temporary_password, new_password):
try:
# 初回サインイン時に認証を開始
response = client.initiate_auth(
ClientId=CLIENT_ID,
AuthFlow='USER_PASSWORD_AUTH',
AuthParameters={
'USERNAME': username,
'PASSWORD': temporary_password
},
)
# 新しいパスワードが要求されるチャレンジに対応
if response.get('ChallengeName') == 'NEW_PASSWORD_REQUIRED':
response = client.respond_to_auth_challenge(
ClientId=CLIENT_ID,
ChallengeName='NEW_PASSWORD_REQUIRED',
Session=response['Session'],
ChallengeResponses={
'USERNAME': username,
'NEW_PASSWORD': new_password
}
)
print("パスワード変更が成功しました。")
else:
print("予期しない認証フローの応答がありました。")
except ClientError as e:
print(f"パスワード変更エラー: {e.response['Error']['Message']}")
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Cognitoパスワード変更スクリプト")
parser.add_argument('username', type=str, help='ユーザー名(メールアドレス)')
parser.add_argument('temporary_password', type=str, help='一時パスワード')
parser.add_argument('new_password', type=str, help='新しいパスワード')
args = parser.parse_args()
change_password(args.username, args.temporary_password, args.new_password)
パスワードの変更は以下のようにする。「パスワード変更が成功しました。」と表示されるはずだ。
python change_password.py user1@example.com TemporaryPassword1! NewSecurePassword1!
サインイン(signin.py)
あとは同様にサインインする。
# signin.py
import boto3
from botocore.exceptions import ClientError
# Terraformで出力された値を使用
CLIENT_ID = 'YOUR_APP_CLIENT_ID' # 例: '1example23456789'
PROFILE_NAME = 'hogehoge'
session = boto3.Session(profile_name=PROFILE_NAME)
client = session.client('cognito-idp')
def sign_in(username, password):
try:
response = client.initiate_auth(
ClientId=CLIENT_ID,
AuthFlow='USER_PASSWORD_AUTH',
AuthParameters={
'USERNAME': username,
'PASSWORD': password
},
)
print(response)
id_token = response['AuthenticationResult']['IdToken']
access_token = response['AuthenticationResult']['AccessToken']
refresh_token = response['AuthenticationResult']['RefreshToken']
print("サインイン成功。")
print(f"IDトークン: {id_token}")
print(f"アクセストークン: {access_token}")
print(f"リフレッシュトークン: {refresh_token}")
except ClientError as e:
error = e.response['Error']['Code']
if error == 'NotAuthorizedException':
print("パスワードが正しくありません。")
elif error == 'UserNotFoundException':
print("ユーザーが存在しません。")
elif error == 'PasswordResetRequiredException':
print("パスワードのリセットが必要です。")
else:
print(f"サインインエラー: {e.response['Error']['Message']}")
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Cognitoサインインスクリプト")
parser.add_argument('username', type=str, help='ユーザー名(メールアドレス)')
parser.add_argument('password', type=str, help='パスワード')
args = parser.parse_args()
sign_in(args.username, args.password)
サインインコマンドは以下の通り。
python signin.py user1@example.com NewSecurePassword1!
成功すると以下のように認証情報が払い出される。
{'ChallengeParameters': {}, 'AuthenticationResult': {'AccessToken': 'eyJraWQiOiJyK2c3QUUzY.....', 'IdToken': 'eyJraWQiOiJTTkV1dE......'}, 'ResponseMetadata': {'RequestId': 'd0ba2c6d-ae3e-4f41-b9cb-1064de71c87e', 'HTTPStatusCode': 200, 'HTTPHeaders': {'date': 'Wed, 18 Dec 2024 04:32:17 GMT', 'content-type': 'application/x-amz-json-1.1', 'content-length': '4078', 'connection': 'keep-alive', 'x-amzn-requestid': 'd0ba2c6d-ae3e-4f41-b9cb-1064de71c87e'}, 'RetryAttempts': 0}}
サインイン成功。
おわりに
- なんかCognitoのことをわかってきたので、次はALBとの統合あたりを試してみたい
Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内
技術書コーナー
北海道の駅巡りコーナー