Amplify SDKを使ってMFA対応ログイン処理を書くときの覚書

AWS Amplify Advent Calendar 2020、6日目の記事です。

AWS AmplifyのSDKを使うとCognitoのMFAも割と簡単に実装できます。が、AWSが用意しているUIではなく自前のUIでやろうと思うと多少頑張らないといけないところがあるので、その辺について軽く触れます。

Auth.signInの挙動

MFA設定の有無に関わらず、自前でログイン処理を走らせる場合はAuth.signInを利用します。

const result = await Auth.signIn(username, password)

この処理の戻り値を見ることで、MFAなどを利用した多段階認証であるかを確認できます。

const result = await Auth.signIn(username, password)

const hasChallenge = Object.prototype.hasOwnProperty.call(result, 'challengeName');
if (!hasChallenge) {
  // Login Complete!
} else {
  // MFAなどの処理
}

MFAのコードを使った認証

window.confirmなり自前のJSXなりでSMSやアプリに表示されたコードを入力するUIを実装します。

そしてそこから送られたコードをAmplify Auth(もといCognito User Pools)に渡す時には、先ほどの戻り値を一部利用します。

// なにかしらの方法でAuth.signInの戻り値を取得する
const restoredResult = getSignInResult()

// なにがしらの実装でMFAのコードを取得する
const challengeCode = getChallengeCodeFromAnyUI()

if (!restoredResult) throw new Error('No Cognito User data')
await Auth.confirmSignIn(restoredResult, challengeCode, restoredResult.challengeName)

Auth.signInの戻り値にはCognitoUserクラスが入っています。その中にあるchallengeNameを使うことで、ユーザーがSMS / TOTPどちらで認証しようとしているかなどを判別することができます。

ReactであればuseContext、ReduxであればStoreなどに預けてコードを入力させ、続きの処理ではselectなどを使って取り出して続行するという形が一般的かなと思います。

Appendix: MFAを有効化・無効化する

Amplify SDKの場合、どちらの操作も同じ処理で実装できます。

export type MFAType = 'SMS' | 'TOTP' | 'NOMFA';
export const updateMFA = async (type: MFAType) => {
  const user = await Auth.currentAuthenticatedUser();
  return Auth.setPreferredMFA(user, type);
};

NOMFAを渡すと無効化されますので、updateMFAをラップしたdisableMFAなどを用意してやるとより使いやすいでしょう。

export const disableMFA = () => updateMFA('NOMFA')

ちなみに、Challenge(ログイン時)は'SOFTWARE_TOKEN_MFA', 'SMS_MFA'、設定時は'SMS', 'TOTP'なので、Authからの戻り値を使ってうまく動かないと思った時は、この表記揺れに引っかかっていないかチェックしておきましょう。

const fetchMFAStatus = async () => {
  const user = await Auth.currentAuthenticatedUser();
  const result = await Auth.getPreferredMFA(user);
  if (/^SMS/.test(result)) return 'SMS';
  if (result === 'SOFTWARE_TOKEN_MFA') return 'TOTP';
  return result;
};

自分の場合は、設定系では上のように雑に丸めてます。

Comment