Cognito IDプールでゲスト/サインインユーザーで権限を分ける

Cognito IDプールでゲスト/サインインユーザーで権限を分ける

Cognito IDプールは認証済みユーザーに加えて、未認証のゲストユーザーに対しても、一時的なクリデンシャルを生成することができます。
今回はゲスト・サインインユーザーに対して、異なる権限を与える構成を確認します。

構築する環境

Diagram of change permissions for guest/sign-in users in Cognito ID Pool.

S3バケットを作成し、静的ウェブサイトホスティング機能を有効にします。
内部にHTMLファイルを設置して、コンテンツを用意します。

CognitoユーザープールのホストされたUIでサインイン機能を作成します。

Cognito IDプールでゲスト・サインインユーザーに権限を割り当てるように設定します。
具体的には、2つのLambda関数を作成し、それぞれ対応するユーザーが実行できるように設定します。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLに、CloudFormationテンプレートに加え、ブラウザスクリプト等を配置してます。

https://github.com/awstut-an-r/awstut-dva/tree/main/03/003

テンプレートファイルのポイント解説

サインインユーザーとゲストユーザーで権限を分けるため、Cognito IDプールを設定を中心に確認します。

IDプールにゲスト・サインインユーザー用のIAMロールをアタッチする

Cognito IDプールでユーザーに権限を与えるためには、対応するIAMロールをアタッチするという方法になります。

Resources:
  IdentityPool:
    Type: AWS::Cognito::IdentityPool
    Properties:
      AllowUnauthenticatedIdentities: true
      CognitoIdentityProviders:
        - ClientId: !Ref UserPoolClient
          ProviderName: !Sub "cognito-idp.${AWS::Region}.amazonaws.com/${UserPool}"
      IdentityPoolName: !Sub "${Prefix}-IdentityPool"

  IdentityPoolRoleAttachment:
    Type: AWS::Cognito::IdentityPoolRoleAttachment
    Properties:
      IdentityPoolId: !Ref IdentityPool
      Roles:
        authenticated: !GetAtt IdentityPoolAuthenticatedRole.Arn
        unauthenticated: !GetAtt IdentityPoolUnauthenticatedRole.Arn
Code language: YAML (yaml)

IDプール本体は特別な設定を行いません。
ユーザープールと連携するように設定するだけです。

ポイントはIAMロールをアタッチメントです。
今回はサインイン・ゲストユーザーに別々の権限を割り当てますので、それぞれauthenticated・unathenticatedプロパティを使用して、IAMロールを設定します。

今回は以下のIAMロールを定義します。

Resources:
  IdentityPoolAuthenticatedRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRoleWithWebIdentity
            Principal:
              Federated: cognito-identity.amazonaws.com
            Condition:
              StringEquals:
                cognito-identity.amazonaws.com:aud: !Ref IdentityPool
              ForAnyValue:StringLike:
                cognito-identity.amazonaws.com:amr: authenticated
      Policies:
        - PolicyName: IdentityPoolAuthenticatedPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - lambda:InvokeFunction
                Resource:
                  - !Ref AuthenticatedFunctionArn

  IdentityPoolUnauthenticatedRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRoleWithWebIdentity
            Principal:
              Federated: cognito-identity.amazonaws.com
            Condition:
              StringEquals:
                cognito-identity.amazonaws.com:aud: !Ref IdentityPool
              ForAnyValue:StringLike:
                cognito-identity.amazonaws.com:amr: unauthenticated
      Policies:
        - PolicyName: IdentityPoolUnauthenticatedPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - lambda:InvokeFunction
                Resource:
                  - !Ref UnauthenticatedFunctionArn
Code language: YAML (yaml)

ConditionプロパティでIAMロールを引き受けるための条件を設定します。
詳細は以下のページをご確認ください。

https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/role-trust-and-permissions.html

Policiesプロパティで、付与する権限を定義します。
Lambda関数を指定して実行を許可します。

実行するLambda関数

Cognitoとは関係ありませんが、Lambda関数の定義を確認します。

Resources:
  AuthenticatedFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub "${Prefix}-AuthenticatedFunction"
      Runtime: python3.9
      Role: !GetAtt LambdaRole.Arn
      Handler: index.lambda_handler
      Code:
        ZipFile: |
          import datetime

          def lambda_handler(event, context):
            return str(datetime.date.today())


  UnauthenticatedFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub "${Prefix}-UnauthenticatedFunction"
      Runtime: python3.9
      Role: !GetAtt LambdaRole.Arn
      Handler: index.lambda_handler
      Code:
        ZipFile: |
          import datetime

          def lambda_handler(event, context):
            return str(datetime.datetime.now().time())
Code language: YAML (yaml)

インラインで実行するコードを記載します。
Lambda関数の定義方法の詳細については、以下のページをご確認ください。

あわせて読みたい
CloudFormationでLambdaを作成する3パータン(S3/インライン/コンテナ) 【CloudFormationでLambdaを作成する】 CloudFormationでLambdaを作成する場合、大別すると以下の3パターンあります。 S3バケットにコードをアップロードする インライ...

ランタイム環境はPython3.9を選択し、それぞれ以下を実行するように設定します。

  • サインインユーザー用関数:本日の日付を返す。
  • ゲストユーザー用関数:現在の時刻を返す。

ブラウザスクリプト

JavaScriptを使用して、サインイン後のCognito機能を実装します。
AWS SDK for JavaScript v3を使用してブラウザスクリプトを作成する手順は、以下のページをご確認ください。

あわせて読みたい
ブラウザでAWS SDK for JavaScript v3を扱う 【ブラウザでAWS SDK for JavaScript v3を扱う】 AWSを使用してWebアプリを開発する場合、AWS SDK for JavaScriptの使用がベストプラクティスとなります。同SDKには2つ...

本ページでは、ゲスト・サインインユーザーの振り分けに関する内容を中心に取り上げます。

IDトークン取得

今回の構成では、OAuthフローにImplicit Grant(暗黙の付与)を設定しています。
Implicit Grantですと、URLパラメータからIDトークンを取得することができます。
サインインユーザーの場合、以下のコードでIDトークンを取得することができます。

const params = new URLSearchParams(location.hash.slice(1));
const idToken = params.get("id_token");
Code language: JavaScript (javascript)

詳細は以下のページをご確認ください。

あわせて読みたい
Cognitoユーザープールの認証後にIDプールで権限を与える(Implicit Grant編) 【Cognitoユーザープールの認証後にIDプールで権限を与える】 CognitoユーザープールとIDプールを使用して、サインインユーザーにAWSリソースにアクセスするための権限...

ユーザーに応じてLambdaクライアント作成

Lambda関数にアクセスするために、クライアントオブジェクトを作成します。
ポイントはIDトークンの有無によって、対応を分ける点です。

import {
  CognitoIdentityClient
} from "@aws-sdk/client-cognito-identity";

import {
  fromCognitoIdentityPool
} from "@aws-sdk/credential-provider-cognito-identity";

import {
  LambdaClient,
  InvokeCommand
} from "@aws-sdk/client-lambda";

import {
  toUtf8
} from "@aws-sdk/util-utf8-browser";

let lambdaClient;
let functionName;
if (idToken) {
  lambdaClient = new LambdaClient({
    region: REGION,
    credentials: fromCognitoIdentityPool({
      client: new CognitoIdentityClient({ region: REGION }),
      identityPoolId: IDENTITY_POOL_ID,
      logins: {
        [`cognito-idp.${REGION}.amazonaws.com/${USER_POOL_ID}`]: idToken
      }
    }),
  });
  functionName = FUNCTION_NAME_AUTHENTICATED;
} else {
  lambdaClient = new LambdaClient({
    region: REGION,
    credentials: fromCognitoIdentityPool({
      client: new CognitoIdentityClient({ region: REGION }),
      identityPoolId: IDENTITY_POOL_ID
    }),
  });
  functionName = FUNCTION_NAME_UNAUTHENTICATED;
}
Code language: JavaScript (javascript)

サインインユーザーの場合、先述のコードによって、IDトークンの取得できます。
ですからそのトークンを使って、クライアントオブジェクトを作成します。

一方ゲストユーザーの場合、サインインしていませんから、IDトークンを取得できません。
ですからIDトークンを使わない形で、クライアントオブジェクトを作成します。

このクライアントオブジェクト時に、IDプールにアタッチしたIAMロールに基づいたクリデンシャルが使われます。
つまりユーザーに応じた権限が設定されているということです。

Lambda関数実行

この後の処理は共通です。
クライアントオブジェクトを使ってLambda関数を実行します。

(async () => {
  const response = await lambdaClient.send(
    new InvokeCommand({ FunctionName: functionName })
  );
  document.getElementById("function-result").innerText = toUtf8(response.Payload);
})();
Code language: JavaScript (javascript)

Lambda関数を実行し、実行結果をHTMLに埋め込みます。
先述の通り、クリデンシャルで付与された権限の範囲内で関数を実行します。

HTMLファイル

参考にHTMLも確認します。

<html>
  <head></head>
  <body>
    <h1>index.html</h1>
    <p id="function-result"></p>
    <ul>
      <li>
        <p><a href="https://[domain].auth.ap-northeast-1.amazoncognito.com/login?response_type=token&client_id=[client-id]&redirect_uri=[redirect-url]/index.html">Sign In</a></p>
      </li>
      <li>
        <p><a href="https://[domain].auth.ap-northeast-1.amazoncognito.com/logout?client_id=[client-id]&logout_uri=[redirect-url]/index.html">Sign Out</a></p>
      </li>
    </ul>
    <script type="text/javascript" src="./main.js"></script>
  </body>
</html>
Code language: HTML, XML (xml)

クラス名がfunction-resultのpタグ内に、Lambda関数の実行結果を埋め込みます。

サインイン・サインアウト用のURLを設置します。
リダイレクトURLはこのindex.htmlを指すように設定します。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【CloudFormationのネストされたスタックで環境を構築する方法】 CloudFormationにおけるネストされたスタックを検証します。 CloudFormationでは、スタックをネストす...

S3バケットにコンテンツを設置する

S3バケットにHTML・JavaScriptファイルを設置します。

Two files are uploaded to the S3 bucket.

この2ファイルがサインイン/サインアウト後のコンテンツです。

検証:ゲストユーザーでアクセス

準備が整いましたので、実際にコンテンツにアクセスします。
以下のURLにアクセスします。

https://s3-ap-northeast-1.amazonaws.com/dva-03-003/index.html

Functions for guest users were executed.

現在の時刻が表示されました。
これは現時点ではサインインしていませんので、ゲストユーザーとして本ページにアクセスしていることになり、ゲストユーザー用のLamabda関数が実行されたということです。

検証:サインインユーザーでアクセス

続いてサインアップ/サインイン処理を行います。
詳細は以下のページをご確認ください。

あわせて読みたい
Cognito ユーザープールでサインインページを作成する 【CognitoユーザープールのホストされたUIでサインインページを作成する】 Cognitoを使用してサインインページを作成します。 CognitoはAWSが提供する認証系サービスで...

サインアップ/サインインが完了しましたら、改めて本ページにリダイレクトされます。

Functions for Sign-in users were executed.

現在の日付が表示されました。
これはサインイン処理によって、IDトークンが生成され、サインインユーザー用のLambda関数が実行されたということです。

(参考)IDプール

参考までに、ここまでの手順で生成されたIdentityを確認します。

Two Identities have been created.
Two Identities have been created.

ご覧の通り、2つのIdentityが作成されていることがわかります。
つまりサインインユーザーだけでなく、ゲストユーザー用のIdentityも生成され、それを使ってゲスト用のLambda関数が実行されたということです。

まとめ

ゲスト・サインインユーザーで権限を分ける方法を確認しました。
具体的には、IDプールでゲスト・サインインユーザー用のIAMロールをアタッチした上で、IDトークンの有無によって処理を分岐させる方法です。