📜

ログイン・パスワードリセット処理における、Cognito APIの挙動について

2024/10/16に公開

はじめに

認証を実装していて、CognitoのAPIを触っている中で、いくつか面白い仕様を見つけた。備忘録として記載しておく。

前提

CognitoのAPIを使って、ログイン・ログアウトと、パスワードリセットのフローを実装している。

  • ログイン:Initiate Auth APIを使用。
  • パスワードリセット:Forgot Password APIと、Confirm Forgot Password APIを使用。
    • 前者のAPIを使って、指定したユーザ名(と紐づくEmail or SMS宛)に認証コードを送信する。
    • 後者のAPIを使って、認証コード、ユーザ名(ログイン時のユーザ名/Email)、新パスワードを送信してリセットを完了する。

各APIの挙動

3つのAPIについて、特筆すべき内容を以下に記す。公式が言及している情報のみを、ソース付きで掲載している。

Initiate Auth APIの挙動

  • ★アカウントロックがデフォルトで実装されている。
    • 5回連続ミスで1秒→指数関数的に増大(最大15分)でロックがかかる。

https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html

https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html

認証されていないサインインまたはIAM認証されたサインインがパスワードで 5 回失敗した後、Amazon Cognito は 1 秒間ユーザーをロックアウトします。ロックアウトの期間は、試行が 1 回失敗するたびに 2 倍になり、最大で約 15 分になります。ロックアウト期間中に試行すると Password attempts exceeded 例外が生成され、その後のロックアウト期間の長さには影響しません。サインイン試行の累積失敗回数 n (Password attempts exceeded 例外を含まない) に対して、Amazon Cognito はユーザーを 2^(n-5) 秒間ロックアウトします。ロックアウトを n=0 初期状態にリセットするには、ユーザーは、ロックアウト期間後にサインインに成功するか、連続 15 分間、サインイン試行を開始してはなりません。この動作は変更される可能性があります。この動作は、パスワードベースの認証も実行しない限り、カスタムチャレンジに適用されません。

Forgot Password APIの挙動

  • ★存在しないユーザの通知手段で送信した場合も、認証コードの送信に成功したふりをする。
    • Cognito側で「Prevent User Existence Errors」という設定が有効になっている場合、ユーザが存在しない場合でもエラーを返さず、成功したように振る舞う。設定で無効化もできるが、推奨はされない。
    • 実在はするが、Cognitoのユーザプールに未登録のメールアドレスを入力した場合、そのアドレス宛にメールが届くことはない。

https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ForgotPassword.html

https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pool-managing-errors.html

ユーザー存在エラーを防ぐと、Amazon Cognito は、ユーザーパスワードのリセット操作に対して以下の応答を返します。
ForgotPassword
ユーザーが見つからない、非アクティブ化されている、またはパスワードを回復するための検証済みの配信メカニズムがない場合、Amazon Cognito はそのユーザーについてシミュレート済みの配信ミディアムを用いた CodeDeliveryDetails を返します。シミュレートされた配信メディアは、入力ユーザー名形式とユーザープールの検証設定によって決まります。
ConfirmForgotPassword
Amazon Cognito は、存在しない、または無効になっているユーザーについて CodeMismatchException エラーを返します。ForgotPassword の使用時にコードが要求されない場合、Amazon Cognito は ExpiredCodeException エラーを返します。

Confirm Forgot Password APIの挙動

  • このAPIではConfirmationCode, Username,Passwordの送信が必須。
    • 認証コードの期限は60分(※1)。期限切れのコードを送信した場合、「ExpiredCodeException」の例外を返す。
    • 存在しないor無効のユーザの場合は「CodeMismathException」の例外を返す(前述の引用参照)。
  • ★5回以上連続試行すると、リセット処理が一定時間ロックされる。
    • LimitExceededException」の例外が返る。
    • test@test.comのユーザで5回連続失敗すると、test@test.comのUsernameでは一定時間必ず送信に失敗するようになる。
    • 上記はこのAPIだけに影響する。例えばtest@test.comのユーザでログインを実行した場合、ログイン成否には影響がない。
    • 上記のLimitExceededException例外 の仕様については、ほとんど言及がない(※2)。かろうじて以下がヒットする程度(※3)。

https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ConfirmForgotPassword.html

https://repost.aws/ja/knowledge-center/cognito-recover-password

※1: E メールアドレスまたは電話番号の検証
ForgotPassword API コマンドを使用して、ユーザーパスワードを回復できます。ForgotPassword API コマンドは、検証済みの E メールアドレスまたは確認済みの電話番号にリカバリーコードを送信します。リカバリーコードは 1 時間有効です。次に、ConfirmForgotPassword API コマンドを使用して、パスワードをリセットする確認コードを入力します。

https://github.com/amazon-archives/amazon-cognito-identity-js/issues/548

※2: 次の投稿を参照してください。これらの保護メカニズムについては言及していますが、関連する正確な制限については外部に公開していません。 (2017年時点の回答)

https://docs.aws.amazon.com/cognito/latest/developerguide/managing-users-passwords.html

※3: パスワード忘れ時の動作 一定時間内に、forgot-passwordおよびconfirm-forgot-passwordアクションの一部として、ユーザがパスワードリセットコードを要求または入力する試行を5~20回許可する。 正確な値は、リクエストに関連するリスクパラメータによって異なります。 この動作は変更される可能性があることにご注意ください。

余談

  • Usernameを毎回入力させるのは手間ではないか?

    • HTTPはステートレスなので、何らかの仕組みを使わないとUsernameのパラメータをAPIごとに毎回直接送信することになる。
    • どちらのAPIもUsernameが必須になる。UsernameのデータをCookieとして持たせることで、毎回ユーザにユーザ名やEmailを入力させずに送信できる。
    • 例えばCognitoのホストされたUIだと、確認画面でEmailの入力が不要になっている。ユーザ体験の向上が見込める。
    • データを渡す場合、安全でない方法でUsernameのデータを渡すと、逆に情報が漏洩するので注意。
      (NG例:クエリパラメータにEmailの平文を渡す、セッションストレージに平文でEmailを保存するなど)
  • Usernameを画面ごとに入力する方式に、セキュリティ上の問題はないか?

    • 認証コードは6桁の数字であり、間違えるとエラーが返る。また有効期限が60分かつ、5回以上の連続送信でUsernameに対してロックがかかる仕様であるため、画面ごとに入力する形式でも、セキュリティ上の問題は考えにくい。
    • 「新パスワードを固定値、認証コードを総当たりで、同じUsernameにロックがかからない程度の時間を空けて送信をする」というリバースブルートフォース攻撃もどきの場合も考えたが、解析完了より前に、60分の期限が先にきそうである。
    • ↑というより、ロックがかかるまでのカウンターを内部で持っていると仮定すると、そのカウンターがリセットされる条件に依ると考えられる。
    • 例えばカウンターがリセットされるまでの基準が、ロックがかかるまで無期限だったり、60分以上の時間が必要だった場合は、上記の攻撃は事実上不可能になる。
      (5回以内に運良く当てれた場合は通るが、そんな可能性は限りなく低い)

考察・雑感

  • Initiate Auth APIについて

    • アカウントロックが実装されている。ユーザが意識しない秒数の範囲でロックがかかるのは、鮮やかな実装だと思う。
    • 最大15分のロックがかかるが、普通ユーザは5回も10回も間違えると「パスワード忘れちゃったや」となって、パスワードリセットに走るのが自然な動きだと思っている。ツール試行だけを的確に弾くようになっているのが良いと思った。
    • 開発者側がアカウントロック機能を実装しなくても、自然と対策されているのが有難い。
  • Forgot Password APIについて

    • 「ユーザが存在するか?」の情報は攻撃のヒントになる。それを教えない振る舞いをデフォルトで行うのが賢い。
    • UserNotFoundの例外を定義しつつも、設定を無効にしない限りは常に成功を返すのは面白い振る舞いだと思った。
  • Confirm Forgot Password APIについて

    • 「認証コードと通知したユーザのUsernameが一致すること」「有効期限の60分以内であること」「連続で失敗していないこと」と条件がかなり厳しく設定されていて良い。開発者が、意識せずに実装時の穴を回避できる仕組みになっていると思った。
    • パスワードのリセットを完了する重要な処理であるため、公開情報が少ない。「認証コードの数字の桁、有効期限はカスタマイズが可能なのか」「ロックがかかる挙動の詳細」はぱっと検索した限り、公式の言及が見つけられなかった。。

参考情報

Discussion