メインコンテンツにスキップ

AWS Cognitoとの格闘記:僕が認証システムをゼロから作った話

タグ: 🏷 AWS ,Cognito ,Golang ,React ,JWT ,CORS

背景

認証システムは、どんなアプリケーションにも欠かせない機能ですよね。でも、初心者だった僕にとって、認証は一度足を踏み入れると泥沼化する「魔の領域」と呼べるほど複雑なものでした。

  • セキュリティの複雑さ: パスワードの安全な保存、不正アクセスの防止など。
  • 運用の困難さ: パスワードリセット、アカウントロックなど、地味ながら重要な機能。

「とりあえず動けばいいや」で作った認証システムは、セキュリティホールの温床になりがちです。AWS Cognitoを使えば「簡単に認証システムができるはず」と期待していましたが、実際には数多くの罠と複雑な仕様に、本当に悩まされました。

この記事では、AWS Cognitoと格闘した僕の実体験を通じて、認証システムを構築する際の課題と、その解決策をご紹介していきたいと思います。

AWS Cognitoとは?

AWS Cognitoは、AWSが提供する、ログインやユーザー管理の仕組みを肩代わりしてくれるサービスです。これを活用することで、開発者は面倒な認証機能を自前で開発する手間を大幅に省くことができます。

格闘記 第1章: パスワードポリシーの罠

問題の発覚

ユーザーテスト中に、**「パスワードが受け付けられない」**という問題が頻繁に発生しました。一見、十分に複雑に見えるパスワードでも、なぜかCognitoに拒否されてしまうんです。

Cognitoパスワードポリシーの複雑さ

原因は、Cognitoの非常に厳格なパスワードポリシーにあったんです。大文字・小文字・数字・記号を含む8文字以上、というよくあるルールに加えて、一般的すぎる単語(passwordなど)や、ユーザー名に似た文字列まで禁止されていたんです。これがユーザーを大いに混乱させていました。

解決策: インテリジェントなパスワード支援

そこで、ユーザーがパスワードを入力する画面で、リアルタイムに「このパスワードは使えますよ」「大文字が足りませんよ」と教えてあげる機能を実装しました。これで、ユーザーは無駄な試行錯誤をすることなく、スムーズに安全なパスワードを設定できるようになりました。

教訓: ユーザーに厳しいルールを課す場合は、それを乗り越えるための親切なガイド機能を用意することが、良いユーザー体験につながる。

格闘記 第2章: JWTトークンのセキュアな取り扱い

JWTトークンとは?

JWTとは?

JWT(JSON Web Token)は、ログインしたユーザーに発行される「一時的な身分証明書」のようなものです。ユーザーがAPIにリクエストを送る際、このJWTを提示することで「私は正当なユーザーです」と証明します。この証明書には有効期限があり、安全性を高めています。

問題: トークンの不適切な管理

初期実装では、このJWTをブラウザのlocalStorageという場所に保存していました。これは非常に危険な方法です。

localStorageとXSS攻撃 localStorageは、Webページ上のスクリプト(JavaScript)から簡単にアクセスできてしまいます。悪意のある第三者がサイトに不正なスクリプトを埋め込む「XSS攻撃」を受けると、localStorageに保存されたJWTが盗まれ、ユーザーになりすまされてしまう危険性があります。

解決策: セキュアなトークン管理

HttpOnlyクッキーという仕組みを使って、この問題を解決しました。

HttpOnlyクッキーとは? HttpOnly属性が付いたクッキーは、プログラム(JavaScript)からアクセスできなくなります。ブラウザとサーバー間の通信でのみ送受信されるため、XSS攻撃を受けてもJWTが盗まれるリスクを大幅に減らすことができます。例えるなら、誰でも触れる机の上(localStorage)ではなく、鍵のかかった引き出し(HttpOnlyクッキー)に大切なものをしまうようなイメージです。

私たちは、サーバーからJWTをHttpOnlyクッキーとして発行し、ブラウザに安全に保管させることで、セキュリティを向上させました。

教訓: 認証情報のような機密データは、プログラムからアクセスできない安全な場所に保管するべき。

格闘記 第3章: CORS設定との格闘

CORSとは?

CORSとは?

CORS(Cross-Origin Resource Sharing)は、ブラウザが持つセキュリティ機能の一つです。基本的に、Webページ(例: https://example.com)は、自分とは異なるドメイン(例: https://api.example.com)にリクエストを送ることができません。CORSは、APIサーバー側で「このドメインからのアクセスは許可しますよ」と明示的に設定することで、この制限を安全に解除する仕組みです。

問題: 複雑なCORS要件

開発環境(http://localhost:3000)や本番環境(https://myapp.com)など、複数の環境からAPIにアクセスする必要があり、CORSの設定が非常に複雑になりました。安易にすべてのアクセスを許可する*(ワイルドカード)設定は、セキュリティ上非常に危険です。

解決策: インテリジェントなCORSミドルウェア

環境に応じて許可するドメインを動的に切り替える「賢い仲介役(ミドルウェア)」をサーバー側に実装しました。これにより、開発環境ではローカルからのアクセスを許可し、本番環境では本番のドメインのみを許可する、といった安全な設定を自動で行えるようになりました。

教訓: CORS設定はセキュリティの要。環境ごとに許可するドメインを厳密に管理し、安易なワイルドカード設定は避けるべき。

格闘記 第4章: 運用監視とCloudWatch連携

認証システムの監視要件

認証システムは、ただ動くだけでなく、「誰が、いつ、どこからログインしようとして、成功したか/失敗したか」を常に監視する必要があります。これにより、不正アクセスの兆候を早期に発見できます。

解決策: 構造化ログと自動アラート

私たちは、ログイン試行のたびに詳細な情報(IPアドレス、使用ブラウザ、成功/失敗など)を構造化ログとして記録し、AWSの監視サービスであるCloudWatchに送信する仕組みを構築しました。

さらに、「同じIPアドレスから1分間に10回以上ログイン失敗があった場合」などのルールを設定し、異常を検知したら自動で開発チームにアラートが飛ぶようにしました。これにより、セキュリティインシデントへの対応速度が劇的に向上しました。

教訓: 認証システムは作って終わりではない。詳細なログと自動アラートの仕組みを構築し、プロアクティブに監視することが不可欠。

セキュリティは最初から実装が肝

AWS Cognitoは非常に強力なサービスですが、安全かつ効果的に使いこなすには、周辺技術の深い理解が欠かせません。今回の格闘を通じて、僕たちは以下の重要な教訓を学びました。

  • セキュリティは後から付けられない: 設計の初期段階からセキュリティを考慮することが重要です。
  • ユーザビリティとのバランス: 厳しいルールと使いやすさは両立できます。
  • 監視の自動化: プロアクティブな監視体制が、システムの信頼性を支えます。

実装されたセキュリティ機能:

  • 🔒 パスワードポリシー: Cognitoの8つの制約に対応
  • 🛡️ JWTセキュリティ: HTTPOnlyクッキー + セキュリティマスキング
  • 🌐 CORS制御: 環境別の柔軟なオリジン管理
  • 📊 包括的監視: リアルタイム異常検知 + 自動アラート