Skip to content

Cognitoユーザープールにメール認証によるMFAを導入#1401

Open
inoue22 wants to merge 7 commits into
aws-samples:mainfrom
inoue22:feat/cognito-mfa
Open

Cognitoユーザープールにメール認証によるMFAを導入#1401
inoue22 wants to merge 7 commits into
aws-samples:mainfrom
inoue22:feat/cognito-mfa

Conversation

@inoue22
Copy link
Copy Markdown
Contributor

@inoue22 inoue22 commented Jan 3, 2026

Description of Changes

Please explain the changes in detail.
If there is any impact on existing users (compatibility, degradation, breaking changes, etc.), be sure to include it in the explanation.

CognitoユーザープールへMFA有効化を追加し、ログイン時にメールを用いたMFAを可能としました。

変更内容

  • MFA機能の実装
    • cdk.jsonに以下の設定パラメータを追加しました
      • mfaEnabled: MFA機能の有効/無効を制御(デフォルト: false)
      • mfaFromEmail: MFA認証コード送信元メールアドレス(例: no-reply@example.com
      • mfaReplyToEmail: 返信先メールアドレス(オプション)
    • パラメータを受け取るようCDKのauth.tsを改修しました

デプロイ済み環境への影響

  • mfaEnabledのデフォルト値はfalseのため、デプロイ済み環境への影響はありません
  • MFA機能を有効にする場合は、cdk.jsonmfaEnabled: true, mfaFromEmailに送信元メールアドレスをに設定し、SESの事前設定を完了させてからCDKスタックの更新が必要です

注意事項

  • MFA有効化時はパスワードリセットが制限されます

  • AWS公式ドキュメントに記載の通り、CognitoユーザープールではMFAとパスワードリセットコードを同じメールアドレス(または電話番号)で受け取ることができません

    ユーザーは、MFA とパスワードのリセットコードを、同じ E メールアドレスや電話番号で受け取ることはできません。E メールメッセージのワンタイムパスワード (OTP) を MFA に使用する場合、アカウントの復旧には SMS メッセージを使用する必要があります。

  • メールベースのMFAを有効化した場合、セルフサービスでパスワードリセットを行うにはSMS(電話番号)が必要ですが、電話番号を必須属性として追加することは既存のCognitoユーザープールへの破壊的変更となるため、本PRでは以下の対応としました

    • MFA有効化時はパスワードリセット画面(パスワードを忘れましたか?)を無効化
    • パスワードリセットが必要な場合は、管理者によるマネジメントコンソールまたはAWS CLIによる操作を想定

補足事項

SESドメイン認証

  • mfaFromEmailで指定するメールアドレスのドメインは、Amazon SESで認証済みである必要があります
    • 厳密には認証なしでもMFAの送信は可能ですが、GmailやOutlookではSPF認証が必須のためメールサービス側でMFAを受信できない問題が発生します
  • 以下のスクリプトを実行することでRoute 53に登録済みの独自ドメインに対して設定を適用できることを確認しました
Amazon SESの独自ドメイン認証設定スクリプト(注意: Route 53のDNS設定を書き換えます)
#!/bin/bash

set -e

DOMAIN="example.com"
HOSTED_ZONE_ID="XXXXXXXXXXXXXXXXXXXXX"
REGION="us-east-1"
# ---- 以下の値は変更不要 -----
MAIL_FROM_SUBDOMAIN="ses"
MAIL_FROM_DOMAIN="${MAIL_FROM_SUBDOMAIN}.${DOMAIN}"

#######################################
# 色付きの出力用
#######################################
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

#######################################
# ログ出力関数
#######################################
log_info() {
    echo -e "${GREEN}[INFO]${NC} $1"
}

log_warn() {
    echo -e "${YELLOW}[WARN]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# Route 53にレコードを追加する関数
add_route53_record() {
    local zone_id=$1
    local name=$2
    local type=$3
    local ttl=$4
    local value=$5
    local comment=$6

    log_info "Route 53にレコードを追加: $name ($type)"

    # TXTレコードの場合は値を引用符で囲む
    if [ "$type" = "TXT" ]; then
        value="\\\"${value}\\\""
    fi

    # JSONファイルを作成
    cat > /tmp/route53-change.json < /dev/null

    if [ $? -ne 0 ]; then
        log_error "✗ レコード追加失敗: $name"
        return 1
    fi
}

# MXレコードを追加する関数
add_route53_mx_record() {
    local zone_id=$1
    local name=$2
    local ttl=$3
    local priority=$4
    local value=$5

    log_info "Route 53にMXレコードを追加: $name"

    cat > /tmp/route53-change-mx.json < /dev/null

    if [ $? -ne 0 ]; then
        log_error "✗ MXレコード追加失敗: $name"
        return 1
    fi
}

# パラメータ解析
while getopts "r:h" opt; do
    case $opt in
        r) REGION="$OPTARG" ;;
        h) usage ;;
        *) usage ;;
    esac
done

log_info "=========================================="
log_info "SES Domain Authentication Setup"
log_info "=========================================="
log_info "ドメイン: $DOMAIN"
log_info "Hosted Zone ID: $HOSTED_ZONE_ID"
log_info "SESリージョン: $REGION"
log_info "カスタムMAIL FROMドメイン: $MAIL_FROM_DOMAIN"
log_info "=========================================="
echo ""

# AWS CLIの設定確認
if ! command -v aws &> /dev/null; then
    log_error "AWS CLI v2がインストールされていません"
    exit 1
fi

log_info "AWS CLI バージョン: $(aws --version)"
echo ""

#######################################
# 1. ドメイン検証用のTXTレコード
#######################################
log_info "Step 1: ドメイン検証の開始"

VERIFICATION_TOKEN=$(aws ses verify-domain-identity \
    --domain "$DOMAIN" \
    --region "$REGION" \
    --query 'VerificationToken' \
    --output text)

if [ $? -eq 0 ]; then
    log_info "  トークン: $VERIFICATION_TOKEN"

    # TXTレコードを追加
    add_route53_record "$HOSTED_ZONE_ID" \
        "_amazonses.${DOMAIN}" \
        "TXT" \
        "3600" \
        "${VERIFICATION_TOKEN}" \
        "SES Domain Verification Token"
else
    log_error "✗ ドメイン検証トークンの取得に失敗しました"
    exit 1
fi

echo ""

#######################################
# 2. DKIM設定 (CNAME x3)
#######################################
log_info "Step 2: DKIM設定"

DKIM_TOKENS=$(aws ses verify-domain-dkim \
    --domain "$DOMAIN" \
    --region "$REGION" \
    --query 'DkimTokens' \
    --output text)

if [ $? -eq 0 ]; then
    # 各DKIMトークンに対してCNAMEレコードを作成
    TOKEN_COUNT=0
    for TOKEN in $DKIM_TOKENS; do
        TOKEN_COUNT=$((TOKEN_COUNT + 1))
        log_info "  DKIMトークン $TOKEN_COUNT: $TOKEN"

        add_route53_record "$HOSTED_ZONE_ID" \
            "${TOKEN}._domainkey.${DOMAIN}" \
            "CNAME" \
            "3600" \
            "${TOKEN}.dkim.amazonses.com." \
            "SES DKIM CNAME Record ${TOKEN_COUNT}"
    done
else
    log_error "✗ DKIMトークンの取得に失敗しました"
    exit 1
fi

echo ""

#######################################
# 3. カスタムMAIL FROMドメイン設定
#######################################
log_info "Step 3: カスタムMAIL FROMドメイン設定"

# AWS SESでカスタムMAIL FROMドメインを設定
aws ses set-identity-mail-from-domain \
    --identity "$DOMAIN" \
    --mail-from-domain "$MAIL_FROM_DOMAIN" \
    --behavior-on-mx-failure UseDefaultValue \
    --region "$REGION" \
    --output json > /dev/null

if [ $? -ne 0 ]; then
    log_error "✗ カスタムMAIL FROMドメインの設定に失敗しました"
    exit 1
fi

# MXレコードを追加
add_route53_mx_record "$HOSTED_ZONE_ID" \
    "$MAIL_FROM_DOMAIN" \
    "3600" \
    "10" \
    "feedback-smtp.${REGION}.amazonses.com."

# SPFレコード (TXT) を追加
SPF_VALUE="v=spf1 include:amazonses.com ~all"
add_route53_record "$HOSTED_ZONE_ID" \
    "$MAIL_FROM_DOMAIN" \
    "TXT" \
    "3600" \
    "${SPF_VALUE}" \
    "SPF Record for Custom MAIL FROM"

echo ""

#######################################
# 4. SPF設定(メインドメイン)
#######################################
log_info "Step 4: SPF設定(メインドメイン)"

# メインドメインのSPFレコード (TXT) を追加
SPF_VALUE="v=spf1 include:amazonses.com ~all"
add_route53_record "$HOSTED_ZONE_ID" \
    "$DOMAIN" \
    "TXT" \
    "3600" \
    "${SPF_VALUE}" \
    "SPF Record for Main Domain"

echo ""

#######################################
# 5. DMARC設定 (TXT)
#######################################
log_info "Step 5: DMARC設定"

# DMARCポリシー
DMARC_VALUE="v=DMARC1; p=none"

add_route53_record "$HOSTED_ZONE_ID" \
    "_dmarc.${DOMAIN}" \
    "TXT" \
    "3600" \
    "${DMARC_VALUE}" \
    "DMARC Policy Record"

echo ""

#######################################
# 完了メッセージ
#######################################
log_info "=========================================="
log_info "セットアップ完了!"
log_info "=========================================="

# クリーンアップ
rm -f /tmp/route53-change.json /tmp/route53-change-mx.json

メールのカスタマイズの制限

  • 本PRではメールの件名・本文のカスタマイズには対応していません
  • MFA認証コードは英文のメールで送信されます(ユーザー作成時と同様)
  • aws-cdk-lib/aws-cognitoには件名・本文をカスタマイズするためのパラメータは存在するため、将来的な拡張の余地はあります

Checklist

  • Modified relevant documentation
  • Verified operation in local environment
  • Executed npm run cdk:test and if there are snapshot differences, execute npm run cdk:test:update-snapshot to update snapshots

Related Issues

Please list related issues as much as possible.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 5, 2026

This PR is stale because it has been open for 30 days with no activity.

@github-actions github-actions Bot added the stale Issues or Pull Requests with no updates label Feb 5, 2026
@inoue22
Copy link
Copy Markdown
Contributor Author

inoue22 commented Feb 5, 2026

#1433 の変更の取り込み・動作確認を実施します。

@github-actions github-actions Bot removed the stale Issues or Pull Requests with no updates label Feb 6, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 9, 2026

This PR is stale because it has been open for 30 days with no activity.

@github-actions github-actions Bot added the stale Issues or Pull Requests with no updates label Mar 9, 2026
@inoue22
Copy link
Copy Markdown
Contributor Author

inoue22 commented Mar 23, 2026

#1433 の変更の取り込み・動作確認が完了しました。

今後の開発マイルストーンに照らし合わせて、本PRで修正が必要な箇所がありましたらご連絡いただけますと幸いです。

@github-actions github-actions Bot removed the stale Issues or Pull Requests with no updates label Mar 23, 2026
Copy link
Copy Markdown
Collaborator

@maekawataiki maekawataiki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contribution ありがとうございます!レビューが遅れ失礼いたしました。
以下の点に加えて、MFA の有効化方法についてのドキュメントを追加いただけますと幸いです!

Comment thread packages/cdk/lib/stack-input.ts Outdated
samlCognitoDomainName: z.string().nullish(),
samlCognitoFederatedIdentityProviderName: z.string().nullish(),
mfaEnabled: z.boolean().default(false),
mfaFromEmail: z.string(),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MFA 無効時でも必須フィールドになっており cdk.json では書かれているので問題ないですが parameter.ts などで環境ごとに指定が必要になってしまうのでデフォルト値をここで設定するのはいかがでしょうか?

mfaFromEmail: z.string().email().default('no-reply@example.com'),

samlAuthEnabled: false,
samlCognitoDomainName: '',
samlCognitoFederatedIdentityProviderName: '',
mfaEnabled: true,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

既存環境へ影響がないことをテストする意味で、デフォルトの Snapshot は mfa 無効で mfa 有効の Snapshot Test を切り出すのはいかがでしょうか?

@inoue22
Copy link
Copy Markdown
Contributor Author

inoue22 commented Apr 23, 2026

レビューいただきありがとうございます。対応が完了しましたので連絡いたします。

ソースコードの修正

  • packages/cdk/lib/stack-input.tsの変更点: mfaFromEmailのZod定義を変更しました。MFAが無効の場合でも明示的な指定が不要になるよう修正しました。
  • packages/cdk/test/generative-ai-use-cases.test.tsの変更点: MFA有効時のテストをmatches the snapshot (mfa enabled)として別ケースに切り出しました。テストのベースをmfaEnabled: falseへ変更して既存環境への影響がないことを確認しました。

MFAの有効化方法についてのドキュメントの追加
docs/ja/DEPLOY_OPTION.md、docs/en/DEPLOY_OPTION.md、docs/ko/DEPLOY_OPTION.mdのセキュリティ関連設定セクションにMFAの有効化方法を追加しました。
なお、英語版・韓国語版のドキュメントはClaude Sonnet 4.6のみを使用して作成しました。

ご確認の程よろしくお願いいたします。

Copy link
Copy Markdown
Collaborator

@maekawataiki maekawataiki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

追加で2点ほどコメントいたしました。ご確認いただけますと幸いです。

})
: undefined;

const userPool = new UserPool(this, 'UserPool', {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cognito の Feature Plan は Essentials 以上でないと Email MFA が使えません。

  • Feature Plan 概念が導入される前から存在するアカウントでは、新規作成 User Pool のデフォルトは LITE のまま
  • 概念導入後に作成されたアカウントでは新規 User Pool のデフォルトが ESSENTIALS

という挙動の差があり、今の実装ですと古い環境ではデプロイに失敗するようです。

以下のように明示的に MFA 有効化する際は Essential 指定することで古い環境でもデプロイできました。

featurePlan: props.mfaEnabled ? FeaturePlan.ESSENTIALS : undefined,

samlCognitoDomainName: z.string().nullish(),
samlCognitoFederatedIdentityProviderName: z.string().nullish(),
mfaEnabled: z.boolean().default(false),
mfaFromEmail: z.string().email().default('no-reply@example.com'),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

すみません、こちら default 値指定したほうがいいかもしれないとコメントさせていただいたのですが、検証されたメールを設定しないとデプロイ時エラーになるため 「mfaEnabled の際は検証されたメールを明示的に入力する必要がある」を徹底するために、ここでは nullish にして refine でデプロイ前にエラーメッセージを表示する形にしたほうが良いかもしれません。

// 既存の refine に追加
.refine(
(data) => !data.mfaEnabled || !!data.mfaFromEmail,
{ message: 'mfaFromEmail is required when mfaEnabled is true', path: ['mfaFromEmail'] }
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants