AWS CognitoでのauthenticateUser（ログイン）処理で呼ばれる関数を調べてみた。

# 背景
AWS使ってサーバーレスで自分用の家計簿的なwebサービスを勉強も兼ねて開発中。Cognitoでログイン制御は出来た（[前回の記事](https://qiita.com/silverbox/items/b3c8a53aa4baa3d02ff8)）。 しかし、どうもすっきりしない部分がある。

#### すっきりしない部分
> 新しいパスワード入れた後、以下のエラーがブラウザコンソール出てきた。completeNewPasswordChallenge 関数のcallbackもちゃんと実装必要があるはずだが、今回はスルー。Failしたと思われるが、ユーザーのステータスをAWSコンソールで確認するとCONFIRMEDになっていたし。今後ここらへんもうちょっとちゃんとすることにする。

#### 前提
- MFAは使用しない
- 管理者のみユーザー追加可能。その際は仮パスワード。
- ユーザーにパスワード変更を求める

調べると、同じような事を感じた人がいる様子。
[似たケース](https://stackoverflow.com/questions/51268933/aws-cognito-completenewpasswordchallenge-calls-onfailure-method-but-the-user-is)

#### 修正
テストのために、一旦ユーザー削除。※ボタンが表示されず、削除できないのか？と思ったが、一旦 *ユーザーを無効化* すると *ユーザーを削除* ボタンが出てきた。※ちなみに、ユーザー作成時、**E メールを検証済みにしますか?** にチェックを入れるとメールが飛んでこない様子。

どんなcallbackが必要なのかちゃんと調べる。VSCodeだと、関数にマウスを合わせると引数の型を表示してくれる。callbackにどんなメソッドが必要かも解る。素晴らしい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530376/2f758e30-3e26-9e5c-7b69-22e1d30cea68.png)

callback関数を指定する引数が**this**になっている。これがcallback引数全体を指しているならonFailureもちゃんと実装している。この部分がthisになってるのが関係してる疑惑。サンプルロジックから考えるとどうやらcallbackは再帰呼び出し的に使いまわしするものらしい。callbacksを外に出してみる。

```js:修正したlogin関数部分
  login (username, password) {
    const userData = { Username: username, Pool: this.userPool }
    const cognitoUser = new CognitoUser(userData)
    const authenticationData = { Username: username, Password: password }
    const authenticationDetails = new AuthenticationDetails(authenticationData)
    return new Promise((resolve, reject) => {
      var callbacks = {
        onSuccess: (result) => {
          resolve(result)
        },
        onFailure: (err) => {
          reject(err)
        },
        newPasswordRequired: (userAttributes, requiredAttributes) => {
          var newPass = window.prompt('Please input your new Password', 'Your input will not be masked. please make sure there is no other person.')
          cognitoUser.completeNewPasswordChallenge(newPass, {}, callbacks)
        }
      }
      cognitoUser.authenticateUser(authenticationDetails, callbacks)
    })
  }
```

エラー出なくなった。this参照が意図しない形になっていた様子。onFailureのエラーが出ていたのは、エラー実処理をしようとしていたのではなく、単に参照してる部分があったという事かと思われる。
[公式サンプル](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/using-amazon-cognito-identity-user-pools-javascript-example-authenticating-admin-created-user.html) でもthisは使っているが、今回参考にさせてもらったソースではPromiseの内部で処理してたりするので、thisがずれていたのだと思う。


# 調査

せっかくなので、ちゃんと調べてみる。VSCodeで、cognitoUser.authenticateUser 部分をホバーしてみると、第二引数は **callbacks**。形式は **IAuthenticationCallback** という事が分かる。[公式ソースの IAuthenticationCallback 部分](https://github.com/aws-amplify/amplify-js/blob/master/packages/amazon-cognito-identity-js/index.d.ts#L14-L29) を見ると、以下の関数がcallbacksに存在する事が解る。

- onSuccess
- onFailure
- newPasswordRequired
- mfaRequired
- totpRequired
- customChallenge
- mfaSetup
- selectMFAType

同 [completeNewPasswordChallengeのcallback部分](https://github.com/aws-amplify/amplify-js/blob/master/packages/amazon-cognito-identity-js/index.d.ts#L168-L180) を見てみると、callbacksは以下の関数。ほぼ重なるものの、違う。

- onSuccess
- onFailure
- mfaRequired
- customChallenge
- mfaSetup

多分、今までのはただうまく行っていただけと思われる。厳密にいえば再帰的に同じcallbacksを使うのではなくて、ちゃんとそれぞれのcallbacksを用意すべきと思われる。まぁ、当然ですね。認証の各ステップ毎に求められる処理は違うはず。もちろん同じ処理は共通関数にするとして、callbacksまるごと共有にするのは違っていたと思います。onFailureにしても、フェーズによって次の処理が違うと思うし。自分のケースでは、completeNewPasswordChallengeでの成功＆失敗が認証全体の成功&失敗だったから問題なかっただけかと。

#### IAuthenticationCallbackの各関数内ですべき処理をまとめてみる（※実践してないです。調査結果です。間違ってたらごめんなさい。コメントもらえると有り難いです）。
|callbacks内関数|呼ばれる時|すべき処理|その処理に使う関数名|参考リンク|
|:---|:---|:---|:---|:---|
|onSuccess|サインイン成功|トークン取得とか|アプリによる|－|
|onFailure|サインイン失敗|エラーメッセージとか|アプリによる|－|
|newPasswordRequired|仮パスワードログイン成功後|ユーザー入力の新しいパスワード渡す|cognitoUser.completeNewPasswordChallenge|[参考](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/using-amazon-cognito-identity-user-pools-javascript-example-authenticating-admin-created-user.html)|
|mfaRequired|サインインに SMS_MFA が必要な場合|ユーザー入力認証コードを送る|cognitoUser.sendMFACode|[参考](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/tutorial-integrating-user-pools-javascript.html#tutorial-integrating-user-pools-user-sign-in-javascript)|
|totpRequired|サインインに TOTP（ワンタイムパスワード） が必要な場合|ユーザー入力認証コードを送る|cognitoUser.sendMFACode|[参考](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/using-amazon-cognito-user-identity-pools-javascript-examples.html)|
|customChallenge|特殊認証追加してる場合|内容による|cognitoUser.sendCustomChallengeAnswer|－|
|mfaSetup|TOTP（ワンタイムパスワード）有効時の初回|associateSecretCodeを含むcallbackを渡し、その中でsecretCodeをユーザーデバイスに登録してもらう。|cognitoUser.associateSoftwareToken、cognitoUser.verifySoftwareToken|[参考](https://qiita.com/kai_kou/items/aa7dc0042b44649f576a)|
|selectMFAType|MFAの種類が選べる時|SMSかTOTPか指定（"SMS_MFA"か"SOFTWARE_TOKEN_MFA"）|cognitoUser.sendMFASelectionAnswer|[参考](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/using-amazon-cognito-user-identity-pools-javascript-examples.html)|

# 今回の件で得た豆知識
- Cognitoユーザープールでは、ユーザーは無効化してからでないと削除できない
- javaScriptの **this** は取扱注意。特に多人数で扱うソースの場合はどのオブジェクトを指すかすぐには解りにくいと思う。処理位置などを変更すると影響出てきそう。
- VSCodeだと（多分他のプログラム用エディタでも）、javaScript関数にマウスを合わせると引数の型を表示してくれる。

# 参考にさせてもらったページ
[Amazon Cognitoのワンタイムパスワード(TOTP)認証をNode.jsで試してみた](https://qiita.com/kai_kou/items/aa7dc0042b44649f576a)