Cognito IDプールでゲスト/サインインユーザーで権限を分ける
Cognito IDプールは認証済みユーザーに加えて、未認証のゲストユーザーに対しても、一時的なクリデンシャルを生成することができます。
今回はゲスト・サインインユーザーに対して、異なる権限を与える構成を確認します。
構築する環境
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関数の定義方法の詳細については、以下のページをご確認ください。
ランタイム環境はPython3.9を選択し、それぞれ以下を実行するように設定します。
- サインインユーザー用関数:本日の日付を返す。
- ゲストユーザー用関数:現在の時刻を返す。
ブラウザスクリプト
JavaScriptを使用して、サインイン後のCognito機能を実装します。
AWS SDK for JavaScript v3を使用してブラウザスクリプトを作成する手順は、以下のページをご確認ください。
本ページでは、ゲスト・サインインユーザーの振り分けに関する内容を中心に取り上げます。
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)
詳細は以下のページをご確認ください。
ユーザーに応じて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スタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
S3バケットにコンテンツを設置する
S3バケットにHTML・JavaScriptファイルを設置します。
この2ファイルがサインイン/サインアウト後のコンテンツです。
検証:ゲストユーザーでアクセス
準備が整いましたので、実際にコンテンツにアクセスします。
以下のURLにアクセスします。
https://s3-ap-northeast-1.amazonaws.com/dva-03-003/index.html
現在の時刻が表示されました。
これは現時点ではサインインしていませんので、ゲストユーザーとして本ページにアクセスしていることになり、ゲストユーザー用のLamabda関数が実行されたということです。
検証:サインインユーザーでアクセス
続いてサインアップ/サインイン処理を行います。
詳細は以下のページをご確認ください。
サインアップ/サインインが完了しましたら、改めて本ページにリダイレクトされます。
現在の日付が表示されました。
これはサインイン処理によって、IDトークンが生成され、サインインユーザー用のLambda関数が実行されたということです。
(参考)IDプール
参考までに、ここまでの手順で生成されたIdentityを確認します。
ご覧の通り、2つのIdentityが作成されていることがわかります。
つまりサインインユーザーだけでなく、ゲストユーザー用のIdentityも生成され、それを使ってゲスト用のLambda関数が実行されたということです。
まとめ
ゲスト・サインインユーザーで権限を分ける方法を確認しました。
具体的には、IDプールでゲスト・サインインユーザー用のIAMロールをアタッチした上で、IDトークンの有無によって処理を分岐させる方法です。