Cognitoユーザープールの認証後にIDプールで権限を与える(Implicit Grant編)

Cognitoユーザープールの認証後にIDプールで権限を付与する - Implicit Grant編

Cognitoユーザープールの認証後にIDプールで権限を与える

CognitoユーザープールとIDプールを使用して、サインインユーザーにAWSリソースにアクセスするための権限を付与します。

CognitoユーザープールのホストされたUIを使用して、サインインページを作成します。

Cognitoによる認証・認可を実行するためにJavaScriptを使用します。
今回はAWS SDK for JavaScript v3を使用します。

今回のOauthフローでは、Implicit Grant(暗黙の付与)を選択します。

構築する環境

Diagram of Authorization by Cognito ID Pool after Authentication by User Pool - Implicit Grant Ver

S3バケットを作成し、静的ウェブサイトホスティング機能を有効にします。内部にHTMLファイルを設置して、コンテンツを用意します。
CognitoユーザープールのホストされたUIとIDプールを組み合わせ、認証ユーザーに対して、SSMパラメータにアクセスする権限を与えます。

環境構築用のCloudFormationテンプレートファイル

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

https://github.com/awstut-an-r/awstut-fa/tree/main/012

ポイント解説

CognitoユーザープールのホストされたUI

Cognitoユーザープールが提供する機能の1つに、ホストされたUIというものがあります。マネージドの認証機能です。今回はこれを使用してサインインページを構築します。
Cognito ホストされたUIについては、以下のページをご確認ください。

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

本ページでは、ポイントとなる点のみを取り上げます。

Resources:
  UserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      AllowedOAuthFlowsUserPoolClient: true
      AllowedOAuthFlows:
        #- code
        - implicit
      AllowedOAuthScopes:
        - openid
        - profile
      CallbackURLs:
        - !Sub "${BucketWesSiteEndpointUrl}/${SigninHtml}"
      ClientName: !Sub ${Prefix}-UserPoolClient
      ExplicitAuthFlows:
        - ALLOW_REFRESH_TOKEN_AUTH
        - ALLOW_USER_SRP_AUTH
      LogoutURLs:
        - !Sub "${BucketWesSiteEndpointUrl}/${SignoutHtml}"
      SupportedIdentityProviders:
        - COGNITO
      UserPoolId: !Ref UserPool
Code language: YAML (yaml)

ポイントとなる設定はAllowedOAuthFlowsプロパティです。
今回のOAuthフローはImplicit Grant(暗黙の付与)ですので、このプロパティに「implicit」を設定します。

次にホストされたUIによるサインイン/サインアウトページのURLを確認します。
それぞれ以下のURLとなります。

  • サインインページ用URL:https://[プレフィックス].auth.ap-northeast-1.amazoncognito.com/login?response_type=token&client_id=[アプリクライアントID]&redirect_uri=[サインイン後のリダイレクト先URL]
  • サインアウトページ用URL:”https://[プレフィックス].auth.ap-northeast-1.amazoncognito.com/logout?response_type=token&client_id=[アプリクライアントID]&logout_uri=[サインアウト後のリダイレクト先URL]

ポイントはURLパラメータのresponse_typeです。
Implicit Grantの場合、直接トークンを取得できますので、本パラメータを「token」に設定します。

Cognito IDプールで認証ユーザーに権限を付与する

IDプール関係のリソースを確認します。IDプールはAWSリソースにアクセスする際の認可機能を司ります。
まずIDプール本体です。

Resources:
  IdentityPool:
    Type: AWS::Cognito::IdentityPool
    Properties:
      AllowUnauthenticatedIdentities: false
      CognitoIdentityProviders:
        - ClientId: !Ref UserPoolClient
          ProviderName: !Sub "cognito-idp.${AWS::Region}.amazonaws.com/${UserPool}"
      IdentityPoolName: !Sub ${Prefix}-IdentityPool
Code language: YAML (yaml)

AllowUnauthenticatedIdentitiesプロパティで、ユーザープールにて認証されていないユーザーに対しても、AWSリソースへのアクセス権限を付与するかどうかを設定できます。今回は認証されたユーザーに対してのみアクセス権を認めますので、本プロパティは「false」とします。
CognitoIdentityProvidersプロパティで、IDプールに関連づけるユーザープールおよびアプリクライアントを指定します。

認証ユーザーに付与する権限をIAMロールととして定義し、IDプールにアタッチします。

Resources:
  IdentityPoolRoleAttachment:
    Type: AWS::Cognito::IdentityPoolRoleAttachment
    Properties:
      IdentityPoolId: !Ref IdentityPool
      Roles:
        authenticated: !GetAtt IdentityPoolAuthenticatedRole.Arn

  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:
                  - ssm:GetParameter
                Resource:
                  - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${AuthenticatedParameter}"
Code language: YAML (yaml)

今回は認証ユーザーに対して、SSMパラメータストアに対するアクセス権限を許可します。
SSMパラメータストアの文字列を取得できる権限を持ったIAMロールを作成し、IDプールに関連づけます。
なおフェデレーションに関する設定は、「ロールの信頼とアクセス権限」を参考に設定しました。

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

認証ユーザに権限を付与するブラウザスクリプト

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つ...

本ページでは、ポイントとなる箇所を取り上げます。

npmパッケージ

まずnpmでインストールするパッケージを確認します。
以下のコマンドで、3つのパッケージをインストールします。

$ npm install @aws-sdk/client-cognito-identity

$ npm install @aws-sdk/credential-provider-cognito-identity

$ npm install @aws-sdk/client-ssm
Code language: Bash (bash)

CognitoとSSMに関するパッケージです。

ブラウザスクリプト

まずパッケージの読み込みに関する記述を確認します。

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

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

import {
  SSMClient,
  GetParameterCommand
} from "@aws-sdk/client-ssm";
Code language: JavaScript (javascript)

先ほどインストールした3パッケージから、必要なオブジェクトを読み込みます。

次にパラメータの定義に関して確認します。

const REGION = "[region]";
const USER_POOL_ID = "[cognito-user-pool-id]";
const IDENTITY_POOL_ID = "[cognito-id-pool-id]";
const PARAMETER_NAME = "[ssm-parameter-id]";

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

最初の4行は、リージョン情報や、各種リソースのIDといった情報です。

ポイントは下2行です。
サインイン後のページのURLから、IDトークンに関する文字列を抽出しています。
これは今回のOAuthフローがImplicit Grant(暗黙の付与)であるためです。
なおサインイン後のページのURLは、以下のようなものです。

https://s3-ap-northeast-1.amazonaws.com/fa-012/signin.html#id_token=[id-token]&expires_in=3600&token_type=Bearer

SSMリソースにアクセスするためのクライアントオブジェクトを作成します。

const ssmClient = new SSMClient({
  region: REGION,
  credentials: fromCognitoIdentityPool({
    client: new CognitoIdentityClient({ region: REGION }),
    identityPoolId: IDENTITY_POOL_ID,
    logins: {
      [`cognito-idp.${REGION}.amazonaws.com/${USER_POOL_ID}`]: idToken
    }
  }),
});
Code language: JavaScript (javascript)

SSMクライアントを作成するために、IDプールから一時的なクリデンシャルを取得します。
その際に、Idpであるユーザープールの名前と、先ほど定義したユーザープールのIDトークンを使用します。

SSMパラメータストアからパラメータを取得する関数を定義します。

const getParameter = async () => {
  try {
    const response = await ssmClient.send(
      new GetParameterCommand({ Name: PARAMETER_NAME })
    );
    document.getElementById('parameter').innerText = `SSM Parameter Store: ${response.Parameter.Value}`;
  } catch (err) {
    console.log(err);
    document.getElementById('parameter').innerText = 'Deny access to SSM Parameter Store.';
  }
};

window.getParameter = getParameter;
Code language: JavaScript (javascript)

作成したSSMクライアントに、GetParameterCommandオブジェクトを渡して、パラメータストアから文字列を取得します。
正常に取得できた場合は、その値をHTMLに埋め込みます。

ユーザー名を取得する関数を定義します。

const getName = async () => {
  try {
    const tokens = idToken.split('.');
    const tokenDecoded = JSON.parse(atob(tokens[1]));
    document.getElementById('name').innerText = `Name: ${tokenDecoded.name}`;
  } catch (err) {
    console.log(err);
    document.getElementById('name').innerText = 'Name: Guest';
  }
};

window.getName = getName;
Code language: JavaScript (javascript)

ユーザープールのIDトークンはJWTトークンです。

ID トークンとは、name、email、および phone_number などの認証されたユーザーのアイデンティティに関するクレームが含まれる JSON Web トークン (JWT) です。この ID 情報はアプリケーション内で使用できます。ID トークンは、リソースサーバーまたはサーバーアプリケーションに対するユーザーの認証にも使用できます。

ID トークンの使用

デコードすることでユーザー情報を取得できます。今回はトークンから取得したユーザーの名前(name)情報を、pタグ内に埋め込みます。IDトークンのペイロードに関しては、ID トークンの使用をご確認ください。

このファイルをnpmでビルドすることで、ブラウザスクリプトは完成です。

サインイン後のページ用ファイル

<html>
  <head>
  </head>
  <body>
    <h1>signin.html</h1>
    <p id="name"></p>
    <p id="parameter"></p>
    <script type="text/javascript" src="./main.js"></script>
    <script>
      getName();
      getParameter();
    </script>
  </body>
</html>
Code language: HTML, XML (xml)

シンプルはHTMLファイルです。
先ほど確認した2つの関数を実行し、動的にユーザー名とSSMパラメータストアから取得した文字列を表示させます。

SSMパラメータストア

Resources:
  AuthenticatedParameter:
    Type: AWS::SSM::Parameter
    Properties:
      Name: !Sub "${Prefix}-authenticated"
      Type: String
      Value: Authenticated
Code language: YAML (yaml)

特別な設定は行わず、パラメータストアに文字列を保存します。
今回は以下の通りに設定します。

  • パラメータ名:fa-012-authenticated
  • 保存する値:Authenticated

環境構築

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

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

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

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

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • S3バケットの名前:fa-012
  • CognitoユーザープールのID:ap-northeast-1_W8ZPnQYRK
  • Cognitoユーザープール アプリクライアントのID:2lp4695vpuo88ettvk78lmq694
  • Cognitoユーザープール ドメインプレフィックス:fa-012
  • Cognito IDプールのID:ap-northeast-1:6d117ebe-506b-4f0d-b7dc-a438a4dbd57b

以上より、先述のホストされるUIによるサインイン/サインアウトページ用のURLは以下に定まります。

  • サインインページ用URL
    • https://fa-012.auth.ap-northeast-1.amazoncognito.com/login?response_type=token&client_id=2lp4695vpuo88ettvk78lmq694&redirect_uri=https://s3-ap-northeast-1.amazonaws.com/fa-012/signin.html
  • サインアウトページ用URL
    • https://fa-012.auth.ap-northeast-1.amazoncognito.com/logout?response_type=token&client_id=2lp4695vpuo88ettvk78lmq694&logout_uri=https://s3-ap-northeast-1.amazonaws.com/fa-012/signout.html

S3バケットにHTMLを設置する

S3バケットにHTMLを設置し、サインイン/サインアウト後のコンテンツを用意します。

先述のサインイン/サインアウト用URLをindex.htmlに記載後、AWS CLIを使用して設置します。

$ aws s3 cp ./html s3://fa-012/ --recursive

$ aws s3 cp main.js s3://fa-012
Code language: Bash (bash)

認証ユーザー向けコンテンツにアクセスする:成功時

準備が整いましたので、実際にホストされたUIにアクセスします。

まず以下のURLにアクセスします。

https://s3-ap-northeast-1.amazonaws.com/fa-012/index.html

するとルートページであるindex.htmlが表示されます。

Access the Cognito user pool sign-in page.

「Sign In」を押下すると、サインインページが表示されます。

初回に限り、サインアップ処理を必要です。サインアップ処理に関しては、「Cognito ユーザープールでログインページを作成する – ホストされたUIにアクセスする:サインアップ」をご確認ください。

今回は「awstut」というユーザー名を登録します。サインインページは以下の通りです。

Enter your username and password to sign-in to the Cognito user pool.

メールアドレスおよびパスワードを入力後、「Sign In」を押下します。

If you have successfully signed in, you can access AWS resources.

signin.htmlページが表示されました。ユーザーの名前およびSSMパラメータストアの値が表示されています。前者はIDトークンから取得し、後者はIDプールから一時的なクリデンシャルを使用して、パラメータストアにアクセスして取得できました。

以上より、CognitoユーザープールおよびIDプールを組み合わせることで、認証ユーザーに限定したコンテンツを公開することができました。

認証ユーザー向けコンテンツにアクセスする:エラー時

最後にサインイン処理に失敗、または未実施の場合の挙動を確認します。

先ほどはサインイン用URLを経てsignin.htmlにアクセスしましたが、今度は直接signin.htmlにアクセスします。

If the sign-in fails, you will be treated as a guest user.

signin.htmlが表示されましたが、先ほどは内容が異なります。直接ページにアクセスしたことで、IDトークン情報がなく、クリデンシャル取得に失敗しました。よってユーザー名が「Guest」となり、SSMパラメータストアへもアクセスできませんでした。

以上より、認証ユーザーでなければAWSリソースにアクセスできないことを確認しました。

まとめ

Cognitoユーザープール、ホストされたUIおよびIDプールを組み合わせることで、認証ユーザーのみに限定してAWSリソースにアクセス権限を与えることができることを確認できました。