はじめに
注文アプリケーションには、ユーザーをサインインさせる方法が必要です。このラボでは、ユーザーディレクトリと公開アプリクライアントを作成し、架空のユーザーでサインインします。発行されたトークンを検証し、セッションを更新してから後片付けをします。
最初に 注文 API を構築して検証する を完了してください。この新しい環境には、独自の公開バックエンド、使い捨てのサインイン入力、トークン検証の例が用意されています。パスワードとトークンのファイルは非公開にしてください。
認定試験との関連
| 認定資格 | 試験タスク | 演習内容 |
|---|---|---|
| Solutions Architect – Associate (SAA-C03) | タスク 1.2 | Cognito のサインイン、トークン検証、セッション更新。 |
| Developer – Associate (DVA-C02) | タスク 2.1 | Cognito のサインイン、トークン検証、セッション更新。 |
| Security – Specialty (SCS-C03) | タスク 4.1 | 基礎演習:Cognito のサインイン、トークン検証、セッション更新。 |
ラボの概要

アプリケーションのユーザーディレクトリを作成する
このステップでは、ユーザープール、公開アプリクライアント、確認済みの架空のユーザーを用意します。
作業ディレクトリに移動し、umask 077 を使って、新しく作成するファイルを自分のユーザーだけがアクセスできるようにします。これはファイル権限に影響し、API の認可ルールには影響しません。
cd /home/labex/project
umask 077
Cognito ユーザープールは、アプリケーションのユーザーディレクトリです。そのユーザーはアプリケーションにサインインします。一方、準備済みの IAM CLI アイデンティティは、クラウドリソースを管理します。上部の AWS View を確認し、これらのサービスのリソースを比較してください。
所有するユーザープールを作成します。$(...) は、返されたプール ID を保存し、後続のサービスコマンドで使えるようにします。
POOL_ID=$(aws cognito-idp create-user-pool \
--pool-name labex-a04-users \
--query UserPool.Id \
--output text)
echo "$POOL_ID"
その ID を記録します。> は、値をリソース一覧のファイルに書き込みます。
printf '%s\n' "$POOL_ID" > pool-id.txt
アプリクライアントは、このプールを通じてユーザーをサインインさせるアプリケーションを識別します。公開アプリケーションは、クライアントシークレットを秘密に保てません。シークレットなしでこのクライアントを作成し、この手順で使うユーザー名とパスワードのフロー、およびリフレッシュトークンのフローを許可します。
CLIENT_ID=$(aws cognito-idp create-user-pool-client \
--user-pool-id "$POOL_ID" \
--client-name orders-client \
--no-generate-secret \
--explicit-auth-flows ALLOW_USER_PASSWORD_AUTH ALLOW_REFRESH_TOKEN_AUTH \
--query UserPoolClient.ClientId \
--output text)
echo "$CLIENT_ID"
printf '%s\n' "$CLIENT_ID" > client-id.txt
秘密の値を要求せずに、安全なクライアント設定を確認します。
aws cognito-idp describe-user-pool-client \
--user-pool-id "$POOL_ID" \
--client-id "$CLIENT_ID" \
--query '{Name:UserPoolClient.ClientName,Flows:UserPoolClient.ExplicitAuthFlows}'
メール送信を抑制して、実演用ユーザーを作成します。これは管理者がユーザーを用意する操作であり、後でユーザーが行うサインインとは異なります。
aws cognito-idp admin-create-user \
--user-pool-id "$POOL_ID" \
--username labex-demo \
--message-action SUPPRESS \
--user-attributes Name=email,Value=demo@example.test \
--query '{Username:User.Username,Status:User.UserStatus}'
初期状態は FORCE_CHANGE_PASSWORD です。この使い捨てのテストユーザーだけに対して、提供された恒久パスワードの入力ファイルを CLI で適用します。ファイルには Username、Password、Permanent フィールドがあり、file:// 構文でパスワードを表示せずに読み込めます。
aws cognito-idp admin-set-user-password \
--user-pool-id "$POOL_ID" \
--cli-input-json file://demo-password.json
安全なユーザー状態だけを確認します。
aws cognito-idp admin-get-user \
--user-pool-id "$POOL_ID" \
--username labex-demo \
--query '{Username:Username,Status:UserStatus,Enabled:Enabled}'
labex-demo、CONFIRMED、true が返るはずです。AWS View で、所有するプール ID、orders-client、クライアントシークレットがないこと、確認済みで有効なユーザーを比較します。実際のサービスのプール、クライアント、ユーザーの状態の例です。

無関係な labex-a04-reference プールは空のままです。ユーザートークンは、まだ要求していません。
サインインし、アプリケーションのアイデンティティを読み取る
このステップでは、確認済みユーザーの実際のトークンを取得し、誤ったパスワードの拒否を観察します。
USER_PASSWORD_AUTH を使う InitiateAuth は、アプリケーションのユーザー名とパスワードを認証します。CLI の IAM 管理者アイデンティティを通じて、ユーザーを認可するものではありません。提供された sign-in.json には、この実演の架空のユーザー名とパスワードのパラメータが含まれます。
AuthenticationResult だけを非公開のファイルに保存します。このレスポンスには Bearer トークンが含まれるため、cat で表示しないでください。
aws cognito-idp initiate-auth --client-id "$CLIENT_ID" --auth-flow USER_PASSWORD_AUTH --auth-parameters file://sign-in.json --query AuthenticationResult --output json > tokens.json
jq で、秘密ではないレスポンスのフィールドだけを表示します。トークンの存在を確認する式は、トークンの値ではなく真偽値を返します。
jq '{TokenType,ExpiresIn,TokensPresent:(has("AccessToken") and has("IdToken") and has("RefreshToken"))}' tokens.json
Bearer、正の有効期間、TokensPresent: true が返るはずです。アクセストークンは、適切なユーザーや API のリクエストを認可します。ID トークンは、アプリケーションにアイデンティティ情報を渡します。リフレッシュトークンは、新しいアクセストークンと ID トークンを取得できます。それぞれ目的が異なり、相互に置き換えられるものではありません。
アクセストークンと ID トークンを、受け渡し用の非公開ファイルに取り出します。jq -r は文字列をそのまま書き込み、リダイレクトによってターミナルに表示させません。
jq -r '.AccessToken' tokens.json > access-token.txt
jq -r '.IdToken' tokens.json > id-token.txt
GetUser は、アクセストークンで認証します。出力にはユーザー名だけを要求します。
aws cognito-idp get-user \
--access-token "$(cat access-token.txt)" \
--query Username \
--output text
labex-demo が返るはずです。これは、発行されたトークンに対する実際の Cognito のレスポンスであり、ローカルの JSON ファイルだけから推測した証拠ではありません。
提供された誤ったパスワードのパラメータを試します。予期しないトークンレスポンスが返った場合も、別の非公開ファイルにリダイレクトします。
aws cognito-idp initiate-auth --client-id "$CLIENT_ID" --auth-flow USER_PASSWORD_AUTH --auth-parameters file://wrong-sign-in.json --query AuthenticationResult --output json > rejected-login.json
NotAuthorizedException: Incorrect username or password が返るはずです。このコマンドは、意図的にゼロ以外の終了コードで終了します。その正確な拒否を読んでから、次のステップに進んでください。接続や認証通信のエラーは、誤ったパスワードが拒否された証拠ではありません。この失敗の結果で、有効なトークンを上書きしないでください。このステップの自動チェックは、発行されたトークンと実際の GetUser の結果を独立して検証します。特定の誤ったパスワードのエラーは、手動で観察してください。

署名付きトークンを検証し、セッションを更新する
このステップでは、トークンの署名と意図された受信先を検証し、セッションを更新して、サインインと API ルートの保護を区別します。
JWT には、エンコードされたヘッダーとクレームのデータ、および署名が含まれます。デコードするだけでは、誰が発行したかは証明できません。JWKS は発行者の公開署名キーの集合です。RS256 は、それらの公開キーで検証できる RSA 署名を使います。
提供された通常のアプリケーションの検証例を読みます。パスワードやトークンのリテラルは含まれていません。
cat verify_tokens.py
この例は、準備済みの作業環境のエンドポイントからプールの公開 JWKS を取得し、RS256 とキー ID を検証してから、発行者、現在時刻、token_use、アプリクライアントを確認します。署名された発行者は、Cognito プールのアイデンティティを使います。アクセストークンは client_id でクライアントを識別し、ID トークンは aud を使います。保存したトークンファイルと、実際のプールおよびクライアントの ID で実行します。
/home/labex/project/client-venv/bin/python verify_tokens.py tokens.json "$POOL_ID" "$CLIENT_ID"
access と id の安全な概要が返り、どちらも RS256 と current_time_valid: true を示すはずです。クレームを受け入れる前に、メモリー内で署名の 1 バイトを変更して拒否を確認する処理も行います。改ざんしたトークンは保存しません。アクセストークンの aws.cognito.signin.user.admin スコープは、Cognito のユーザーセルフサービス操作を許可します。このスコープは、AWS 管理者のアクセス、注文の所有権ルール、独自の業務スコープではありません。
元のリフレッシュトークンから、非公開の更新パラメータを作成します。これはファイルに書き込み、値を公開しません。
jq '{REFRESH_TOKEN:.RefreshToken}' tokens.json > refresh-parameters.json
ユーザーのパスワードを再送せずに、新しいアクセストークンと ID トークンを取得します。
aws cognito-idp initiate-auth --client-id "$CLIENT_ID" --auth-flow REFRESH_TOKEN_AUTH --auth-parameters file://refresh-parameters.json --query AuthenticationResult --output json > refreshed.json
同じ公開キーとクレームのチェックで、更新したトークンを検証します。
/home/labex/project/client-venv/bin/python verify_tokens.py refreshed.json "$POOL_ID" "$CLIENT_ID"
同じプールとクライアントに対する、有効な access と id の概要が返るはずです。更新レスポンスに、別のリフレッシュトークンが含まれる必要はありません。削除するまでは、元の非公開レスポンスを保持してください。
サインインだけでは、準備済みの公開 API は変わりません。生成された ID を見つけ、Bearer トークンなしで公開のヘルスリクエストを送ります。
API_ID=$(aws apigatewayv2 get-apis \
--query "Items[?Name=='labex-a04'].ApiId | [0]" \
--output text)
echo "$API_ID"
printf '%s\n' "$API_ID" > api-id.txt
curl -i "http://127.0.0.1:8081/api/$API_ID/health"
HTTP 200 と healthy: true が返るはずです。AWS View には、すべてのルートで認可が NONE、注文アイテムがない状態が引き続き表示されます。ユーザー認証は成功しましたが、API はまだトークンを要求していません。次の単元で、JWT によるルート認可を追加します。有効なトークンがあっても、特定の注文の所有権が自動的に確立されるわけではありません。
更新は、同じアプリクライアントのログインセッションを継続します。API ルートの認可設定を変更したり、注文へのアクセス権を与えたりする操作ではありません。更新後の ID 概要と公開のままのヘルスレスポンスを比較するときは、これらの判断を分けて考えてください。
サインインと API のリソースを削除する
このステップでは、無関係な参照プール、テーブル、ログを保持しながら、所有するサインインリソースと、バックエンドの準備用リソースを削除します。
リソース一覧は、pool-id.txt の labex-a04-users、client-id.txt の orders-client、labex-demo ユーザー、api-id.txt の API ID、labex-a04-orders-api、その関数ロググループ、labex-a04-orders、ロールの OrdersData ポリシーです。これらの名前付きリソースだけを削除してください。
架空のユーザーを削除し、その後、アプリクライアントと所有するプールを削除します。
aws cognito-idp admin-delete-user --user-pool-id "$POOL_ID" --username labex-demo
aws cognito-idp delete-user-pool-client --user-pool-id "$POOL_ID" --client-id "$CLIENT_ID"
aws cognito-idp delete-user-pool --user-pool-id "$POOL_ID"
準備済みの動作する API と、その所有するバックエンドリソースを削除します。
aws apigatewayv2 delete-api --api-id "$API_ID"
aws lambda delete-function --function-name labex-a04-orders-api
aws logs delete-log-group --log-group-name /aws/lambda/labex-a04-orders-api
aws dynamodb delete-table \
--table-name labex-a04-orders \
--query TableDescription.TableName \
--output text
aws iam delete-role-policy --role-name labex-a04-execution --policy-name OrdersData
サービスから正常に一覧をクエリします。通信の失敗は、不在の証明にはなりません。
aws cognito-idp list-user-pools --max-results 60 --query 'UserPools[].Name'
aws apigatewayv2 get-apis --query 'Items[].Name'
aws lambda list-functions --query 'Functions[].FunctionName'
aws dynamodb list-tables --query TableNames
参照プールと参照テーブルだけが残り、API と関数のリストは空になるはずです。AWS View も同じ状態を示し、参照アイテムと INFO platform reference keep unchanged を保持していなければなりません。準備済みの実行ロールには、FunctionLogs が残ります。
提供された架空のパスワード入力も含め、自分の非公開の認証ファイルを削除します。rm -f は、明示的に列挙したローカルファイルだけを削除します。
rm -f tokens.json access-token.txt id-token.txt refreshed.json refresh-parameters.json rejected-login.json sign-in.json wrong-sign-in.json demo-password.json
ローカルファイルの削除だけでは、サーバー側のセッションは失効しません。ここでは、架空のユーザー、クライアント、プールも削除しました。このラボは、一般的な JWT のログアウトや失効の仕組みを実装したとは主張しません。制作者による後片付けと VM の終了まで、無関係な参照リソースを保持してください。
まとめ
アプリケーションのユーザーディレクトリと公開アプリクライアントを作成し、架空のユーザーを用意してサインインし、誤ったパスワードの拒否を観察しました。実際の RS256 署名と、発行者、クライアント、時刻、種類のクレームを検証し、実際のユーザーアイデンティティを読み取り、アクセストークンと ID トークンを更新しました。Cognito ユーザーと IAM アイデンティティ、サインインと API の認可を区別し、最後に、参照データを保持しながら、所有するリソースと非公開ファイルを削除しました。
詳しくは、AWS 公式ドキュメントを参照してください:ユーザープールトークンの用途; JWT 署名とクレームの検証; セッション更新とリフレッシュトークンフロー。



