224 Views
September 29, 25
スライド概要
2025年9月29日に開催した社内イベント「AWSランチセッション第10回」の発表資料です。
閉域からのCognito認証と、SESを使用したEメールによるMFAについて、要点を分かりやすく解説しています。
インフラと密接に関係するクライアントアプリについても、2通りの実装方法をコードを交えて解説しています。
また、SESの本稼働に必要となる考慮点やアーキテクチャもあわせて解説しています。
これを機に、意外と需要が高い閉域でのユーザー認証を覗いてみませんか?
SFとコンピュータが好き
2025-09-29 AWSランチセッション 第10回 AWS 閉域からのCognito認証とSESによるMFAの勘所 山崎 拓也
山崎 拓也 所属: SIer 仕事: • AWS案件のアプリやインフラのリード • 社内AWSサポート 好き: 低レイヤ、SF、AWS AWSアワード: • 2024~2025 Japan AWS Top Engineer • 2022~2025 Japan All AWS Certifications Engineer
アジェンダ • はじめに • 閉域からのCognito認証 • SESを使ったEメールによるMFA • まとめ • SES本稼働にむけて
はじめに
AWS Cloudからインターネットに出られない状況を「閉域」とする • Direct ConnectやSite-to-Site VPNなどからアクセスする場合、 クライアントはインターネットにアクセスできないものとする
今回は便宜上、閉域のEC2にてWebサーバーやWebアプリを扱う • 必要なパッケージを持ったAMIを作成し、閉域環境にてインスタンス作成 • ブラウザが必要なためWindows Serverを用いる • フレームワークはNext.jsを用いる • アプリの操作はSSMから行う
閉域からのCognito認証
閉域からAWSサービスへのアクセスにはVPCエンドポイントを使う
閉域からCognitoでユーザー認証したいけど・・・
CognitoはVPCエンドポイントをサポートしていない • ユーザー認証にはCognitoのAPIが呼び出せればいい・・・
API GatewayでCognitoエンドポイントをプロキシできる • 通信がAWSのネットワークから出ない
API Gatewayの設定 • HTTP統合でCognitoユーザープールのエンドポイントへ転送する Amazon Cognito エンドポイントとクォータ: https://docs.aws.amazon.com/ja_jp/general/latest/gr/cognito.html
閉域からはCognitoマネージドログインページに到達できない • アプリ側でログイン周りの実装が必要となる
実装方法は2通り 1. AWS SDKを使用する • Cognito API単位での操作を自由に実装できる 2. AWS Amplifyライブラリを使用する • API呼び出しがラップされるため実装が簡単 • 認証情報の取り回しも楽 • おすすめ
ユーザー認証に関わるCognito API No. 操作 呼び出すAPI 1 サインアップ SignUp 2 サインアップ時のメールアドレス検証 ConfirmSignUp 3 サインイン InitiateAuth 4 認証情報のリフレッシュ InitiateAuth (リフレッシュトークンを送る) 5 サインアウト GlobalSignOut 各APIをAPI Gatewayに向けてリクエストする 同じAPI
AWS SDKを使用したサインインの実装例
❶
// Cognitoクライアント作成
const client = new CognitoIdentityProviderClient({
region: 'ap-northeast-1',
endpoint: '<API GatewayのURL>'
});
❷
// Cognito APIのコマンド作成
const command = new InitiateAuthCommand({
AuthFlow: 'USER_PASSWORD_AUTH',
ClientId: '<CognitoのクライアントID>',
AuthParameters: {
USERNAME: email,
PASSWORD: password
}
});
❸
// API呼び出し
const result = await client.send(command);
InitiateAuth APIはサインイン用のAPI
MFA無効時はそのままトークンが返る
• AccessToken
• IdToken
• RefreshToken
AWS Amplifyライブラリを使用したサインインの実装例
❶
import { Amplify } from 'aws-amplify';
import { signIn, fetchAuthSession } from 'aws-amplify/auth';
Amplify.configure({
Auth: {
Cognito: {
userPoolId: '<CognitoのユーザープールID>',
userPoolClientId: '<CognitoのクライアントID>',
userPoolEndpoint: '<API GatewayのURL>'
}
}
});
❷
// サインイン
await signIn({ username: email, password });
❸
// トークン取得
const session = await fetchAuthSession();
セッションから以下トークンが取得できる
• AccessToken
• IdToken
• リフレッシュトークンに触ることはできない
•
fetchAuthSession()
•
fetchAuthSession({ forceRefresh: true })
で必要に応じて自動的にリフレッシュされる
で強制的にリフレッシュも可能
SPAで直接Cognito APIを呼び出す場合はCORSの設定が必要 • Amplifyライブラリを使用する場合はこちらになる • 必要となるAccess-Control-Allow-Headersは以下(トライ&エラーで絞り込みました) • 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token, X-Amz-User-Agent,User-Agent,cache-control,amz-sdk-request, amz-sdk-invocation-id,X-Amz-Target' • '*'でも大丈夫
API GatewayのOPTIONSメソッドの設定 Amplifyライブラリが リクエストに使用する ヘッダーを許可する
APIサーバーを置ける場合はCORSの設定が不要 • APIサーバーからAPI Gatewayへリバースプロキシする • Next.jsの場合、Cognito SDKを使ってAPI RoutesでAPIを実装する
SESを使ったEメールによるMFA (SES: Amazon Simple Email Service)
CognitoのEメールによるMFAには、SESの利用が必須 • CognitoのデフォルトのEメール送信は利用できない
SESはドメインをIDとして検証することでメール送信可能になる • Route53管理下のドメインなら、自動でレコード追加されるため設定が簡単 • Route53以外の場合、ドメイン検証に「_amazonses.<domain>」の TXT レ コードが必要 • DKIMやSPF、DMARCなどの設定は「Eメールの認証方法」*を参照 ID: example.com * Eメールの認証方法: https://docs.aws.amazon.com/ja_jp/ses/latest/dg/send-email-authentication-spf.html
ではSESでドメインを検証すればCognitoのEメールMFAが可能? • それだけではできない ID: example.com
Cognitoの送信元メールアドレスは、SESで検証済みである必要がある • ドメインではなく、送信に使用するメールアドレス自体がSESでIDとして 検証済みである必要がある Cognitoの設定画面 検証済みメールアドレスを選択する SESの設定画面 メールアドレスをIDとして登録 するため、検証が必要
SESでのメールアドレス検証には、メールを受信できる必要がある • メールアドレス検証時に検証用メールが送信される • メール内のリンクにアクセスする必要がある * Eメールの認証方法: https://docs.aws.amazon.com/ja_jp/ses/latest/dg/send-email-authentication-spf.html
SESでもメールを受信できる • 検証済みドメインにMXレコードを追加する • MXレコードの値は「10 inbound-smtp.region.amazonaws.com」 • 受信したメールはS3などに配信できる 検証済みドメイン宛て [email protected] Amazon SES E メール受信の設定: https://docs.aws.amazon.com/ja_jp/ses/latest/dg/receiving-email-setting-up.html
ようやくCognitoでのEメールMFAが設定できる
Cognito認証のEメールによるMFA設定手順の整理 1. MFAコード送信元メールアドレスのドメインをSESにIDとして登録し、検 証する 2. SESでMFAコード送信元メールアドレスへのメールを受信可能にする (MXレコードの設定も) 3. MFAコード送信元メールアドレスをSESにIDとして登録し、検証する 4. Cognitoの送信元Eメールアドレスに、 MFAコード送信元メールアドレス を設定 5. CognitoのMFA設定からEメールを選択
クライアントアプリもMFA認証に対応させる No. 操作 呼び出すAPI 1 サインアップ SignUp 2 サインアップ時のメールアドレス検証 ConfirmSignUp 3 サインイン InitiateAuth (MFA有効化後は、チャレンジ名とセッションが返る) 4 認証情報のリフレッシュ InitiateAuth (リフレッシュトークンを送る) 5 サインアウト GlobalSignOut 6 認証チャレンジへの応答 RespondToAuthChallenge (MFAコードを送る) (認証トークンが返る) • サインイン時、InitiateAuth API からチャレンジ名とセッションが返る • チャレンジ応答として、RespondToAuthChallenge APIでセッションとメー ルで受信したコードを送る
サインイン時のAPIとアプリ画面の遷移 InitiateAuth API RespondToAuthChallenge API
AWS SDKを使用したサインインの実装例(MFA版)
❶
// InitiateAuth API(サインイン)
const init_auth_cmd = new InitiateAuthCommand({
AuthFlow: 'USER_PASSWORD_AUTH',
ClientId: '<CognitoのクライアントID>',
AuthParameters: {
USERNAME: email,
PASSWORD: password
}
});
const init_auth_res = await client.send(init_auth_cmd);
❷
// RespondToAuthChallenge API(チャレンジ応答)
const challenge_cmd = new RespondToAuthChallengeCommand({
ClientId: '<CognitoのクライアントID>',
ChallengeName: init_auth_res.ChallengeName,
Session: init_auth_res.Session,
ChallengeResponses: {
EMAIL_OTP_CODE: '<メールで受信したコード>',
USERNAME: email
}
});
const challenge_res = await client.send(challenge_cmd);
EメールMFAのチャレンジ名は
• ChallengeName: EMAIL_OTP
トークンが返る
• AccessToken
• IdToken
• RefreshToken
AWS Amplifyライブラリを使用したサインインの実装例(MFA版)
import { Amplify } from 'aws-amplify';
import { signIn, fetchAuthSession, confirmSignIn } from 'aws-amplify/auth';
Amplify.configure({
Auth: {
Cognito: {
userPoolId: '<CognitoのユーザープールID>',
userPoolClientId: '<CognitoのクライアントID>',
userPoolEndpoint: '<API GatewayのURL>'
}
}
});
❶
// サインイン
const res = await signIn({ username: email, password });
❷
if (res.nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE') {
// チャレンジ応答
await confirmSignIn({ challengeResponse: '<メールで受信したコード>' });
❸
// トークン取得
const session = await fetchAuthSession();
}
セッションからトークン取得
• AccessToken
• IdToken
Cognito MFAでカスタム可能な要素 • MFAコードのタイムアウト時間 • タイムアウトした場合 RespondToAuthChallenge API にて エラーとなる • 「Cognito アプリケーションクライアント > 認証フローセッションの持続期間」から設定可能 • MFAコードのメール内容 • 件名とメッセージをカスタム可能 • 「{####}」部分がMFAコードに置き換わる
作成したクライアントアプリの実装はGitHubに公開しています • https://github.com/kkmtyyz/aws-cognito-client-sample • アプリの実装は2つ • Cognito SDKを使用した実装: /apps/using-cognito-sdk • Amplifyライブラリを使用した実装: /apps/using-amplify-lib
まとめ • CognitoのエンドポイントはAPI Gatewayでプロキシできる • クライアントにAmplifyライブラリを使う場合、CORS設定が必要 • Cognito SDKを使ってAPIサーバーでプロキシする場合、CORS設定は不要 • CognitoのEメールMFAにはSESの利用が必須 • 送信元メールアドレスは、受信可能かつSESで検証済みである必要がある • 次ページから「SES本稼働にむけて」
SES本稼働にむけて
SESにはサンドボックス環境と本番環境がある • デフォルトではサンドボックス環境として機能が制限されている • 例えば、 送信先は検証済みメールアドレス or ドメインのみ 24時間あたり最大200メッセージのみ送信可能 • 本番環境への移行には本稼働リクエストを行う必要がある 本稼働アクセスのリクエスト (Amazon SES サンドボックスからの移行): https://docs.aws.amazon.com/ja_jp/ses/latest/dg/request-production-access.html
SESコンソールから本稼働リクエストが行える • リクエストを送信するとAWSサポートにサポートケースが作成され、 審査に必要な情報を返信するようメッセージが来る
SES本稼働リクエストの審査でサポートから求められる内容 • 「メール送信プロセスや手順について、可能な限り詳しい情報を記載してく ださい」 • メールを送信する頻度 • 受信者リストのメンテナンス方法 • バウンス、申し立て、解除申請の管理方法 • 送信する予定のメールのサンプル • 返信後、早ければ24時間以内に承認される • バウンス、申し立ては以下ドキュメントを一読しておくとよい Amazon SES における E メール配信可能性の概要: https://docs.aws.amazon.com/ja_jp/ses/latest/dg/send-email-concepts-deliverability.html
バウンス、申し立て、解除申請の対策は自前でも行う • バウンス対策、申し立て対策、ユーザーからの配信解除申請の対応はSESの 機能で実現できる • アカウントレベルのサプレッションリスト • 配信解除リンクの自動埋め込み、それを受けた配信停止 • しかし、柔軟な運用や、送信先アドレス管理のためにも自前でも仕組みを作 るのがおすすめ
アカウントレベルのサプレッションリスト • バウンスや申し立てとなったアドレスは自動的に追加される • アドレスを手動で追加・削除することが可能 • サプレッションリストのアドレスへ送信されるメールはSESによりブロック される [email protected]にメール送信 サプレッションリストにあるからダメ!
配信解除リンクの自動埋め込み、それを受けた配信停止(準備) • 設定セットを作成するとバウンス・申し立て・配信解 除申請などのイベントをSNSへ配信できる • 配信解除したアドレスを保存するため、 空のContactListを作成する • ContactListはコンソールから触れない点に注意 $ aws sesv2 create-contact-list --contact-list-name "MyContactList"
配信解除リンクの自動埋め込み、それを受けた配信停止(送信)
• 本文の {{amazonSESUnsubscribeUrl}} が配信停止URLに置き換わる
• 設定セットとContactListを指定して送信する
sesv2 = boto3.client('sesv2', region_name='ap-northeast-1')
# メール送信
response = sesv2.send_email(
FromEmailAddress='[email protected]', # 送信元
Destination={ 'ToAddresses': ['[email protected]'] }, # 送信先
Content={
'Simple': {
'Subject': { 'Data': '配信停止リンク付きメール' }, # 件名
'Body': {
'Html': {
'Data': '<a href="{{amazonSESUnsubscribeUrl}}">配信停止はこちら</a>'
}
}
}
},
ConfigurationSetName='MyConfigurationSet', # 設定セット
ListManagementOptions={ 'ContactListName': 'MyContactList', } # ContentList
)
受信した配信解除リンク付きメールと、配信解除ページ 配信解除ページ (AWSがホストしてるWebページ) 配信解除リンク
配信解除するとContactListに登録される
• 以降、このアドレスへはメール送信不可となる
$ aws sesv2 list-contacts --contact-list-name MyContactList
{
"Contacts": [
{
"EmailAddress": "[email protected]",
"UnsubscribeAll": true,
"LastUpdatedTimestamp": "2025-09-28T05:58:12.895000+09:00"
}
]
}
$
送信先アドレス管理のアーキテクチャ • DynamoDBで送信先アドレスを管理する • バウンス、申し立て、解除申請などのイベント時にLambda関数で管理テー ブルを更新する • メール送信前に管理テーブルで送信先の状態を確認する
SESコンソールからバウンスや申し立てのテストが可能
ご清聴ありがとうございました。