Amazon Cognitoの「グループ機能」でAmazon CloudFrontのアクセスを自在に制御する

こんにちは、ひるたんぬです。

5月も下旬になり、(個人的に)今年の花粉シーズンは無事乗り切ることができました。
ピークのときは寝るときも鼻詰まりで辛かったです。
そんなとき、横向きになって寝ていると、一時的ではありますが、上側の鼻詰まりが解消しますよね。
(下図の場合は、左側の鼻詰まりが解消します。)

鼻をかんですらいないのに、なぜ鼻が通るのでしょうか…?

…(前略)神経反射を利用した鼻腔通気の方法として腋窩周囲の皮膚圧迫についても検討されています。…(中略)…圧迫した側の交感神経の活動が抑えられて血管が弛緩し、鼻粘膜が膨張するのです。対側は、交感神経の活動が逆に強化されて鼻粘膜の収縮が起こるということです。…(中略)…鼻が詰まっている時に、詰まった側を上にして寝ると鼻が通るのは、横向きに寝ることで腋が押さえられているからなのですね。…(後略)…
引用:m3.com「神経反射用いた鼻症状制御のコツ【時流◆くしゃみの医学的インパクト】

鼻水そのものの流れが変わるのではなく、神経作用により鼻粘膜が膨張・収縮することが原因だったのですね。。

さて、今回は題名にもある通り、Cognitoの「グループ機能」を活用して、CloudFrontに対するアクセスを制御する方法を2パターンご紹介したいと思います。

Cognitoのグループ機能とは?

まずは、今回の肝となるCognitoのグループ機能についてご説明します。
Cognitoのグループはユーザープール内に作成するものであり、ユーザープール内に登録されているユーザーをグループに所属させることが可能になります。
IAMロールの紐づけも可能なため、AWSリソースのアクセス制御に利用することも可能です。
詳細につきましては、AWSの公式ドキュメントをご参照ください。

ユーザープール・グループ・ユーザーの関係を整理すると、以下のようになります。
ユーザーは任意の数のグループに所属することが可能です。(所属なし、も可能です。)

Entra IDなどの外部IdPからのユーザーについては、自動的に作成されたグループに追加されます。
また、そのグループからの脱退はできません。

実現方法

グループでのアクセス制御を実施するためには、CognitoをCloudFrontで制御するcognito-at-edgeのコードに少々追記します。
具体的には、サインインユーザーが所属するグループの照合処理、アクセス拒否する場合のレスポンス書き換えを追加しました。

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: 'ap-northeast-1_abcdefgh1', // user pool ID
  userPoolAppId: '1examp1ec1ient1d', // user pool app client ID
  userPoolDomain: 'pathauth-userpool-domain.auth.ap-northeast-1.amazoncognito.com', // user pool domain
  cookieDomain: 'example.com', // domain name
  cookiePath: '/',
});

// 以下が追記内容

const requiredUserGroup = 'GroupName'

async function fetchUser(request) {
  try {
    const tokens = authenticator._getTokensFromCookie(request.headers.cookie);
    return await authenticator._jwtVerifier.verify(tokens.idToken);
  } catch (_error) {
    return null;
  }
}

exports.handler = async (event) => {
  const response = await authenticator.handle(event);
  const { request } = event.Records[0].cf;
  const user = await fetchUser(request);

  if (user) {
    const userGroups = user['cognito:groups'] || [];
    if (!userGroups.includes(requiredUserGroup)) {
      return {
        status: '401',
        statusDescription: 'Unauthorized',
        headers: {
          'content-type': [{
            key: 'Content-Type',
            value: 'text/plain; charset=utf-8',
          }],
          'cache-control': [{
            key: 'Cache-Control',
            value: 'no-cache, no-store, max-age=0, must-revalidate',
          }],
        },
        body: 'Unauthorized: You do not have permission to access this resource.',
      };
    }
  }
  return response;
};
上記コードではcookieDomainを設定していますが、CloudFrontのデフォルトドメイン(*.cloudfront.net)で検証を行う場合などは不要です。

CloudFrontに対するアクセス制御

ここからは、Cognitoのグループ機能を用いたCloudFrontに対するアクセス制御について、二つのユースケースでご紹介したいと思います。

ユースケース①:同一サイト(ディストリビューション)内のパスごとにアクセス制御をしたい

パスごとにアクセス許可を変更したい場合(例えば、一般会員向けページと上級会員向けページなど)は、この例に該当します。
なお、特定のパスにのみCognitoの認証を設定する方法につきましては、以前執筆した記事をご参照ください。

今回のユースケースを図に表すと以下のようになります。

実際にこれを実装してみます。

ユーザーのグループへの所属

ユーザーをグループに所属させる方法はコンソール上では二通りあります。

  • グループ設定からユーザーを追加
    → 編集したいグループを選択し、「ユーザーをグループに追加」を選択することで可能です。
  • ユーザー設定からグループを追加
    → 編集したいユーザーを選択し、「ユーザーをグループに追加」を選択することで可能です。

アプリケーションクライアントの設定

今回は複数のパスに対して一つのアプリケーションクライアントで設定をするため、コールバックURLを複数個追加します。

Lambda@Edgeの実装

上記コードから変更した箇所のみ抜粋します。

const authenticator = new Authenticator({
  // Replace these parameter values with those of your own environment
  region: 'ap-northeast-1', // user pool region
  userPoolId: 'ap-northeast-1_abcdefgh1', // user pool ID
  userPoolAppId: '1examp1ec1ient1d', // user pool app client ID
  userPoolDomain: 'pathauth-userpool-domain.auth.ap-northeast-1.amazoncognito.com', // user pool domain
  parseAuthPath: '/member', // afrer authentication path
  cookiePath: '/member',
});

// 以下が追記内容

const requiredUserGroup = 'GroupName'

今回はパラメータに「parseAuthPath」を追加しています。parseAuthPathについては、githubにて以下のように説明されています。

URI path used as redirect target after successful Cognito authentication (eg: /oauth2/idpresponse), defaults to the web domain root. Needs to be a path that is handled by the library. When using this parameter, you should also provide a value for cookiePath to ensure your cookies are available for the right paths.
引用:github「/cognito-at-edge

また、「requiredUserGroup」については、Cognitoのグループ名(アクセスを許可したいグループ名)を設定します。

コードの準備ができたらCloudShellなどでビルドします。ビルド方法などは上記参考記事をご参照ください。

結果の確認

実際に各ユーザーでページを表示させた結果を確認します。
認証が必要な際には、Cognitoのマネージドログインページが表示されます。

今回はCognitoのエッセンシャルプランを使用しています。
  • ユーザーA:一般会員グループに所属
  • ユーザーB:一般会員・上級会員グループに所属
  • ユーザーC:どのグループにも所属しない

アクセスが許可されていないパスに対しては、Lambdaで定義したレスポンスのメッセージ(Unauthorized: You do not have permission to access this resource.)が表示されています。

ユースケース②:複数のサイト(ディストリビューション)を一つのCognitoでアクセス制御したい

会員情報は一箇所で管理したいが、サイトごとにアクセス許可を変更したい場合は、この例に該当します。
今回のユースケースを図に表すと以下のようになります。

今回はサイトがそれぞれ独立していることから、アプリケーションクライアントも別個で作成しています。

Lambda@Edgeの実装

上記コードから変更した箇所のみ抜粋します。

const authenticator = new Authenticator({
  // Replace these parameter values with those of your own environment
  region: 'ap-northeast-1', // user pool region
  userPoolId: 'ap-northeast-1_abcdefgh1', // user pool ID
  userPoolAppId: '1examp1ec1ient1d', // user pool app client ID
  userPoolDomain: 'pathauth-userpool-domain.auth.ap-northeast-1.amazoncognito.com', // user pool domain
  // cookieDomain: 'example.com', デフォルトドメインで検証のため削除
  cookiePath: '/',
});

// 以下が追記内容

const requiredUserGroup = 'GroupName'

結果の確認

実際に各ユーザーでページを表示させた結果を確認します。

  • ユーザーA:サイトYグループに所属
  • ユーザーB:サイトY・サイトZグループに所属
  • ユーザーC:どのグループにも所属しない

おわりに

今回は、Cognitoの「グループ機能」を活用して、CloudFrontに対するアクセスを制御する方法を2パターンご紹介しました。
Cognitoを集約して利用することで、あちこちにユーザーが散らばることがなくなり管理負荷の軽減に貢献できるほか、重複ユーザーを排除できるためユーザー数に応じて課金されるCognitoにおいて、利用料金の節約が可能となります。

×
タイトルとURLをコピーしました