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

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

そして今回はCodeBuildを使用してHTML・JavaScriptファイルを作成します。

詳細につきましては、以下のページをご確認ください。

あわせて読みたい
CFNのWaitConditionを使用して、Lambdaデプロイパッケージのビルドを待つ 【CFNのWaitConditionを使用して、Lambdaデプロイパッケージのビルドを待つ】 CloudFormationを使用して、Lambda関数を作成することを考えます。 以下のページでご紹介...

本ページでは、CodeBuildのBuildSpecベースでポイントを取り上げます。

npmパッケージインストール

npm install --save-dev webpack
npm install --save-dev path-browserify
npm install -D webpack-cli
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)

webpackとCognito、SSMに関するパッケージをインストールします。

config.js

window.APP_CONFIG = {
  REGION: "[region]",
  USER_POOL_ID: "[user-pool-id]",
  IDENTITY_POOL_ID: "[identity-pool-id]",
  PARAMETER_NAME: "[ssm-parameter-name]"
};
Code language: JavaScript (javascript)

リソースを作成したリージョンやユーザプール・IDプールのID等のパラメータを定義するファイルです。後述のJavaScriptファイルで、本ファイルを読み込みます。

browser.js

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";

// Read config injected at build time
const {
  REGION,
  USER_POOL_ID,
  IDENTITY_POOL_ID,
  PARAMETER_NAME
} = window.APP_CONFIG;

const params = new URLSearchParams(location.hash.slice(1));
const idToken = params.get("id_token");

const PROVIDER = "cognito-idp." + REGION + ".amazonaws.com/" + USER_POOL_ID;

const ssmClient = new SSMClient({
  region: REGION,
  credentials: fromCognitoIdentityPool({
    client: new CognitoIdentityClient({ region: REGION }),
    identityPoolId: IDENTITY_POOL_ID,
    logins: {
      [PROVIDER]: idToken
    }
  })
});

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.';
  }
};

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.getParameter = getParameter;
window.getName = getName;
Code language: JavaScript (javascript)

ファイルの冒頭で先ほどインストールしたパッケージを読み込みます。
次にconfig.jsで定義した変数を読み込みます。

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

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

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

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

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

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

ID トークンの使用

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

webpack.config.js

var path = require("path");
module.exports = {
  entry: [path.join(__dirname, "browser.js")],
  output: {
    path: __dirname,
    filename: "main.js"
  },
   resolve:{
  fallback: { path: require.resolve("path-browserify")}
  }
};
Code language: JavaScript (javascript)

webpackによってbrowser.jsがビルドされ、main.jsとして出力されます。

package.json

{
  "devDependencies": {
    "path-browserify": "^1.0.1",
    "webpack": "^5.104.1",
    "webpack-cli": "^6.0.1"
  },
  "dependencies": {
    "@aws-sdk/client-cognito-identity": "^3.958.0",
    "@aws-sdk/client-ssm": "^3.958.0",
    "@aws-sdk/credential-provider-cognito-identity": "^3.958.0"
  },
  "scripts": {
    "build": "webpack"
  }
}
Code language: JSON / JSON with Comments (json)

色をつけた行がポイントです。ビルドのオプションを指定します。ビルドにはwebpackを使用します。

ビルド

npm run build
Code language: Bash (bash)

main.jsをビルドします。

HTML

index.html

<html>
  <head></head>
  <body>
    <h1>index.html</h1>
    <ul>
      <li>
        <p><a href="https://fa-012.auth.ap-northeast-1.amazoncognito.com/login?response_type=token&client_id=[user-pool-client-id]&redirect_uri=https://s3-ap-northeast-1.amazonaws.com/[bucket-name]/signin.html">Sign In</a></p>
      </li>
      <li>
        <p><a href="https://fa-012.auth.ap-northeast-1.amazoncognito.com/logout?response_type=token&client_id=[user-pool-client-id]&logout_uri=https://s3-ap-northeast-1.amazonaws.com/[bucket-name]/signout.html">Sign Out</a></p>
      </li>
    </ul>
  </body>
</html>
Code language: HTML, XML (xml)

メインページ用のHTMLファイルです。

サインインおよびサインアウト用ページのリンクを配置しています。

signin.html

<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 type="text/javascript" src="./config.js"></script>
    <script>
      getName();
      getParameter();
    </script>
  </body>
</html>
Code language: HTML, XML (xml)

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

signout.html

<html>
  <head></head>
  <body>
    <h1>signout.html</h1>
  </body>
</html>
Code language: HTML, XML (xml)

サインアウト後に表示されるページ用のHTMLファイルです。

S3バケットにアップロード

aws s3 cp . s3://$BUCKET_NAME/ --recursive --exclude "" --include ".js"
aws s3 cp . s3://$BUCKET_NAME/ --recursive --exclude "" --include ".html"
Code language: Bash (bash)

AWS CLIを使用して、上記のJavaScriptおよびHTMLファイルをアップロードします。

S3の静的ウェブサイトホスティング機能

静的ウェブサイトホスティング機能で、S3バケットに設置したHTMLファイルを公開します。

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

あわせて読みたい
S3静的ウェブサイトホスティング機能でサイトを公開する 【S3静的ウェブサイトホスティング機能でサイトを公開する構成】 S3の静的ウェブサイトホスティング機能を使って、ウェブサイトを公開する方法を確認します。 ウェブサ...

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-02a2d030-e61d-11f0-997c-06645dbedfd
  • 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

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

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

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

https://s3-ap-northeast-1.amazonaws.com/fa-012-02a2d030-e61d-11f0-997c-06645dbedfd/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リソースにアクセス権限を与えることができることを確認できました。

目次