垣見です。 AWS IAM Identity Centerの一時的な権限昇格を実現するOSSソリューション「Temporary elevated access management(以下、TEAM)」ですが、「結局どういう仕組みで動いてるの?」と気になった方も多いのではないでしょうか。
今回は、TEAMの仕組みを掘り下げて解説していきます。
TEAMを使っていない方でも、DynamoDB Streamsを使ったイベント駆動型サーバレスシステムの実装例としても面白いかもしれません。
- はじめに
- TEAMの構成
- 申請の状態推移 ― 「ステータス」について
- サービスの動き
- teamRouter 関数について
- TEAMのロジック ― ステートマシンについて
- TEAMのDynamoDBテーブル一覧
- TEAMのLambda全種類まとめ
- その他重要な仕組み
- まとめ
はじめに
TEAMは「必要なときだけ、必要な権限を、承認フロー付きで一時的に付与して、時間が来たら自動で剥奪する」仕組みです。AWS IAM Identity Center(旧AWS SSO)のAPIを活用して、これを実現しています。
TEAMについての概要や導入手順は、以前のブログ記事で紹介しています。
デプロイ手順自体は用意されたシェルスクリプトで完結するTEAMですが、サーバレスワークロードで動くOSSということもあり、連携するリソースが多く、トラブルシューティングや細かい仕様の把握はなかなか苦戦します。
そこで、改めてソースコードを参考にまとめました。
なお、内容はバージョン1.4.2時点のものですのでご留意ください。今後のアップデートで変更される可能性があります。
TEAMの構成

TEAMはAWS Amplify (Gen 1)をベースに構築されています。フロントエンドはReact、バックエンドはAWS Amplifyが提供するサーバレスなマネージドサービス群で構成されていて、主要なコンポーネントは以下のとおりです。
| サービス | 役割 |
|---|---|
| AWS Amplify (Gen 1) | フロントエンド(React)のホスティングとバックエンドリソースの管理 |
| Amazon Cognito | ユーザー認証。IAM Identity Centerと連携してログインを実現 |
| AWS AppSync(GraphQL) | フロントエンドとバックエンドの通信を担うGraphQL API。リアルタイム通知にSubscriptionも活用 |
| AWS Lambda | 各種ビジネスロジックの実行。AppSyncのリゾルバやDynamoDB Streamsのトリガーとして動作 |
| AWS Step Functions | 申請のライフサイクル管理。5つのステートマシンが連携 |
| Amazon DynamoDB | 申請情報、セッション情報、承認者設定、権限ポリシーなどの永続化 |
| AWS IAM Identity Center | 実際の権限付与(createAccountAssignment)と剥奪(deleteAccountAssignment) |
※以降、「Amazon」「AWS」を省略した略記を使用します。
ユーザーはAmplifyで提供されるReact画面からTEAMを閲覧し、AppSyncを通して後ろのAWS APIをたたきます。
DynamoDBを参照するStep Functions・Lambdaによって各種の処理が動き、AWS IAM Identity Centerを操作するAPIによって権限付与・はく奪が行われます。

申請の状態推移 ― 「ステータス」について
重要な点として、TEAMは、DynamoDB上で管理された「申請」ごとの「ステータス」の変化を起点にすべてが動く、イベント駆動型のシステムです。
TEAMのDynamoDBはDynamoDB Stream機能が有効化されており、申請のステータスが変わるたびに「DynamoDB Streams → teamRouter関数(後述) → Step Functions」という処理発火の連鎖が発生し、次の処理へと進んでいきます。
つまり、この「ステータス」こそがTEAMの駆動力です。

以下はDynamoDBのrequestsテーブルに保持される、申請1件の項目イメージです。statusがどんどん更新されていき、この場合は最終的にendedになっています。
| id | __typename | accountId | accountName | approver | approver_ids | approverId | approvers | comment | createdAt | duration | endTime | justification | owner | role | roleId | session_duration | startTime | status | ticketNo | updatedAt | username | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| eeda3964-xxxx-xxxx-xxxx-11546963d | requests | 123456789012 | production-service-app | approver-admin@example.com | ["idc_approver-01", "idc_manager-02"] | idc_approver-admin@example.com | ["approver-admin@example.com", "manager@example.com"] | 承認します | 2026-03-24T05:56:19.205Z | 1 | requester-user@example.com | 2026-03-24T06:56:50.259Z | 障害調査およびログ確認のため(チケット #123) | 77442ad8-xxxx...::idc_requester@example.com | ReadOnlyAccess-Plus-S3 | arn:aws:sso:::permissionSet/ssoins-xxx/ps-yyy | PT1H | 2026-03-24T05:56:45.506Z | ended | swx-test-001 | 2026-03-24T06:56:49.858Z | idc_requester-user@example.com |
※ちなみに、requestsテーブルだけでは updatedAtは出るものの、「過去のそれぞれのステータス変更がいつ起きたのか」をさかのぼって追うことは難しいです。詳しい遷移を追いたい場合、CloudWatchロググループ/aws/stepfunction/team-step-function/${env}(デフォルトでは14日間の保持になっています)、を確認する、StepFunctionsの実行履歴等を確認する、またはDynamoDB Streamsからどこかに吐き出す仕組みを新たに実装するなどの必要があります。
サービスの動き
それを踏まえ、サービスが連携する流れを、裏で動くのがDescribe系権限だけの閲覧時と、実際に申請を出して環境変更が加わるときの2つのパターンに分けて解説します。
パターン1:ユーザーがTEAM上で情報を見るとき
管理画面でアカウント一覧やOU一覧、許可セットなどを表示する場合の流れは以下の通りです。
ユーザーが画面を開く ↓ React → AppSync(GraphQL Query) ↓ Lambda(リゾルバ)が各AWSサービスのAPIを呼び出して情報を取得 ↓ 取得結果をAppSync経由でフロントエンドに返す
基本はAppSyncでGraphQL Queryがなされ、後ろのLambdaでデータを取得・返す流れです。この取得用のLambdaが種別に多数あります。
例えばTEAMでの権限申請を行うときにも、「ユーザーが申請できる許可セット・アカウント一覧」「ユーザーが属するAWS IAM Identity Centerグループ」などを取得する必要があります。
実装に興味がある人向けのコラム
ちなみにOU一覧のように取得に時間がかかるデータは、少し工夫されています。Lambdaを非同期で起動し、取得完了後にAppSyncのMutation(データ書き換え) → Subscriptionでフロントエンドにリアルタイム配信する、という、二つのLambdaを使ったPub/Subパターンが使われています。
React → AppSync Query → teamgetOUs Lambda①(トリガー役、即座に返す)
↓
↓ 非同期起動
↓
teamPublishOUs Lambda②(バックグラウンドでOU取得)
↓
↓ 取得完了
↓
AppSync Mutation: publishOUs
↓
React ← AppSync Subscription: onPublishOUs(リアルタイム受信)
パターン2:申請が起こってAWS環境に変更が加わるとき
ユーザーがアクセス権限を申請し、承認を経て実際に権限が付与・剥奪されるまでの流れです。ここで重要な役割を果たすのが先ほどの「ステータス」とDynamoDB Streamsです。
TEAMではDynamoDB Streamsは全テーブル(requests, sessions, Approvers, Settings, Eligibility)で有効化されます。
DynamoDB Streamsは、DynamoDBテーブル内の項目(Item)に対する追加、変更、削除のイベントを時系列でキャプチャする機能であり、テーブルのレコードが作成・更新されたときにイベントを発火させる仕組みを作ることができます。
TEAMではこれをうまく活用していて、「requestsテーブルのstatus値が変わったらteamRouter Lambdaを起動する」といった、テーブルの変更をトリガーにした処理の連鎖を実現しています。
ユーザーが申請送信 ↓ React → AppSync Mutation → DynamoDB(requestsテーブルにレコード作成) ↓ DynamoDB Streams が発火 ↓ teamRouter Lambda(オーケストレーター) ↓ ↓ 申請情報の補完・バリデーション・権限チェック ↓ ステータスに応じた Step Functions を起動 ↓ Step Functions ステートマシン ↓ ↓ 承認待ち → スケジュール → 権限付与 → 時間経過 → 権限剥奪(ここでステータスも変化!) ↓ IAM Identity Center API ├─ createAccountAssignment(権限付与) └─ deleteAccountAssignment(権限剥奪)
※Mutation:GraphQLの基本操作で、データを書き換えるための操作。REST APIで言うPOST, PUT, PATCH, DELETEなど

ポイントは、SMがステータスを更新すると再びDynamoDB Streams → teamRouterの連鎖が起きる点です(図中の点線矢印)。これにより、承認→スケジュール→権限付与→剥奪という一連のフローが、ステータス変更の連鎖として実現されています。
こちらのパターンでは、最終的にIAM Identity CenterのAPIが呼ばれて、実際のAWS環境に変更が加わります。この流れの中核を担うのが、後述する5つのステートマシンです。
DynamoDB Streamsとの連携の実装について
Amplifyの@modelディレクティブによりDynamoDB Streamsは全テーブルで有効化されますが、Lambdaのトリガーとして明示的にバインドされているのは以下の2つだけです。
| テーブル | 接続先Lambda | 用途 |
|---|---|---|
| requests | teamRouter | 申請の状態変化を検知してStep Functionsを起動 |
| sessions | teamgetLogs | セッション作成を検知してCloudTrail Lakeへの監査ログクエリを開始 |
requestsテーブル側は、CloudFormationテンプレート(teamRouter-cloudformation-template.json)のAWS::Lambda::EventSourceMappingリソースで、DynamoDB StreamsのARNとLambda関数を紐づけることで実現しています。
定義を簡単に書くと以下のようなイメージです。
"LambdaEventSourceMappingrequests": { "Type": "AWS::Lambda::EventSourceMapping", "Properties": { "BatchSize": 10, "Enabled": true, "EventSourceArn": "requestsTable:StreamArn", "BisectBatchOnFunctionError": true, "MaximumRetryAttempts": 3, "FunctionName": "teamRouter Lambda の ARN", "StartingPosition": "LATEST" } }
EventSourceArnにDynamoDBテーブルのStream ARNを、FunctionNameにLambdaのARNを指定するだけで、テーブルの変更をトリガーにLambdaが起動するようになります。FilterCriteriaの設定がないため、INSERT(作成)・MODIFY(更新)・REMOVE(削除)すべてのイベントでLambdaが発火します。
フィルタリングはLambda(teamRouter)のコード側で行っています。
- statusがerrorやendedなら何もしない
- まだ補完が済んでいない(emailやapproverがない)なら、補完だけしてStep Functionsは起動しない
- 補完済みのときだけTrueを返し、Step Functionsの起動処理に進む
一方、sessionsテーブル側は、CloudFormationテンプレート(teamgetLogs-cloudformation-template.json)で以下のようにフィルタが設定されており、INSERT(レコード新規作成)のときだけLambdaが実行されるようになっています。
"LambdaEventSourceMappingsessions": { "Type": "AWS::Lambda::EventSourceMapping", "Properties": { "EventSourceArn": "sessionsTable:StreamArn", "FilterCriteria": { "Filters": [ { "Pattern": "{ \"eventName\": [\"INSERT\"]}" } ] }, "FunctionName": "teamgetLogs" } }
docs.aws.amazon.com blog.serverworks.co.jp
残りの3テーブル(Approvers, Settings, Eligibility)は管理者が設定する静的なデータなので、Streamsによるイベント駆動は不要ということのようです。
teamRouter 関数について
ステートマシンの解説に入る前に、その手前で動くteamRouter Lambdaについて触れておきます。
teamRouterはAppSyncから直接呼ばれるのではなく、DynamoDB Streamsのトリガーで起動します。requestsテーブルにレコードが作成・更新されるたびに自動的に呼ばれる仕組みです。
teamRouterは初回呼び出し時は以下のように申請者のメールアドレスなどをrequestsテーブルを更新します。
- 申請者のメールアドレス(Cognitoから取得)
- 承認者一覧(Identity Centerのグループメンバーシップから取得)
- 許可セットのセッション時間
そのテーブル更新により、再びDynamoDB Streamsが発火してteamRouterが呼ばれます。 2回目以降は以下のような処理を行います。
- 設定チェック(最大時間、承認要否など)
- 権限チェック(ユーザーが対象アカウント・ロールへの申請権限を持っているか)
- ステータスに応じたStep Functionsの起動
teamRouterが起動するステートマシン(以下、SM):
| ステータス | 条件 | 起動するSM |
|---|---|---|
| pending | 承認が必要なとき | Approval SM |
| pending | 承認不要なとき | Schedule SM |
| approved | 承認者が本人でないとき | Schedule SM |
| rejected | 承認者が本人でないとき | Reject SM |
| revoked | ― | Revoke SM |
| cancelled | ― | Reject SM |
バリデーションに失敗した場合(権限がない、最大時間超過、自分で自分を承認など)は、ステータスをerrorに更新して処理を終了します。
teamRouterはステータスをもとに、適切なステートマシンを呼び出していくというわけです。
それでは、この起動されるステートマシンについて解説していきます。
TEAMのロジック ― ステートマシンについて
teamRouterによって起動された各ステートマシンは、現在の申請のステータスに応じて適切なワークフローが選択されます。
まずは全体の関係を見てみましょう。
申請が承認されると、Schedule SM → Grant SM → Revoke SMと3つのステートマシンが順に連携し、「権限付与 → 指定時間だけ待機 → 自動剥奪」という一連の流れを実現しています。承認が得られなかった場合や期限切れの場合は、権限付与に至ることなくフローが終了します。
この割り振りはteamRouterによって行われます。図中の丸カッコ内(in progressなど)が申請ごとのステータスの推移を表しています。
ユーザーや承認者のアクション(承認・却下・キャンセル・取消)によってステータスが変わると、DynamoDB Streams → teamRouter → Step Functionsという連鎖が起き、次のワークフローが起動します。 (ステートマシン内部での自動的なステータス遷移(scheduled → in progress → endedなど)はSM自身が直接行うため、teamRouterを経由しません。)

各ステートマシンの役割やタイミングをまとめると、以下のとおりです。
| SM名 | 関連するステータス | いつ起動されるか | やること (カッコは関数名) |
ワークフロー |
|---|---|---|---|---|
| Approval SM | pending/ pending → expired |
pendingになったとき(承認要) | ① 承認者に通知(teamNotifications) ② 承認期限(expire秒)まで待機 ③ requestsテーブルステータス確認③-a まだpending → expired にステータス更新( teamStatus)&期限切れ通知 ③-b 承認/却下済み → 何もせず終了 |
|
| Schedule SM | approved → scheduled / pending → scheduled |
approvedになったとき / pendingになったとき(承認不要) |
① scheduled にステータス更新(teamStatus) ② スケジュール通知( teamNotifications) ③ 開始時刻まで待機 ④ requestsテーブルステータス確認④-a まだscheduled → Grant SM を直接起動 ④-b キャンセル済み → 何もせず終了 |
|
| Grant SM | scheduled → in progress |
Schedule SMから直接起動 (scheduledで開始時刻に到達時) |
① 権限付与(createAccountAssignment) ② in progress にステータス更新( teamStatus)&requestsテーブルにstartTime追記 ③ エラー判定 ③-a エラーあり → エラー通知( teamNotifications)して終了 ③-b 正常 → 開始通知( teamNotifications) → duration秒間待機 → Revoke SM を直接起動 |
|
| Revoke SM | in progress → ended / revoked → ended |
Grant SMから直接起動(in progressでduration経過時) / revokedになったとき |
① 二重起動防止のためrequestsテーブルステータス確認② 権限剥奪( deleteAccountAssignment) ③ 終了通知( teamNotifications) ④ 起動元で分岐 ④-a 手動取消(revoked) → requestsテーブルにendTime追記のみで終了 ④-b 自動終了 → ended にステータス更新( teamStatus)&endTime記録 |
|
| Reject SM | rejected / cancelled |
rejected / cancelledになったとき | ① ステータスに応じて却下通知またはキャンセル通知(teamNotifications) (権限未付与段階のためSSO APIの呼び出しはなし) |
|
TEAMのDynamoDBテーブル一覧
TEAMが作成するDynamoDBテーブルは5つです。すべてAmplifyの@modelディレクティブにより作成され、DynamoDB Streamsも全テーブルで有効化されます(ただしLambdaトリガーが接続されているのは前述のとおりrequestsとsessionsのみ)。
| テーブル | 役割 | バックエンド処理の読み書き元 |
|---|---|---|
| requests | 申請のライフサイクル管理 TEAMの中心的なテーブル TTL設定はないので、過去の全申請がたまっていく |
フロントエンド(申請作成・承認・却下)、teamRouter(メールアドレス・承認者の補完)、各ステートマシン(ステータス更新・時刻記録) |
| sessions | 権限付与中のセッション情報。監査ログとの紐付け TTLにより作成から5日後に自動削除 |
Grant SM(セッション作成)、teamgetLogs(CloudTrail Lakeクエリ起動) |
| Approvers | アカウント/OUごとの承認者グループ設定 | 管理者が設定画面で書き込み。teamRouterが申請時に読み取り、承認者を計算 |
| Settings | TEAMの動作に関するグローバル設定(レコードは1つだけ) | 管理者が設定画面で書き込み。teamRouterが承認要否・最大時間・承認期限などを読み取り。PreTokenGeneration LambdaがAdmin/Auditorグループ名を読み取り |
| Eligibility | どのユーザー/グループがどのアカウント・ロールを申請できるかの定義 | 管理者が設定画面で書き込み。teamgetEntitlementが申請画面で読み取り。teamRouterが申請時のバリデーションで読み取り |
requestsテーブルのステータス変更がDynamoDB Streams経由でteamRouterを起動し、Approvers・Settings・Eligibilityの3テーブルを参照しながらバリデーションと承認者計算を行い、Step Functionsを起動する ― というのがTEAMの基本的なデータの流れです。
requestsテーブルの全属性一覧
requestsテーブルの項目イメージは「申請の状態推移」の章でも紹介しましたが、全属性の詳細は以下のとおりです。
| 属性 | 型 | 説明 |
|---|---|---|
| id | ID! | 申請の一意識別子 |
| String | 申請者のメールアドレス(teamRouterが後から補完) | |
| accountId | String! | 申請対象のAWSアカウントID |
| accountName | String! | アカウント名 |
| role | String! | 許可セット名 |
| roleId | String! | 許可セットのARN |
| startTime | AWSDateTime! | 申請した開始時刻 |
| duration | String! | 申請した利用時間 |
| justification | String | 申請理由 |
| status | String | ステータス |
| comment | String | 承認者のコメント |
| username | String | 申請者のユーザー名 |
| approver | String | 承認者のメールアドレス(teamRouterが後から補完) |
| approverId | String | 承認者のID |
| approvers | [String] | 承認者メールアドレス一覧(teamRouterが後から補完) |
| approver_ids | [String] | 承認者ID一覧(teamRouterが後から補完) |
| revoker | String | 取消者のメールアドレス |
| revokerId | String | 取消者のID |
| endTime | AWSDateTime | セッション終了時刻(Revoke SMが記録) |
| ticketNo | String | チケット番号(任意) |
| revokeComment | String | 取消時のコメント |
| session_duration | String | 許可セットのセッション時間(teamRouterが後から補完) |
TEAMのLambda全種類まとめ
TEAMには多数のLambda関数がありますが、それぞれの役割を一覧にまとめました。(バージョン1.4.2時点)
申請処理系
| Lambda名 | 役割 | いつ呼ばれるか |
|---|---|---|
| teamRouter | 適切なStep Functionsワークフローを起動するオーケストレーター | requestsテーブル更新時(DynamoDB Streams) |
| teamStatus | AppSync mutation経由で申請ステータスを更新 | 各Step Functionsのステート遷移時 |
| teamNotifications | SES / SNS / Slackで通知を送信 | 各Step Functionsの通知ステップ |
データ取得系(AppSyncリゾルバ)
| Lambda名 | 役割 | いつ呼ばれるか |
|---|---|---|
| teamgetOUs | teamPublishOUsを非同期起動するトリガー | 管理画面でOU一覧が必要になったとき |
| teamPublishOUs | OUツリーを再帰取得してAppSyncでpublish | teamgetOUsから非同期起動 |
| teamgetAccounts | アカウント一覧を取得 | 管理画面・申請画面でアカウント一覧表示時 |
| teamgetOU | 指定アカウントの親OUを取得 | 申請時にアカウントの所属OU確認 |
| teamgetPermissions | teamGetPermissionSetsを非同期起動するトリガー | 許可セット一覧が必要になったとき |
| teamGetPermissionSets | 許可セットを取得してAppSyncでpublish | teamgetPermissionsから非同期起動 |
| teamgetIdCGroups | IdCグループ一覧を取得 | 管理画面で承認者グループや申請権限のグループ選択時 |
| teamgetUsers | IdCユーザー一覧を取得 | 管理画面で申請権限のユーザー選択時 |
| teamListGroups | 指定グループのメンバーシップ一覧を取得 | 申請時に承認者が十分にいるかのバリデーション |
| teamgetMgmtAccountDetails | 管理アカウントの許可セット一覧を取得(除外リスト) | 申請画面表示時(委任管理者モード)※注 |
| teamgetUserPolicy | teamgetEntitlementを非同期起動するトリガー | 申請画面で利用可能な権限を表示するとき |
| teamgetEntitlement | 申請権限ポリシーを取得しOUをアカウントに展開してpublish | teamgetUserPolicyから非同期起動 |
※注:委任管理者モードでは、管理アカウントに割り当て済みの許可セットは操作できないため、申請不可にするバリデーションに使用されます。
認証・ログ系
| Lambda名 | 役割 | いつ呼ばれるか |
|---|---|---|
| PreTokenGeneration | Cognitoトークン生成時にIdCグループ情報を付与 | ログイン時(Cognitoトリガー) |
| teamgetLogs | CloudTrail Lakeへ監査ログクエリを実行 | sessionsテーブルINSERT時(DynamoDB Streams) |
| teamqueryLogs | CloudTrail Lakeのクエリ結果を取得 | 監査画面でセッションログ表示時 |
デプロイ時に作成されるLambda
上記はTEAMのアプリケーションロジックを担うLambdaですが、deploy.shの実行過程で作成されるインフラ管理用のLambdaもあります。
| Lambda名 | 役割 |
|---|---|
| TriggerBuildLambda | CloudFormationカスタムリソース。Amplifyのビルドジョブを開始 |
| team-amplify-bucket-lambda | CloudFormationカスタムリソース。Amplifyデプロイ用S3バケットのバージョニングを有効化 |
これらはデプロイ時に一度だけ実行され、通常運用中に呼ばれることはありません。
その他重要な仕組み
通知の仕組み
各ステートマシンの中で「通知」と書いていた部分は、すべてteamNotifications Lambdaが担当しています。このLambdaは3つの通知方法に対応しています。
- Amazon SES(メール通知)
- Amazon SNS(トピック経由の通知)
- Slack(Slack SDK経由の通知)
どの方法を有効にするかは、TEAMの管理画面(Settings)から設定できます。複数の同時有効化も可能です。
通知が飛ぶタイミングをまとめると、以下のとおりです。
| タイミング | 通知先 | 通知内容の概要 |
|---|---|---|
| 申請作成(承認要) | 承認者 | 承認待ちの申請があります |
| 承認されてスケジュール確定 | 申請者 | 申請がスケジュールされました |
| 権限付与(セッション開始) | 申請者 | セッションが開始されました |
| セッション終了 | 申請者 | セッションが終了しました |
| 却下 | 申請者 | 申請が却下されました |
| キャンセル | 申請者 | 申請がキャンセルされました |
| 承認期限切れ | 申請者 | 申請が期限切れになりました |
| エラー発生 | 申請者 | エラーが発生しました |
基本的にはStep Functionsステートマシン内で呼ばれるので、気になったら確認するといいと思います。
※ちなみに公式では2026年3月現在ちょっと古い図が載せられています。
認証の仕組み ― CognitoとIdCグループ
TEAMのログインはAmazon Cognitoで管理されています。TEAMは「ペルソナ」というアプリ上での独自のロールを利用しており、それを可能にするのがPreTokenGeneration Lambdaの存在です。

これはCognitoの「Pre token generation Lambdaトリガー」に登録されたLambdaで、CognitoがJWTトークン(IDトークン)を生成する直前に自動的に呼び出されます。
このLambdaがIAM Identity Centerのグループ情報を取得し、トークンのclaims(ペイロード)にカスタム属性として埋め込みます。
これにより、フロントエンド側で「このユーザーはAdminグループに所属しているか?」「Auditorか?」などの判定ができ、TEAM画面で「Admin」「Auditor」ペルソナの場合はアクセス範囲が変わるようになります。
ユーザーがログイン ↓ Cognito → PreTokenGeneration Lambda ↓ ↓ Identity Store APIでユーザーのIdCグループ所属を取得 ↓ ↓ トークンのclaimsに以下を追加: ↓ - userId(Identity StoreのユーザーID) ↓ - groupIds(所属する全IdCグループIDのカンマ区切り) ↓ - groups("Admin" / "Auditors" の文字列) ↓ ↓ さらにCognitoのグループを上書き(groupsToOverride) ↓ → Admin / Auditors グループに所属させる ↓ → AppSyncの@authルール(allow: groups)で権限制御に利用 ↓ フロントエンドがトークンを受け取り、画面の表示制御に利用 (Admin画面の表示可否、承認ボタンの表示可否など)

なお、Admin/AuditorのIdCグループ名はSettingsテーブルで変更可能で、環境変数のデフォルト値からも取得できるようになっています。
ついでに申請者・承認者についても少し触れておきます。
申請者がアクセス権限の申請画面を開くと、トークンに埋め込まれたuserIdとgroupIdsがAppSync経由でteamgetEntitlement Lambdaに渡され、Eligibilityテーブルから「自分が申請できるアカウント・許可セット」が取得されます。
一方、承認者の画面では、requestsテーブルのapproversフィールド(teamRouterが申請時に補完したもの)に自分のメールアドレスが含まれるpendingの申請を、AppSync Queryでフィルタリングして「自分宛の承認依頼」として表示しています。
まとめ
TEAMの中身を掘り下げてみると、Step Functionsのステートマシン同士が連携しながら申請のライフサイクルを管理し、最終的にIAM Identity CenterのAPIで権限の付与・剥奪を自動化しているという、なかなか凝った構成になっていました。
個人的には特にDynamoDB Streams発火によるサーバレス状態遷移システムの実装例として、勉強になりました。
このブログが少しでも皆様のお役に立てば幸いです。
垣見(かきみ)(執筆記事の一覧)
2023年新卒入社 エンタープライズクラウド部所属 2025 Japan AWS Jr.Champions
図解するのが好き。「サバワク」のアイキャッチ作成も担当しています




