CloudFront+S3による静的サイトにCognito認証を追加してみた

2024.01.31

こんにちは、なおにしです。

静的コンテンツのみをWebサイトとして公開したい場合、AWSであればCloudFront+S3の構成はよく利用されるかと思います。
CloudFrontを用いているため、地理的制限の機能を使ったりAWS WAFと組み合わせたりすることである程度のアクセス元制限を行うことはできます。一方で、特定のユーザに対してのみ公開したいというユーザ認証が必要なケースでは別の仕組みが必要になります。
そこで今回は、Amazon Cognitoを用いてCloudFront+S3による静的サイトに認証機能を追加してみました。

どうすれば良いか

Application Load BalancerやAPI Gatewayにはマネジメントコンソール上でCognitoとの連携を設定するためのUIが準備されていますが、CloudFrontにはそれがありません。そこで、AWSから公開されている以下のNode.jsのパッケージをCloudFront+Lambda@Edgeに適用することでCognitoと連携します。

本パッケージに関する説明は以下のとおりです。

この Node.js パッケージは、CloudFront ディストリビューションにリクエストを行うユーザーが Cognito ユーザープールを使用して認証されていることを確認するのに役立ちます。これは、リクエストに含まれる Cookie を確認することで実現され、リクエスト者が認証されていない場合は、ユーザープールのログイン ページにリダイレクトされます。(Google 翻訳)

前提

今回は静的サイトの部分は既に作成済み&公開済みという前提で、以下の赤枠部分を追加する形で設定していきます。

やってみた

事前準備

認証機能の追加にあたって、ユーザが認証情報を入力するためのUIについてはCognitoが提供するUIを利用します。この際、Cognitoが提供する認証ページのドメインを指定する必要があります。ドメインについてもCognitoで提供されるもの(東京リージョンであれば「https://○○○.auth.ap-northeast-1.amazoncognito.com」)を使用することもできますが、前提のとおり既存の静的サイトがあれば独自ドメインもRoute 53で設定済みかと思います。そこで今回は認証ページにカスタムドメインとして取得済みの独自ドメインを設定するために、事前に以下を済ませておきます。

  • Route 53で認証ページ用のレコードを作成

静的サイトをホストしているCloudFrontディストリビューションを指定したエイリアスレコードを作成します。作成するレコード名は、例えば静的サイトが「www.example.com」であれば「auth.example.com」のように静的サイトとは別名で作成します。

  • 認証ページに接続する際に使用するACM証明書を作成

静的サイト構築時にワイルドカード証明書を作成している場合はそのまま流用できます(ACM証明書はバージニア北部リージョン/us-east-1で発行されているはずです)。

Cognitoの設定

認証機能の追加が目的であるためユーザープールを作成します。認可のためのIDプールは使用しません。
サインインオプションは少なくとも1つ選択する必要があるため、今回は「Eメール」を選択します。

MFAの方法としては「Authenticator アプリケーション」を選択します。その他の項目はデフォルトのままです。

マネジメントコンソールに記載のとおり、「自己登録を有効化」すると認証ページから誰でも自分自身をユーザ登録できてしまうため、今回はチェックを外します。その他の項目はデフォルトのままです。

パスワードを再設定する場合などにCognitoからメールを送信する際の送信元を設定します。Amazon SESでメールを送信できる状態であれば、以下のように任意の送信元からメールを送信できます。

以下の各種項目を設定します。

  • 任意のユーザープール名を入力
  • 今回はAPIを実装せずにCognitoで提供される認証ページを使用するため「Cognito のホストされたUIを使用」にチェック
  • 認証ページのためのドメイン設定として「カスタムドメインを使用」を選択
  • カスタムドメインには前述の事前準備で設定した認証ページ用のレコードを設定
  • ACM証明書も事前準備で作成または確認したものを選択
  • 任意のアプリケーションクライアント名を入力
  • コールバックURL(認証後に遷移する先のURL)として、静的サイトのURLを入力

設定した内容を確認して問題なければ「ユーザープールを作成」を選択します。作成完了まで少し時間がかかります。

ユーザープールが作成されたら各種設定を行えるようになるので、検証用にユーザーを作成してみます。

「アプリケーションの統合」タブの下部に先ほど作成したアプリケーションクライアントが表示されているので「ホストされたUIを表示」を選択します。

Cognitoが提供する認証ページが表示されます。ここまでの設定に問題がなければ、上記で作成したユーザ情報でログインしてコールバックURLに設定した静的サイトのページに遷移することが確認できます。

CognitoとCloudFrontの連携設定

上記までの設定が終わっても、静的サイトのURLに直接アクセスすれば認証ページは当然表示されません。
ここからは、静的サイトのURLにアクセスするとCognitoの認証ページに推移するように設定を追加していきます。

Getting startedに記載の内容を参考に前述したNode.jsパッケージを取得して設定およびzip化します。

mkdir test-cognito-at-edge
cd  test-cognito-at-edge
npm install cognito-at-edge
vi index.js
zip -r test-cognito-at-edge.zip *

index.jsには以下を記載して保存します。

const { Authenticator } = require('cognito-at-edge');

const authenticator = new Authenticator({
  // Replace these parameter values with those of your own environment
  region: 'ap-northeast-1', // user pool region
  userPoolId: '(ユーザープールID)', // user pool ID
  userPoolAppId: '(クライアントID)', // user pool app client ID
  userPoolDomain: '(カスタムドメイン ※https://は不要)', // user pool domain
});

exports.handler = async (request) => authenticator.handle(request);

ユーザープールIDとクライアントIDは以下のとおりマネジメントコンソールで表示されています。
ソースコードにID情報を直接書いた状態になっていますが、Lambda@Edgeには諸々の制限があるため今回はこのまま進めます。

上記制限に記載のとおりLambda@EdgeではLambda関数をバージニア北部リージョンでデプロイする必要があります。任意の関数名を指定し、「デフォルトの実行ロールの変更」から「AWS ポリシーテンプレートから新しいロールを作成」を選択します。

ポリシーテンプレートを開くと「基本的なLambda@Edgeのアクセス制限(CloudFrontトリガーの場合)」という項目があるので、こちらを選択して任意のロール名を設定して関数を作成します。

Lambda関数ができたら先ほど作成したzipファイルをアップロードし、Lambda@Edgeにデプロイします。設定は以下のとおりで、「Distribution」には静的サイトに用いているCloudFrontディストリビューションを指定します。

デプロイするとLambda関数の画面ではすぐに適用されたように見えますが、対象のCloudFrontディストリビューションはデプロイステータスになりますので、ステータスが「有効」に変わったことを確認してから静的サイトにアクセスします。
認証ページが表示されれば完了です。
上手く動作しない場合は以下を確認すれば解決するかもしれません。

  • 認証ページを追加する前のブラウザキャッシュが残っていないか
  • ブラウザの設定でサードパーティCookieの使用を許可しているか

終わりに

パラメータ設定のためにjsファイルを若干編集したものの、ほぼノーコードで静的サイトに対して認証を追加することができました。ただし、今回はユーザープールIDなどの情報をスクリプトに直接記載していますが本来は外部参照にすべきなので、そちらについては別途まとめたいと思います。
この記事がどなたかのお役に立てれば幸いです。