Cognitoユーザープールの認証後にIDプールで権限を与える
CognitoユーザープールとIDプールを使用して、サインインユーザーにAWSリソースにアクセスするための権限を付与します。
CognitoユーザープールのホストされたUIを使用して、サインインページを作成します。
Cognitoによる認証・認可を実行するためにJavaScriptを使用します。
今回はAWS SDK for JavaScript v3を使用します。
今回のOauthフローでは、Implicit Grant(暗黙の付与)を選択します。
構築する環境
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については、以下のページをご確認ください。
本ページでは、ポイントとなる点のみを取り上げます。
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を使用して、ブラウザスクリプトを作成する手順は、以下のページをご確認ください。
本ページでは、ポイントとなる箇所を取り上げます。
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スタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- 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が表示されます。
「Sign In」を押下すると、サインインページが表示されます。
初回に限り、サインアップ処理を必要です。サインアップ処理に関しては、「Cognito ユーザープールでログインページを作成する – ホストされたUIにアクセスする:サインアップ」をご確認ください。
今回は「awstut」というユーザー名を登録します。サインインページは以下の通りです。
メールアドレスおよびパスワードを入力後、「Sign In」を押下します。
signin.htmlページが表示されました。ユーザーの名前およびSSMパラメータストアの値が表示されています。前者はIDトークンから取得し、後者はIDプールから一時的なクリデンシャルを使用して、パラメータストアにアクセスして取得できました。
以上より、CognitoユーザープールおよびIDプールを組み合わせることで、認証ユーザーに限定したコンテンツを公開することができました。
認証ユーザー向けコンテンツにアクセスする:エラー時
最後にサインイン処理に失敗、または未実施の場合の挙動を確認します。
先ほどはサインイン用URLを経てsignin.htmlにアクセスしましたが、今度は直接signin.htmlにアクセスします。
signin.htmlが表示されましたが、先ほどは内容が異なります。直接ページにアクセスしたことで、IDトークン情報がなく、クリデンシャル取得に失敗しました。よってユーザー名が「Guest」となり、SSMパラメータストアへもアクセスできませんでした。
以上より、認証ユーザーでなければAWSリソースにアクセスできないことを確認しました。
まとめ
Cognitoユーザープール、ホストされたUIおよびIDプールを組み合わせることで、認証ユーザーのみに限定してAWSリソースにアクセス権限を与えることができることを確認できました。