Cognito のエラーコードまとめ

# 概要

Cognito ユーザープールから返ってくるエラーコードを javascript でエラーハンドリングしてる風にまとめる。
Amplify (javascript) で認証周りを実装した時、 Cognito ユーザープールから返ってくるエラーコード名とその状況が噛み合わないものがあって混乱したので整理した。

2020/04/25 現在、PasswordResetRequiredException が期待される状況で InvalidParameterException が返ってくるバグは修正が終わりデプロイ待ちだというコメントがあったのでここは一安心 ([issue](https://github.com/aws-amplify/aws-sdk-ios/issues/2298) より)。
早く修正分が反映されてほしいなぁ。

# Amplify Auth Methods 別エラーコードまとめ

ユーザープールのアプリクライアントで

- ユーザー存在エラーを防ぐ (PreventUserExistenceErrors) が `有効`
- MFA認証が `オフ`

の場合の一般的なエラーコードについてまとめる。
ちなみに PreventUserExistenceErrors が `レガシー` で設定されている場合、存在しないユーザ名がインプットされると一律 `UserNotFoundException` がエラーコードとして返されるらしい ([Amazon Cognito 開発者ガイド](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-dg.pdf) p.189 より)。

実装サンプルは [Amplify のドキュメント](https://docs.amplify.aws/lib/auth/getting-started/q/platform/js) を参考にした。

## 1. サインアップ関連

### 1.1 サインアップ: Auth.signUp()

Cognito ユーザープールに新しいユーザを作成する。

```ts

import { Auth } from 'aws-amplify'

async function SignUp(username, password, email) {
  try {
    const user = await Auth.signUp({
      username,
      password,
      attributes: {
        email // 必要な属性の例。
      }
    })
    // 必要な属性がない場合は attributes を省略可能。
    // const user = await Auth.signUp({
    //   username,
    //   password
    // })

    // SignUp 成功 (Eメールまたは電話番号による検証を設定している場合 Cognito からコードが送信される)
    console.log(user) // CognitoUser オブジェクト
  } catch (error) {
    switch (error.code) {
      case 'UsernameExistsException':
        // ユーザープール内に既に同じ username が存在する場合に起こる。
      case 'InvalidPasswordException':
        // ユーザープールのポリシーで設定したパスワードの強度を満たさない場合に起こる。
      case 'InvalidParameterException':
        // 必要な属性が足りない場合や、入力された各項目が Cognito 側で正しくパースできない場合（バリデーションエラー）に起こる。
        // password が6文字未満の場合はバリデーションエラーでこちらのエラーコードが返ってくる。
      default:
        // その他のエラー
        console.log(error)
    }
  }
}
```

### 1.2 ユーザーの確認: Auth.confirmSignUp()

ユーザーをEメールあるいは電話番号に送られたコードで確認 (ステータスを UNCONFIRMED から CONFIRMED に) し、サインイン可能な状態にする。

```ts

import { Auth } from 'aws-amplify'

async function ConfirmSignUp(username, code) {
  try {
    await Auth.confirmSignUp(username, code)
  } catch (error) {
    switch (error.code) {
      case 'CodeMismatchException':
        // 無効なコードが入力された場合に起こる。
      case 'LimitExceededException':
        // コードを間違え続けた場合に起こる。
      case 'ExpiredCodeException':
        // コードが期限切れ（24時間をオーバー）した場合に起こる。
        // 注) username が存在しない・無効化されている場合にも起こる。
      case 'NotAuthorizedException':
        // 既にステータスが CONFIRMED になっている場合に起こる。
      case 'CodeDeliveryFailureException':
        // 検証コードの送信に失敗した場合に起こる。
      default:
        // その他のエラー
        console.log(error)
    }
  }
}
```

### 1.3 コードの再送: Auth.resendSignUp()

SignUp 確認用コードを再送する。

```ts

import { Auth } from 'aws-amplify'

async function ResendConfirmationCode(username) {
  try {
    await Auth.resendSignUp(username)
    // 存在しない・無効化されている username を使用してもエラーにはならない。
  } catch (error) {
    switch (error.code) {
      case 'CodeDeliveryFailureException':
        // 検証コードの送信に失敗した場合に起こる。
      default:
        // その他のエラー
        console.log(error)
    }
  }
}
```

## 2. サインイン関連

### 2.1 サインイン: Auth.signIn & Auth.completeNewPassword

ユーザー名とパスワードを使ってサインインする。
Cognito コンソールで作成されたユーザーが仮パスワードでサインインした場合、新しいパスワードと必要な属性を入力させる。

```ts

import { Auth } from 'aws-amplify'

async function SignIn(username, password) {
  try {
    const user = await Auth.signIn(username, password)
    if (user.challengeName === 'NEW_PASSWORD_REQUIRED') {
      // Cognitoコンソールで作成したユーザが最初にサインインするときに起こる。
      // Auth.completeNewPassword() で新しいパスワードと必要な属性を入力する必要がある。
      const newPassword = *** // 新しいパスワード
      const email = *** // 必要な属性 (例：email)

      const loggedUser = await Auth.completeNewPassword(user, newPassword, {
        username,
        email
      })
      // 新しいパスワードの設定成功 // CognitoUser オブジェクト
      console.log(loggedUser)
    } else {
      // SignIn 成功
      console.log(user) // CognitoUser オブジェクト
    }
  } catch (error) {
    switch (error.code) {
      case 'UserNotConfirmedException':
        // ユーザのステータスが UNCONFIRMED の場合に起こる。
        // SignUp用のコードを再送し、ステータスを CONFIRMED にする必要がある。
        // 検証コードの再送は １．３節の ResendConfirmationCode() を参照。
      case 'PasswordResetRequiredException':
        // Cognito コンソールでパスワードをリセット（ユーザープールにユーザをインポートする場合も含む）した場合に起こる。
        // パスワードをリセットする必要がある。
        // パスワードのリセットは 3.1節の SendForgotPasswordCode() 参照。
      case 'NotAuthorizedException':
        // 誤ったパスワードを入力した場合に起こる。
        // 注) パスワードを間違え続けた場合にも起こり、 error.message が 'Password attempts exceeded' になる。
        // （エラーコードとして LimitExceededException が返ってくると思ったらそうではなかった）
      case 'UserNotFoundException':
        // PASSWORD_VERIFIER は通るものの username が Cognito ユーザープールに存在しない場合に起こる。
      case 'InvalidParameterException':
        // 入力された username や password が Cognito 側で正しくパースできないとき（バリデーションエラー）に起こる。
        // 注) 2019/04/24 現在、Cognito コンソールでパスワードをリセットした場合は 'PasswordResetRequiredException' ではなくこのエラーコードが返される。
      default:
        // その他のエラー
        console.log(error)
    }
  }
}
```

## 3. パスワード関連

### 3.1 パスワードのリセット: Auth.forgotPassword & Auth.forgotPasswordSubmit

パスワードを忘れてしまいサインインできない場合、アカウント回復手段として指定されたEメールもしくは電話番号宛にコードを送信する。
ユーザ名と受信したコード、新しいパスワードを入力してパスワードをリセットする。

```ts

import { Auth } from 'aws-amplify'

async function SendForgotPasswordCode(username) {
  try {
    await Auth.forgotPassword(userName)
    // 存在しない・無効化されている username を使用してもエラーにはならない。
    // パスワードの回復手段を「なし」に設定している場合もエラーにはならない。
  } catch (error) {
    console.log(error)
  }
}

async function ConfirmForgotPassword(username, code, newPassword) {
  try {
    await Auth.forgotPasswordSubmit(
      username,
      code,
      newPassword
    )
  } catch (error) {
    switch (error.code) {
      case 'CodeMismatchException':
        // 無効なコードが入力された場合に起こる。
        // 注) username が存在しない・無効化されている場合にも起こる。
      case 'LimitExceededException':
        // コードを間違え続けた場合に起こる。
      case 'ExpiredCodeException':
        // コードが期限切れ（1時間をオーバー）した場合に起こる。
        // 注) Auth.forgotPassword() によってコードがリクエストされていない場合にも起こる。
      case 'InvalidPasswordException':
        // ユーザープールのポリシーで設定したパスワードの強度を満たさない場合に起こる。
      case 'InvalidParameterException':
        // password が6文字未満など Cognito 側で正しくパースできない場合（バリデーションエラー）に起こる。
      default:
        // その他のエラー
        console.log(error)
    }
  }
}
```

### 3.2 パスワードの変更: Auth.changePassword

認証（サインイン）済みのユーザーがサインイン時に使用しているパスワードと新しいパスワードを入力することでパスワードを変更する。

```ts

import { Auth } from 'aws-amplify'

async function ChangePassword(oldPassword, newPassword) {
  try {
    // 認証（サインイン）済みのユーザーのユーザーオブジェクトを取得
    const user = await Auth.currentAuthenticatedUser()
    await Auth.changePassword(user, oldPassword, newPassword)
  } catch (error) {
    switch (error.code) {
      case 'NotAuthorizedException':
        // oldPassword が誤っている場合に起こる。
      case 'LimitExceededException':
        // oldPassword を間違え続けた場合に起こる。
      case 'InvalidPasswordException':
        // ユーザープールのポリシーで設定したパスワードの強度を満たさない場合に起こる。
      case 'InvalidParameterException':
        // password が6文字未満など Cognito 側で正しくパースできない場合（バリデーションエラー）に起こる。
      default:
        // その他のエラー
        console.log(error)
    }
  }
}
```

## 4. ユーザー管理周り

### 4.1 アカウント回復手段(Eメール・電話番号)の確認: Auth.verifyCurrentUserAttribute & Auth.verifyCurrentUserAttributeSubmit

認証（サインイン）済みのユーザーがアカウントの回復手段としてEメールまたは電話番号を利用できるようにする。
送られてきたコードを入力することで、Eメールの場合は Eメール確認済み (email_verified) を false から true に、電話番号の場合は 電話番号確認済み (phone_number_verified) を false から true にする。

```ts

import { Auth } from 'aws-amplify'

async function SendVerifyUserAttrCode() {
  try {
    const attr = 'email' // 電話番号を確認する場合は 'phone_number' 。
    await Auth.verifyCurrentUserAttribute(attr)
    // Eメールもしくは電話番号宛にコードが送信される。
  } catch (error) {
    switch (error.code) {
      case 'CodeDeliveryFailureException':
        // 検証コードの送信に失敗した場合に起こる。
      default:
        // その他のエラー
        console.log(error)
    }
  }
}

async function VerifyUserAttr(code) {
  try {
    const attr = 'email' // 電話番号を確認する場合は 'phone_number' 。
    await Auth.verifyCurrentUserAttributeSubmit(attr, code)
  } catch (error) {
    switch (error.code) {
      case 'CodeMismatchException':
        // 無効なコードが入力された場合に起こる。
      case 'LimitExceededException':
        // コードを間違え続けた場合に起こる。
      case 'ExpiredCodeException':
        // コードが期限切れ（24時間をオーバー）した場合に起こる。
      case 'NotAuthorizedException':
        // ユーザーが無効化された場合に起こる。
      case 'UserNotFoundException':
        // ユーザーがユーザープールに存在しない場合に起こる。
      default:
        // その他のエラー
        console.log(error)
    }
  }
}
```

### 4.2 セッションの取得： Auth.currentSession

現在のセッションオブジェクト (CognitoUserSession) を取得する。
トークンの有効期限が切れていて有効なrefreshTokenが提示された場合、accessTokenとidTokenを自動的にリフレッシュしてくれる。

```ts

import { Auth } from 'aws-amplify'

async function GetUserSession() {
  try {
    const session = await Auth.currentSession()

    // セッションの取得成功
    console.log(session) // CognitoUserSession オブジェクト
  } catch (error) {
    console.log(error)
  }
}
```

### 4.3 セッションのリフレッシュ: Auth.currentAuthenticatedUser & Auth.currentSession

セッションをリフレッシュする。

```ts

import { Auth } from 'aws-amplify'

async function RefreshSession() {
  try {
    const user = await Auth.currentAuthenticatedUser()
    const currentSession = await Auth.currentSession()
    user.refreshSession(currentSession.getRefreshToken(), (err, session) => {
        if (err) throw err

        // セッションのリフレッシュ成功
        console.log(session) // CognitoUserSession オブジェクト
      }
    )
  } catch (error) {
    console.log(error)
  }
}
```

### 4.4 ユーザー属性の更新: Auth.updateUserAttributes

ユーザー属性を更新する。
Eメールを更新した場合確認用のコードが送信されるため、 ユーザー属性の更新後に 4.1節の `VerifyUserAttr()` でEメールを確認済みにする処理を行うべき。

```ts

import { Auth } from 'aws-amplify'

async function UpdateUserAttr(email, familyName) {
  try {
    const user = await Auth.currentAuthenticatedUser()
    const result = await Auth.updateUserAttributes(user, {
      email,
      family_name: familyName
    })

    // ユーザー属性の更新成功 (Eメールを更新した場合コードが送信される)
    console.log(result) // 'SUCCESS'
  } catch (error) {
    switch (error.code) {
      case 'InvalidParameterException':
        // phone_number が E.164 number convention でないなど各属性が Cognito 側で正しくパースできない場合（バリデーションエラー）に起こる。
      case 'CodeDeliveryFailureException':
        // 検証コードの送信に失敗した場合に起こる。
      default:
        // その他のエラー
        console.log(error)
    }
  }
}
```

# 参考
- [Amazon Cognito 開発者ガイド](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-dg.pdf)
- [Amplify Docs - JavaScript](https://docs.amplify.aws/lib/auth/getting-started/q/platform/js)
- [Imported User (RESET_REQUIRED) throws InvalidParameterException on signIn #2298](https://github.com/aws-amplify/aws-sdk-ios/issues/2298)
- [Amazon Cognitoで発生する例外(Exception)をまとめてみた](https://qiita.com/yukitaka13-1110/items/99197466c1ba88f0f8d3)