はじめに
近年のWebアプリケーション開発において、認証・認可機能の重要性はますます高まっています。しかし、独自で安全な認証システムをゼロから開発・運用するのはコストも脆弱性のリスクも伴います。
そこでインフラエンジニアの間で需要が高いのが、AWSの「Application Load Balancer(ALB)」と「Amazon Cognito」を連携させた認証のオフロード(肩代わり)設計です。この構成を採用すれば、Webアプリケーションに一行の認証コードも書くことなく、安全なログイン機能を実装できます。本記事では、この構成の具体的な設定手順と設計・運用のベストプラクティスを解説します。
前提知識/必要な理由
なぜALBとCognitoを連携させるのか?
従来の構成では、認証処理をバックエンドのアプリケーション(ECSやEC2など)で実装していました。これには以下の課題がありました。
- 認証ライブラリの脆弱性アップデートに追随し続ける必要がある
- 異なるマイクロサービス間で、セッション管理やユーザー情報の同期が複雑になる
ALBとCognitoを連携させることで、ALBがクライアントからのリクエストを横取りし、Cognitoによる認証が完了しているか自動で検証します。認証済みのリクエストのみが安全にバックエンドへとルーティングされるため、開発者はビジネスロジックの実装に集中でき、インフラ全体のセキュリティ水準を均一に保つことができます。
具体的な設定手順・設計方法
ステップ1:Amazon Cognito ユーザープールの作成
まずはユーザー情報を管理するCognitoユーザープールを作成します。
- AWSマネジメントコンソールからCognitoを開き、「ユーザープールの作成」をクリックします。
- 「サインインオプション」として「Eメール」を選択します。
- パスワードポリシーや多要素認証(MFA)を要件に応じて設定し、作成を完了します。
ステップ2:アプリクライアントとドメインの設定
ALBがCognitoと通信するための「アプリクライアント」をユーザープール内に作成します。
- 作成したユーザープール内の「アプリケーションの統合」タブから「アプリクライアントの作成」を選択します。
- 「クライアントシークレットを生成する」にチェックが入っていることを確認します。
- 「ホストされたUI(Hosted UI)」の設定で、許可されたコールバックURLにALBのDNS名(またはカスタムドメイン)を指定します(例:
https://your-alb-domain.com/oauth2/idpresponse)。 - 「Cognitoドメイン」を設定し、ログイン画面用のドメイン(AWS提供のサブドメインまたはカスタムドメイン)を確保します。
ステップ3:ALBリスナールールへの認証アクション追加
次に、ALBがリクエストを受け取った際にCognito認証を挟むよう設定します。※ALBでHTTPSリスナーが設定されている必要があります。
- EC2ダッシュボードの「ロードバランサー」から対象のALBを選択します。
- HTTPSリスナー(ポート443)のルール一覧を開き、ルールを編集します。
- 「アクションの追加」で「認証」を選択し、プロトコルとして「Amazon Cognito」を指定します。
- ステップ1、2で作成した「ユーザープール」と「アプリクライアント」を選択します。
- 認証後の次のアクションとして、既存の「ターゲットグループへの転送」を設定します。
ステップ4:バックエンドでのJWT(JSON Web Token)検証
ALBは認証に成功すると、ユーザー情報を含むJWT(JSON Web Token)をHTTPヘッダー(x-amzn-oidc-dataなど)に付与してバックエンドへ転送します。バックエンドアプリ側でこのヘッダーを検証し、ユーザー情報を識別します。以下はPythonを用いた検証コードの例です。
※本実装では外部パッケージ PyJWT および暗号化ライブラリ cryptography を使用します。事前に以下のコマンドでライブラリをインストール、またはLambdaレイヤーやDockerイメージに組み込んでおいてください。
pip install PyJWT cryptography requests
以下がバックエンド(例:FastAPIやFlaskなど)でヘッダーからユーザー情報を安全に抽出・検証するためのPythonコード実装例です。
import jwt
import requests
def verify_alb_jwt(encoded_jwt):
# AWSが署名検証用に公開している公開鍵(JWKS)のエンドポイント
region = "ap-northeast-1"
jwks_url = f"https://public-keys.auth.elb.{region}.amazonaws.com/"
# JWTのヘッダーからキーID(kid)を取得
headers = jwt.get_unverified_header(encoded_jwt)
kid = headers['kid']
# 公開鍵を取得して検証(実務ではキャッシュすることを推奨)
pub_key_response = requests.get(jwks_url + kid)
pub_key = pub_key_response.text
try:
# トークンの署名検証とデコード
payload = jwt.decode(
encoded_jwt,
pub_key,
algorithms=["ES256"],
audience="<YOUR_ALB_ARN_OR_CLIENT_ID>"
)
return payload
except jwt.ExpiredSignatureError:
print("トークンの有効期限が切れています")
return None
except jwt.InvalidTokenError:
print("無効なトークンです")
return None
実務での注意点
1. HTTPS通信の必須化とACM証明書
ALBとCognitoを連携させるには、ALB側でHTTPSリスナー(ポート443)が必須です。AWS Certificate Manager (ACM) で有効なSSL/TLS証明書を発行し、ALBに紐づけておく必要があります。HTTP(ポート80)へのリクエストは、すべてHTTPSへリダイレクトするリスナールールを最優先で設定しておきましょう。
2. セキュリティグループによるバックエンドの保護
ALBによる認証をバイパスして、直接EC2やECSタスクにアクセスされてしまうと、未認証のままシステムが利用される恐れがあります。バックエンドサービス(ECSのセキュリティグループ等)は、ALBからの通信のみを許可するようにインバウンドルールを厳密に設定してください。
3. ALBが付与するHTTPヘッダーの検証(なりすまし対策)
悪意のあるユーザーが手動で x-amzn-oidc-data ヘッダーを偽装してリクエストを送信してくる可能性があります。これを防ぐため、上述のステップ4で紹介した「署名検証コード」をアプリ側で必ず実装し、AWSが署名した正当なトークンであるかを確認するプロセスを省かないようにしてください。
まとめ
ALBとCognitoの連携は、Webアプリケーションの認証機能をインフラレイヤーにオフロードできる非常に強力なソリューションです。開発スピードの向上だけでなく、AWSが管理するセキュアな仕組みに乗ることで運用負荷を劇的に低減できます。
導入の際は、アプリ側でのJWT署名検証の実装と、セキュリティグループを用いたALBルートの強制を徹底し、セキュアなインフラ設計を実現しましょう。