Amazon Web Services ブログ

Amazon Cognito を使ってユーザー名を安全に検証

こちらは AWS コミュニティヒーローの Larry Ogrodnek によるゲストポストです。Larry は、クラウドアーキテクチャ、DevOps、サーバーレス、そして AWS における一般的なソフトウェア開発を専門とする独立系コンサルタントです。彼は、コーヒーを飲みながらいつでも AWS について話したいと考え、開発や他の開発者のサポートを楽しんで行っています。

あなたのシステムではどのような方法でユーザーを識別していますか? ユーザー名? E メール? それらが一意であることは重要ですか?

Amazon Cognito ではユーザー名も E メールも大文字と小文字を区別して取り扱っていると知ったら、あなたは驚くかもしれません。私も以前はそうでした。つまり “JohnSmith” と “johnsmith” は異なり、”jOhNSmiTh” もさらに別の人になります。 E メールアドレスも同様です。こちらはさらに SMTP RFC に指定されているので、”smith” および “Smith” というユーザーは、別のメールボックスを持つ可能性があります。すごいでしょう。

私は最近、Amazon Cognito にカスタムのサインアップ検証を追加しました。今回の投稿はこの実装についてお話します。

一意性にまつわる問題

一意性の判定は、大文字と小文字を区別すること以上に難しい問題です。私はこれまで、国際化ドメイン名のホモグラフ攻撃を取り上げたメールを多くの方々から受け取ってきました。あるサイトは、”example.com” というドメイン名で登録されていましたが、使用されている “a” はキリル文字で、真正のサイトになりすまして情報を収集することをたくらんでいたのです。こうした攻撃は、ユーザー名の登録でも行われる可能性があります。もし私がチェックをしなければ、誰かが私のサイトで別のユーザーになりすますことが可能かもしれません。

予約していますか?

アプリケーションには、ユーザーが作成したメッセージやコンテンツが含まれていますか? 一意性に対処することに加えて、特定の名前を予約しておくとよいかもしれません。例えば、user.myapp.com または myapp.com/user といった場所に、ユーザーが編集できる情報を保持していた場合、誰かが “signup”、”faq”、”support” の登録を行ったらどうなるでしょう。 あるいはそれが “billing” だったら?

悪意のあるユーザーが私のサイトになりすまし、攻撃の一手段として私のサイトを利用することは起こり得るのです。ユーザーが、どんな種類のものであれ受信箱やメッセージングを利用していれば、似たような攻撃も可能です。ユーザー名を予約しておくことに加えて、混乱を避けるために、ユーザーコンテンツをそれ自身のドメインから分離させておくことも必要でしょう。GitHub が、こうした攻撃に対処するために、 2013年にユーザーページを github.com から github.io へ移行させたことが思い出されます。

James Bennet が、彼の優れた投稿 Let’s talk about usernames においてこうした問題を詳しく解説しています。彼は、自身の django-registration アプリケーションで実行している検証の種類についても説明しています。

Amazon Cognito への統合

この問題のことが少しお分かりいただけたでしょうか。それでは、私が Amazon Cognito を使ってこれらの問題にどのように対処しているかについてお話します。

私はラッキーでした。なぜなら、Amazon Cognito を使って、認証のワークフローの大半を AWS Lambda トリガーでカスタマイズできたからです。

ユーザー名や E メールの検証を追加するために、私はサインアップ前 Lambda トリガーを実装しました。これで、カスタム検証を実行して、登録リクエストを承諾または拒否することが可能になります。

注意しなければならないのは、私にはリクエストの変更はできないということです。文字の大きさや名前に関する標準化 (強制的に小文字にする等) を実行する場合は、クライアント上で行わなければなりません。私に行えるのは、それが自分の Lambda 関数で実行されたことを検証することだけです。今後利用可能になれば、もっと扱いやすくなるだろうと思います。

サインアップを無効として宣言する場合、私が行うべきことは Lambda 関数からエラーを返すことだけです。Python であれば、例外を発生させることと同じくらい簡単です。検証に成功したらイベントを返します。このイベントには、汎用の成功応答に必要なフィールドがすでに含まれています。オプションで、いくつかのフィールドを自動検証することも可能です。

小文字で標準化されたユーザー名を送信することを、フロントエンドに適用する場合、必要なのは次のコードのみです。

def run(event, context):
  user = event[‘userName’]
  if not user.isLower():
    raise Exception(“Username must be lowercase”)
  return event

一意の制約と予約を追加

私は、検証チェックを django-registration から username-validator という名前の Python モジュールへ抽出し、このような種類の一意性チェックを Lambda で簡単に実行できるようにしました。

pip install username-validator

混同されやすいホモグリフを検出することに加え、ここには “www”、”admin”、”root”、”security”、”robots.txt” その他の、一連の標準的な予約済みの名前も含まれています。自分のアプリケーションに固有の予約を追加できるほか、個別のチェックも実行できます。

新しい検証やカスタムの予約を追加するため、次のように関数を更新しました。

from username_validator import UsernameValidator

MY_RESERVED = [
  "larry",
  "aws",
  "reinvent"
]

validator = UsernameValidator(additional_names=MY_RESERVED)

def run(event, context):
  user = event['userName']

  if not user.islower():
    raise Exception("Username must be lowercase")

  validator.validate_all(user)

  return event

これで、Amazon Cognito のユーザープールに、サインアップ前 Lambda トリガーとして Lambda 関数を添付し、”aws” にサインアップすると、400 エラーが表示されます。また、サインアップフォームに含めることの可能なテキストもいくつか表示されます。E メールを含むその他属性 (使用している場合) は、イベント [‘request’][‘userAttributes’]} の下から入手できます。以下に例を挙げます。

{ "request": {
    "userAttributes": {"name": "larry", "email": "larry@example.com" }
  }
}

最新情報

同じ方法で他の属性を検証することも可能です。あるいは、新たなチェックを追加して例外を発生させることで、その他のカスタム検証を追加することも可能です。失敗した場合のカスタムメッセージを付けます。

この投稿では、アイデンティティと一意性について考えることがなぜ重要なのか、そして Amazon Cognito にユーザーサインアップのための新たな検証を追加する方法についてご紹介しました。

カスタムの Lambda 関数を使ってサインアップ検証を管理する方法が、お分かりいただけたでしょうか。Lambda トリガーを使って行える、他のユーザープールワークフローのカスタマイズについても、ぜひお読みいただくことをおすすめします。