はじめに

メールアドレス + パスワードでサインインしている既存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内のモジュールを確認しながら解決できると思います。