はじめに
メールアドレス + パスワードでサインインしている既存Cognitoユーザーに対しMFA(多要素認証)を適用するフローを記載します。
前提
・Cognito ユーザープール
emailをエイリアス属性として設定
emailが検証済みのユーザーが既存の状態
・Node v20.9.0
・AWS SDK for JavaScript v3
Cognitoユーザープールの設定変更
CognitoでMFAを使用可能にする為にマネージメントコンソールで設定していきます。
・MFAを有効化
サインインエクスペリエンス タブで多要素認証を編集します。MFAの強制でオプションを選択し、MFAの方法でSMSメッセージにチェックを入れます。
MFAの強制は必須にもできますが、ユーザーがSMSを受信することが義務付けられる形になります。ユーザビリティなども考慮し、今回はユーザーにMFAを適用する・しないを切り替えられるようにオプションを使用します。
・SMSの設定
メッセージング タブでSMSを編集します。IAMロールは新規作成を選択し、作成するIAMロール名を入力します。
SMSメッセージはAmazon SNSを使用して送信される為 その実行許可を持つロールが必要になります。新規作成を選択すれば必要なポリシーを持つロールを自動で生成してくれます。
CognitoユーザーのMFAを有効化
まずはユーザーに電話番号 属性を追加します。
※電話番号の値は国コード + 電話番号。日本の国コードは「+81」、電話番号の先頭の0は省略。
※メールアドレス + パスワードでサインインしてアクセストークンを取得済みとします。
01
02
03
04
05
06
07
08
09
10
11
12
13
|
import { CognitoIdentityProvider, UpdateUserAttributesCommand } from '@aws-sdk/client-cognito-identity-provider'; const command = new UpdateUserAttributesCommand({ UserAttributes: [ { Name: 'phone_number', Value: '+819012345678' } ], AccessToken: 'サインインで取得したアクセストークン' }); await new CognitoIdentityProvider({region: 'Cognitoのリージョン'}).send(command); |
電話番号 属性を追加したことによりSMSでverification codeが送信されて来るので、それを使ってverifyし電話番号を検証済みにします。
01
02
03
04
05
06
07
08
09
|
import { CognitoIdentityProvider, VerifyUserAttributeCommand } from '@aws-sdk/client-cognito-identity-provider'; const command = new VerifyUserAttributeCommand({ AttributeName: 'phone_number', Code: 'SMSで受信したコード', AccessToken: 'サインインで取得したアクセストークン' }); await new CognitoIdentityProvider({region: 'Cognitoのリージョン'}).send(command); |
続いてユーザーのMFAを有効にします。
01
02
03
04
05
06
07
08
09
10
11
12
|
import { CognitoIdentityProvider, AdminSetUserMFAPreferenceCommand } from '@aws-sdk/client-cognito-identity-provider'; const command = new AdminSetUserMFAPreferenceCommand({ Username: 'ユーザーのsub属性', UserPoolId: 'ユーザープールID', SMSMfaSettings: { Enabled: enable, PreferredMfa: enable } }); await new CognitoIdentityProvider({region: 'Cognitoのリージョン'}).send(command); |
Cognitoユーザーの多要素認証を検証
MFAが有効になるとメールアドレス + パスワードによるサインインではトークンが取得できなくなり、代わりに以下のようなレスポンスが返されます。
{ ChallengeName: 'SMS_MFA', ChallengeParameters: { CODE_DELIVERY_DELIVERY_MEDIUM: 'SMS', CODE_DELIVERY_DESTINATION: '+********5678', USER_ID_FOR_SRP: 'ユーザーのsub属性' }, Session: 'ワンタイム セッション文字列' }
さらに、SMSでauthentication codeが送信されます。これと上記のレスポンスを使用してSMSによる認証を行います。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
import { CognitoIdentityProvider, AdminRespondToAuthChallengeCommand, ChallengeNameType } from '@aws-sdk/client-cognito-identity-provider'; const command = new AdminRespondToAuthChallengeCommand({ ChallengeName: ChallengeNameType.SMS_MFA, UserPoolId: 'ユーザープールID', ClientId: 'サインインに使用したアプリクライアントID', ChallengeResponses: { USERNAME: '上記レスポンスのChallengeParameters.USER_ID_FOR_SRP', SMS_MFA_CODE: 'SMSで受信したauthentication code' }, Session: '上記レスポンスのSession' }); await new CognitoIdentityProvider({region: 'Cognitoのリージョン'}).send(command); |
通常のサインインと同様にトークンがレスポンスとして返されます。
最後に
最初から多要素認証を設定する手順やCLIを使用しての多要素認証フローなどの記事はありましたが、未設定からの切り替えやNodeでCognitoのAPIを実行する形のフローはあまり見かけなかったので書いてみました。全てではなくても、ある程度の手順が分かれば公式ドキュメントやSDK内のモジュールを確認しながら解決できると思います。