ALB + Cognito 認証とセッションを用いてユーザー別にページ表示させる

2021.08.30

はじめに

おはようございます、もきゅりんです。

まず、本稿をまとめた背景を説明しておきます。

現状、Cognito を Webアプリケーション を利用するには JSフレームワークの利用、つまりSPA/SSR を前提とした Amplify SDK を利用する実装になるかと思います。 *1

cognito_sdk_table

しかし、弊社に技術相談されるお客様の中には、JSフレームワークおよびSPA/SSR、AmplifySDKを利用しない、Webアプリケーションでの Cognito のご利用を希望されるケースがたまにあります。

そのような要望の際、認証ページの日本語が対応できない *2 ALBとCognitoの統合を使った認証機能は、かなり限定的な利用機会にはなってくるとは思うのですが、一つの選択肢にはなるかな、と認識しています。

本稿が利用機会の一参考例となれば幸いです。

ただし、実際に利用を検討する際には、検証を目的とする本稿では考慮されていない実装の検討が多そうであることにご留意下さい。

なお、このブログをまとめるまで、いろいろとアドバイス頂いた Kitano さん、いつもありがとうございます。

やること

今回の構成図は以下です。

cognito_alb_architecture_image

なお、クライアント(moqrin, zonkey) とALB、Cognito、EC2との間でどのようなやり取りが行われているかを把握していることは前提となります。

詳細は ALB + Cognito認証で付与されるユーザー情報をEC2サイドから眺めてみる | DevelopersIO を参照下さい。

moqrin と zonkey というクライアントが、あるFQDNにアクセスすると認証画面が表示されます。

彼らがサインイン(初めてならサインアップ)すると、2つのサーバーにまたがって、自身が Cogntio で登録した名前と訪問回数と実行しているインスタンスIDが表示される、というシンプルな仕組みです。

本稿では、 x-amzn-oidc-data (ユーザークレーム) からユーザー情報を取得して、ElastiCache に投入した ユーザー名や訪問回数といったセッション情報を表示に利用しています。

user2 views

しかし、CognitoとALBとの統合では、下記引用文のように、ALBがブラウザの Cookie を検証して、ユーザー情報をHTTP ヘッダー内の X-AMZN-OIDC-*に設定してターゲットに転送しています。

Application Load Balancer は、 AWSELB 認証セッション cookie を持つユーザーを元の URI にリダイレクトします。ほとんどのブラウザでは Cookie のサイズを 4K に制限しているため、ロードバランサーはサイズが 4K を超える Cookie を複数の Cookie に分割します。
IdP から受け取ったユーザークレームとアクセストークンの合計サイズが 11K バイトを超える場合、ロードバランサーは HTTP 500 エラーをクライアントに返し、ELBAuthUserClaimsSizeExceeded メトリクスを増分します。
Application Load Balancer は Cookie を検証し、ユーザー情報をHTTP ヘッダー内の `X-AMZN-OIDC-*`に設定してターゲットに転送します。

ですので、本来、今回のような構成では、x-amzn-oidc-data( ユーザークレーム)を以下のように使うものと考えます。

  1. サインアップ時に Cognito の subクレームをRDMS(RDSなど)に保存
  2. 次回以降のサインインでユーザー認証が行われた後、ALBがセッションを検証し、サーバーは、ALBから転送された認証トークン(subクレーム)とDBテーブル内のsubクレームを照合してユーザー情報を取得

ただ、今回は上記のように、RDMSを使うほどのものではないため、ElastiCache を使って外出しされたセッション内からユーザー情報や訪問数を取得しています。

加えて、一般的には Cognito の認証トークン(IDトークン)と Web アプリケーションとのセッション管理は、下記のようになると考えます。

  1. サインアップ時の Cognito のsubクレームをRDMS(RDS)に保存
  2. 次回以降のサインインでユーザー認証が行われた後、Webサーバーがセッションを払い出す、(ElastiCache,DynamoDB,RDS) などにセッションIDと認証トークンとのセットを保存
  3. ブラウザの Cookie に保持されたセッションID、2で保存されたセッションIDとを照合して認証トークン(subクレーム)を取得
  4. RDMS(RDS)からsubクレームを使って、ユーザー情報を取得

やってみる

前提

VPC

  • default VPC があればそれで良いです
  • EC2のセキュリティグループをソース元とした6379ポート許可したセキュリティグループ

ALBとCognitoを組み合わせた認証の設定

設定については、弊社ブログ インフラエンジニアが一切コードを書かずにWebサーバーに認証機能を実装した話 | DevelopersIO で丁寧に説明されている通りですので、参照下さい。

変更点は下記です。

  • Cognito 管理画面でのユーザーの発行は不要
  • ユーザーのサインアップを許可
  • アプリクライアントは SRP (セキュアリモートパスワード) プロトコルベースの認証を有効にする (ALLOW_USER_SRP_AUTH)

ElastiCacheの作成

いじるのは下記だけなので、手動で30秒くらいで設定します。

  • 名前: 適当
  • ノードタイプ: cache.t2.micro
  • レプリケーション数: 0
  • サブネットグループ: default ならそのまま (なければよしなに)
  • セキュリティグループ: 上記セキュリティグループ
  • 自動バックアップの有効化: 無効

Docker で Express を起動する

EC2インスタンスにSSHログインして、Dockerをインストールしていきます。

sudo yum install docker -y
sudo systemctl start docker
sudo systemctl enable docker
sudo yum install git -y

GitHubリポジトリ から取得します。

アプリの中身については、本稿の主旨ではないため特に触れません。 Typescript で書かれた Express の Web サーバーです。

本番環境での利用を想定していないので、ご留意下さい。 Redis と接続するため、以下のように ElastiCache のエンドポイントに変更する必要があります。

// app.ts
-const redisClient = redis.createClient();
+const redisClient = redis.createClient(6379, 'ELASTICACHE_ENDPOINT');
// 省略
     store: new redisStore({
       client: redisClient,
-      host: '127.0.0.1',
+      host: 'ELASTICACHE_ENDPOINT',

書き換えたら実行します。

sudo docker build -t express-ts-demo-session .
sudo docker run -p 3000:3000 express-ts-demo-session

ログインしてリロードすると、冒頭で挙げたような表示結果になります。

user1_instance1

さいごに

元々、Application Load Balancer を使用してユーザーを認証するを参照してください。 - Elastic Load Balancing を参考に Python を JS に置き換えて進めていたのですが、どうしてもうまくいかず、AWS Load Balancer Auth · Issue #514 · auth0/node-jsonwebtoken に辿り着いてようやく検証自体はできました。

冒頭で述べたように、実際に利用を検討する際には、考慮事項の検討についてご留意下さい。

以上です。

どなたかのお役に立てば幸いです。

参考

脚注

  1. それ以外の手段でもやれなくはないのですが、Amplify SDKに比べると手間かかりますよね。
  2. Cognito Auth API & Hosted UI は、ドメイン名、UI のロゴ、CSS のカスタマイズが可能だが、日本語対応は不可。