この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
この記事はアノテーション株式会社 AWS Technical Support Advent Calendar 2022 | Advent Calendar 2022 - Qiita 16日目の記事です。
困っていた内容
各画像(A, B)が異なる S3 バケットに保管されている状況下で下記手順のように、② の Web ページを仲介して S3 バケットを跨ぐ形で各画像にアクセスを行っても、Cognito ユーザープールで認証された状態は維持されるのか教えて下さい。
- S3 バケット A
- 画像 A が保管されている
- S3 バケット B
- 画像 B が保管されている
- 手順
- Cognito ユーザープールに対して認証を行います
- 認証成功後、画像 A と画像 B のリンクが表示された Web ページに遷移する
- 2.の Web ページで画像 A のリンクをクリックして、画像 A を表示させる
- 3.と同じ様に 2.の Web ページで画像 B のリンクをクリックして、画像 B を表示させる
Cognito の認証された状態は維持されるの?
はい、S3 バケットを跨いでのアクセスには関係なく、JSON Web トークン (JWT)の有効期限内は Amazon Cognito によって認証された状態となり、トークン の有効期限内は維持(保持)される仕組みとなっています。
Cognito ユーザープールに認証された状態とは?
Cognito ユーザープールに対して、ユーザーが正常に認証されると AWS 認証情報と交換することができる JSON Web トークン (JWT) を Amazon Cognito が発行します。
Amazon Cognito user pools - Amazon Cognito
ユーザーが正常に認証されると、ユーザー独自の API へのアクセスをセキュア化して承認するために使用する、または AWS 認証情報と交換することができる JSON Web トークン (JWT) を Amazon Cognito が発行します。
ユーザーはこの発行されたトークンを使用することで、自身が Cognito ユーザープールで認証された状態であることを示すとともに、各 AWS サービスのリソースにアクセスするために必要な一時的な AWS 認証情報と交換します。
サインイン後に ID プールを使用して AWS サービスへアクセスする - Amazon Cognito
ユーザーがユーザープールを使用してサインインし、その後 ID プールを使用して AWS のサービスにアクセスできるようにすることが可能です。
認証が正常に行われると、ウェブまたはモバイルアプリが Amazon Cognito からユーザープールトークンを受け取ります。
これらのトークンを使用して、アプリケーションが AWS のその他サービスにアクセスできるようにする AWS 認証情報を取得できます。
また、各 AWS サービスのアクセス先のリソースでは、この発行されたトークンを検証することで、その有効性を確認します。
JSON web トークンの検証 - Amazon Cognito
JWT クレームを検証する
- トークンの有効期限が切れていないことを確認します。
ID トークンの aud クレームとアクセストークンの client_id クレームは、Amazon Cognito ユーザープールで作成されたアプリクライアント ID と一致する必要があります。
発行者 (iss) のクレームは、ユーザープールと一致する必要があります。例えば、us-east-1 リージョンで作成されたユーザープールには、以下の iss 値があります。https://cognito-idp.us-east-1.amazonaws.com/.
token_use クレームをチェックします。
・Web API オペレーションでアクセストークンのみを受け入れている場合は、その値を access にする必要があります。
・ID トークンのみを使用している場合、その値は id にする必要があります。
・ID とアクセストークンのいずれも使用している場合、token_use クレームは、id または access になります。
以上より、Cognito ユーザープールに認証された状態とは、Amazon Cognito によって発行されたトークンが有効である状態を示します。
検証して確認してみた
上記の状況を再現して、S3 バケットを跨いでのアクセスには関係なく、JSON Web トークン (JWT)の有効期限内は Amazon Cognito によって認証された状態となり、トークン の有効期限内は維持されるのか実際に検証して確認してみます。
検証に必要な各 AWS リソース等を下記の順番で準備・作成していきます。
- Amazon S3
- Amazon Cognito
- AWS IAM
- AWS Cloud9
Amazon S3
バケットの作成
検証用に子猫の画像を格納する「test-bucket-cat」と、子犬の画像を格納する「test-bucket-dog」をバケット名とする、2 つの S3 バケットを作成します。
以降では子猫の画像を格納する S3 バケット(test-bucket-cat)の作成および各種設定手順を例に説明します。
- バケット名(命名要件に従い任意の名前を入力してください)
- “test-bucket-cat” or “test-bucket-dog”
- AWS リージョン
- アジアパシフィック(東京)ap-northeast-1
- オブジェクト所有者
- ACL 無効(推奨)
- このバケットのブロックパブリックアクセス設定
- パブリックアクセスをすべて ブロック にチェック
- バケットのバージョニング
- 無効にする
- タグ
- 設定なし
- デフォルトの暗号化
- サーバー側の暗号化 → 無効にする
- 詳細設定
- オブジェクトロック → 無効にする
Cross-Origin Resource Sharing (CORS)の設定
2つの S3 バケットに保管された画像に確認用 Web ページ からアクセスする際に必要となる CORS の設定を行います。
- Amazon S3 > バケット > [作成したバケット名] の順にアクセス
- [アクセス許可]のタブを選択
- 画面最下部の[Cross-Origin Resource Sharing (CORS)] > 編集 を押下
- 下記の CORS の設定をコピーして、編集エリアに貼り付けて、[変更の保存]を押下
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET",
"HEAD"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": []
}
]
アクセスポイントの作成
確認用 Web ページのプログラムから、S3 バケットに対する「listObjects」メソッドと「getObject」メソッドの引数として指定するため、アクセスポイントを作成します。
- アクセスポイント名(命名要件に従い任意の名前を入力してください)
- test-bucket-cat バケット: cat
- test-bucket-dog バケット: dog
- ネットワークオリジン
- インターネット
- [アクセスポイントの作成]を押下
作成した S3 バケットに画像を格納
検証確認用の画像を作成した S3 バケットに保管します。
今回はフリー画像素材の pixabay から2つの画像(子猫と子犬)をダウンロードして使用したいと思います。
- Amazon S3 > バケット > test-bucket-cat の順にアクセス
- [オブジェクト]タブを選択して、ドラッグアンドドロップで画像を格納します(今回は検証確認で分かりやすくするため以下のように画像を各 S3 バケットに保管しています)
- "test-bucket-cat" バケットには「子猫」の画像
- "test-bucket-dog" バケットには「子犬」の画像
- アップロード画面に自動で遷移されましたら、アップロードした画像を確認して問題なければ画面右下の[アップロード]を押下
Amazon Cognito
今回の検証用で使用する Cognito ユーザープールと ID プールを作成します。
ユーザープールの作成
- サインインエクスペリエンスを設定
- プロバイダーのタイプ
- Cognito ユーザープール
- Cognito ユーザープールのサインインオプション
- E メール
- プロバイダーのタイプ
- セキュリティ要件を設定
- パスワードポリシー
- パスワードポリシーモード
- Cognito のデフォルト
- パスワードポリシーモード
- 多要素認証
- MFA なし
- パスワードポリシー
- ユーザーアカウントの復旧
- デフォルト設定のままでOK
- サインアップエクスペリエンスを設定
- デフォルト設定のままでOK
- メッセージ配信を設定
- Cognito で E メールを送信
- アプリケーションを統合
- ユーザープール名(命名要件に従い任意の名前を入力してください)
- TestUserPool
- ホストされた認証ページ
- Cognito のホストされた UI を使用(今回の検証では使用しません : チェック不要)
- ユーザープール名(命名要件に従い任意の名前を入力してください)
- 最初のアプリケーションクライアント
- アプリケーションタイプ
- パブリッククライアント
- アプリケーションクライアント名(命名要件に従い任意の名前を入力してください)
- TestAppClient
- クライアントのシークレット
- クライアントのシークレットを生成しない
- アプリケーションタイプ
- 高度なアプリケーションクライアントの設定
- 認証フロー
- ALLOW_REFRESH_TOKEN_AUTH(デフォルト設定)
- ALLOW_USER_SRP_AUTH
- 認証フローセッション持続期間
- 3 分(デフォルト設定)
- 更新トークンの有効期限
- 30 日(デフォルト設定)
- アクセストークンの有効期限
- 5 分
- ID トークンの有効期限
- 5 分
- 高度なセキュリティ設定 - オプション
- デフォルト設定のままでOK
- 認証フロー
- 属性の読み取りおよび書き込み許可
- デフォルト設定のままでOK
ID プールの作成
- 新しい ID プールの作成
- ID プール名(命名要件に従い任意の名前を入力してください)
- TestIdPool
- ID プール名(命名要件に従い任意の名前を入力してください)
- 認証されていない ID
- 今回の検証では使用しません(チェック不要)
- 認証フローの設定(基本 (クラシック) フローを許可する)
- 今回の検証では使用しません(チェック不要)
- 認証プロバイダー
- [Cognito] のタブを選択
- ユーザープール ID
- ap-northeast-1_*********
- アプリクライアント ID
- Amazon Cognito > ユーザープール > [作成したユーザープール] > [アプリケーションの統合]タブを選択
- 画面最下部の [アプリクライアントと分析] の クライアントID を指定
- Amazon Cognito > ユーザープール > [作成したユーザープール] > [アプリケーションの統合]タブを選択
- ユーザープール ID
- [Cognito] のタブを選択
- Identify the IAM roles to use with your new identity pool
- Cognito ユーザープールに対して認証されたユーザーに対する IAM ロールは後ほど設定しますので、右下の[許可]を選択でOKです
- IdentityPoolId は後ほど使用するのでメモする等控えておいてください
AWS IAM
Cognito_[作成した ID プール名]Auth_Role の編集
- IAM > ロール > Cognito_[作成したID プール名]Auth_Role に移動する
- 該当のポリシー名付近の+ボタンをクリックして展開後、[編集]を押下
- [JSON]タブを選択して、下記の IAM ポリシーをコピーして、編集エリアに貼り付けて、[ポリシーの確認]を押下
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"*"
]
}
]
}
- [変更の保存]を押下
AWS Cloud9
Cloud9 環境を作成
- Details
- Name(命名要件に従い任意の名前を入力してください)
- Environment type
- New EC2 instance(デフォルト設定)
- New EC2 instance
- Instance type
- t3.small (2 GiB GiB RAM + 2 vCPU)
- Platform
- Amazon Linux 2
- Timeout
- 30 minutes
- Instance type
- Network settings
- デフォルト設定のままでOK
- Tags
- 設定不要
検証用ソースコードの準備
- 検証ソースコードのダウンロード
- 今回の検証用のソースコードはこちらの記事を参考にさせていただきました
git clone https://github.com/junjis0203/restricted-s3-access-example.git
- 検証用にソースコードを編集
- 今回の検証用のため、ソースコードを一部下記のように書き換えます
config.js
- 下記の例を参考に各変数にリソースID(ARN)等の値(文字列)を設定する
// リージョン
const REGION = 'ap-northeast-1'
// S3 - バケットアクセスポイントのARN
const S3_BUCKET_CAT = 'arn:aws:s3:ap-northeast-1:012345678912:accesspoint/cat'
const S3_BUCKET_DOG = 'arn:aws:s3:ap-northeast-1:012345678912:accesspoint/dog'
// Cognito - ユーザープール ID、クライアント ID、ID プールの ID
const USER_POOL_ID = 'ap-northeast-1_*********'
const APP_CLIENT_ID = '1a2bc3d45efg6hijkl7m8no9pq'
const IDENTITY_POOL_ID = 'ap-northeast-1:1a234bc5-67d8-9ef0-g1hi-2jk3lmn4567o'
index.html
- 今回の検証用に一部変更や追記した下記ソースコードをコピーしてご活用ください
<html>
<head>
</head>
<body>
<div id="app">
</div>
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.734.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/amazon-cognito-identity-js@4.3.3/dist/amazon-cognito-identity.min.js"></script>
<script src="config.js"></script>
<script src="common.js"></script>
<script>
const cognitoUser = userPool.getCurrentUser();
if (!cognitoUser) {
document.location.href = 'signin.html'
}
cognitoUser.getSession(function(err, result) {
if (err) {
console.error(err);
return
}
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: IDENTITY_POOL_ID,
Logins: {
[LOGINS_KEY]: result.getIdToken().getJwtToken()
},
})
AWS.config.credentials.refresh(function(err) {
if (err) {
console.error(err)
return
}
console.log('Refreshing AWS credentials is succeed')
getImagesCat()
getImagesDog()
})
})
const s3 = new AWS.S3()
function getImagesCat() {
s3.listObjects({Bucket: S3_BUCKET_CAT}, function(err, data) {
if (err) {
console.error(err)
return
}
const ul = document.createElement('ul')
data.Contents.forEach(function(content) {
if (content.Key.endsWith('.jpg')) {
const li = document.createElement('li')
const a = document.createElement('a')
const br = document.createElement('br')
const img = document.createElement('img')
a.href = '#'
a.innerHTML = content.Key
a.addEventListener('click', function(e) {
e.preventDefault()
s3.getObject({Bucket: S3_BUCKET_CAT, Key: content.Key}, function(err, data) {
if (err) {
console.error(err)
return
}
console.log(data)
const blob = new Blob([data.Body], {type: data.ContentType})
img.src = URL.createObjectURL(blob)
})
})
li.appendChild(a)
li.appendChild(br)
li.appendChild(img)
ul.appendChild(li)
}
})
const app = document.getElementById('app')
app.appendChild(ul)
})
}
function getImagesDog() {
s3.listObjects({Bucket: S3_BUCKET_DOG}, function(err, data) {
if (err) {
console.error(err)
return
}
const ul = document.createElement('ul')
data.Contents.forEach(function(content) {
if (content.Key.endsWith('.jpg')) {
const li = document.createElement('li')
const a = document.createElement('a')
const br = document.createElement('br')
const img = document.createElement('img')
a.href = '#'
a.innerHTML = content.Key
a.addEventListener('click', function(e) {
e.preventDefault()
s3.getObject({Bucket: S3_BUCKET_DOG, Key: content.Key}, function(err, data) {
if (err) {
console.error(err)
return
}
console.log(data)
const blob = new Blob([data.Body], {type: data.ContentType})
img.src = URL.createObjectURL(blob)
})
})
li.appendChild(a)
li.appendChild(br)
li.appendChild(img)
ul.appendChild(li)
}
})
const app = document.getElementById('app')
app.appendChild(ul)
})
}
</script>
</body>
</html>
Cognito ユーザープールに対して認証を行う検証用ユーザーの作成
- 検証用ユーザーを作成するシェルスクリプトファイルの作成および編集
$ touch create-testuser.sh
$ vi create-testuser.sh #(もしくは、Cloud9のエディタでファイルを開いて編集)
- create-testuser.sh に以下を記述して保存
USER_POOL_ID=ap-northeast-1_*********
USER_NAME=<メールアドレス>
MAIL=<USER_NAMEと同じメールアドレス>
$ aws cognito-idp admin-create-user \
--user-pool-id ${USER_POOL_ID} \
--username ${USER_NAME} \
--temporary-password <任意の一時的なパスワードを入力> \
--user-attributes \
Name=email,Value=${MAIL} \
Name=email_verified,Value=True
- create-testuser.sh を編集後、以下を実施
$ chmod +x create-testuser.sh
$ ./create-testuser.sh
{
"User": {
"Username": "a123bc45-abc1-1234-5678-12ab345cd6ef",
"Enabled": true,
"UserStatus": "FORCE_CHANGE_PASSWORD",
"UserCreateDate": **********.***,
"UserLastModifiedDate": **********.***,
"Attributes": [
{
"Name": "sub",
"Value": "a123bc45-abc1-1234-5678-12ab345cd6ef"
},
{
"Name": "email_verified",
"Value": "True"
},
{
"Name": "email",
"Value": "test@gmail.com"
}
]
}
}
検証
[Preview] をより確認
- Cloud9 より index.html を右クリックして、[Preview] を選択
ログイン
- Email と Password を入力して、[sign in] をクリック
- New Password が表示されるため、新しいパスワードを入力して、[change password] をクリック
認証成功後
- 認証が成功すると、各バケットに保管されている画像へのリンクが表示されます
- Google Chrome デベロッパー ツールにおいて、ローカルストレージ内に、各種 JSON Web トークン (JWT)が取得できていることも確認できます
リンクをクリックして、異なる S3 バケットに保管されている画像を確認用 Web ページから表示してみる
- 子猫の画像
- 子犬の画像
JSON Web トークン (JWT)の有効期限が切れた場合の挙動確認
今回の検証では、JSON Web トークン (JWT)における、ID トークン(および、アクセストークン)の有効期限を 5 分に設定しているため、5分を経過した場合にどのような挙動になるか確認してみました。
1. トークンの有効期限が切れたとき - 画面の表示
- 以下のとおり、画面には何も表示されなくなります
2. トークンの有効期限が切れたとき - コンソールログ
- 以下のとおり、”CredentialsError” が記録されていることを確認しました
CredentialsError: Missing credentials in config, if using AWS_CONFIG_FILE, set AWS_SDK_LOAD_CONFIG=1
【補足】 ”CredentialsError” 後に、S3 バケットに格納している画像のリンクを再度表示させるには・・・
- Cloud9の [Preview] を ”Refresh” すると、ローカルストレージに保管されている 更新トークン(リフレッシュトークン)を使用して再度新しい ID トークンとアクセストークンを取得することができます
- その後、各 S3 バケットに保管されている画像へのリンクが再び表示されます
おまけ
JSON Web トークン (JWT) を検証するソースコードは GitHub - awslabs/aws-jwt-verify: JS library for verifying JWTs signed by Amazon Cognito, and any OIDC-compatible IDP that signs JWTs with RS256, RS384, and RS512 より、Amazon Cognito によって署名された JWT を検証するための JavaScript ライブラリが公開されています。
今回の検証で Amazon Cognito ユーザープールから取得した JSON Web トークン (JWT) を使用して、Cloud9 上から実行して確認してみました。
ソースコード - aws-jwt-verify.mjs
import { CognitoJwtVerifier } from "aws-jwt-verify";
// Verifier that expects valid access tokens:
const verifier = CognitoJwtVerifier.create({
userPoolId: "ap-northeast-1_*********",
tokenUse: "id",
clientId: "1a2bc3d45efg6hijkl7m8no9pq",
});
try {
const payload = await verifier.verify(
"[ID トークンを入力してください]"
);
console.log("Token is valid. Payload:", payload);
} catch {
console.log("Token not valid!");
}
実行例 - 有効期限内の ID トークンの場合
$ node aws-jwt-verify.mjs
Token is valid. Payload: {
sub: '1234abcd-123a-1234-abcd-abcd1234abcd',
email_verified: true,
iss: 'https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_*********',
'cognito:username': '1234abcd-123a-1234-abcd-abcd1234abcd',
origin_jti: '5678abcd-567a-5678-abcd-abcd5678abcd',
aud: '1a2bc3d45efg6hijkl7m8no9pq',
event_id: '1256abcd-125a-1256-abcd-abcd1256abcd',
token_use: 'id',
auth_time: **********,
exp: **********,
iat: **********,
jti: 'ab1234dd-rta1-45f1-kd9d-89dsd3bjs9np',
email: 'test@gmail.com'
}
実行例 - 有効期限切れの ID トークンの場合
$ node aws-jwt-verify.mjs
Token not valid!
まとめ
- S3 バケットを跨いでのアクセスには関係なく、JSON Web トークン (JWT)の有効期限内は Amazon Cognito(ユーザープール) によって認証された状態となり、トークンの有効期限内は維持(保持)されること
- Amazon Cognito(ユーザープール)に認証された状態とは、Amazon Cognito(ユーザープール)によって発行されたトークンが有効である状態を示すこと
この記事が少しでも誰かのお役にたてば幸いです。
参考資料
-
Cross−Origin Resource Sharing (CORS) の使用 - Amazon Simple Storage Service
-
Amazon S3 アクセスポイントを使用したデータアクセスの管理 - Amazon Simple Storage Service
-
Class: AWS.CognitoIdentityServiceProvider — AWS SDK for JavaScript
-
GitHub - aws/aws-sdk-js: AWS SDK for JavaScript in the browser and Node.js
-
amplify-js/packages/amazon-cognito-identity-js at main · aws-amplify/amplify-js · GitHub