[Amazon Bedrock] AgentCore Browser で Cognito + TOTP 認証つきの自作アプリを Claude に自律操作させてみました

[Amazon Bedrock] AgentCore Browser で Cognito + TOTP 認証つきの自作アプリを Claude に自律操作させてみました

Amazon Bedrock AgentCore Browser で、Cognito の TOTP 認証を突破して自作アプリを自動操作するデモを実装してみました。ログインから MFA 入力、フォーム記入、human-in-the-loop による承認まで、実運用を想定した一連の流れを紹介します。
2026.07.19

1 はじめに

製造ビジネステクノロジー部の平内(SIN)です。

Amazon Bedrock AgentCore Browser を使うと、隔離されたマネージドブラウザ上で LLM に Web アプリケーションの GUI を操作させることができます。API が用意されていない管理画面などを、DOMを気にすること無く、見たままに自動操作できる点が魅力です。

しかし、「MFA(多要素認証)が必要なサイトを自動操作できるのか」という疑問が少し残ります。実運用のアプリでは、通常ログインがあり、TOTP(認証アプリの6桁コード)による MFA も一般的だからです。

本記事では、操作対象を自作の Cognito 認証つきアプリとし、ログインから MFA入力、画面操作、操作の記録までを再現し、E2Eテストなどで使用できるサンプルを作成してみました。

鍵になるのは MFA の扱いです。TOTP は、MFA 登録時にサーバ(Cognito)から提示されるシークレットさえ保存しておけば、そこから6桁コードを通信なしで計算できます。本デモではこのシークレットを Secrets Manager に保存しておくことで、認証アプリの代わりに自前のスクリプトが MFA コードを生成できるようにしています。

ブラウザの操作には Playwright、判断には Bedrock(Claude)を使用しました。AWS の Nova Act でも実現は可能ですが、今回は「MFA を確実に入力する」「送信の直前で確実に止める」「Claude で判断する」という流れにあわせて、Playwright + Bedrock(Claude)の組み合わせとしました。

題材は「クラウドコスト管理 SaaS(デモ)」です。ダッシュボードで予算を超過したサービスを確認し、予算増額申請フォームから申請する、というストーリーです。

第三者のサイトに対する MFA自動入力は、利用規約違反となる可能性が高いため、自作アプリや明示的な許可を得たシステムのみでの使用をお勧めします

サンプルコードは GitHub で公開しています。

GitHub: agentcore-browser-cognito-totp

2 構成

全体の構成は次のとおりです。通常はブラウザから操作しますが、ローカルの run.py を使えば、Bedrock で推論しながら AgentCore Browser(隔離ブラウザ)経由で、Cognito 認証つきの自作アプリ(S3 + CloudFront)を自動操作できます。また、操作の記録は S3 に残ります。

どちらの場合も、MFAはSecrets Managerに保存された共有シークレットから生成しています。

001

3 CDK

本デモの環境は、CDK(TypeScript)で構築できます。生成される主なリソースは以下です。

リソース 役割
認証 Cognito User Pool + Client ログイン + TOTP MFA
配信 S3 + CloudFront(OAC) SPA を公開 URL で配信
監査 S3 セッションリプレイの記録先
権限 IAM 実行ロール カスタムブラウザが記録を書き込む
秘密 Secrets Manager TOTP 共有シークレット の保管先

GitHub: cdk/lib/agentcore-cognito-totp-stack.ts

// cdk/lib/agentcore-cognito-totp-stack.ts(要点抜粋)
const userPool = new cognito.UserPool(this, 'UserPool', {
  mfa: cognito.Mfa.REQUIRED,
  mfaSecondFactor: { sms: false, otp: true }, // TOTP のみ
});

// SPA は非公開 S3 + CloudFront(OAC) で配信
const siteBucket = new s3.Bucket(this, 'SiteBucket', {
  blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
});
const distribution = new cloudfront.Distribution(this, 'Distribution', {
  defaultBehavior: { origin: origins.S3BucketOrigin.withOriginAccessControl(siteBucket) },
});

なお、Cognito の ID は、config.json として配信バケットに置き、フロントが起動時にそれを読み込むようになっています。

new s3deploy.BucketDeployment(this, 'DeploySite', {
  destinationBucket: siteBucket,
  distribution,
  sources: [
    s3deploy.Source.asset('../frontend/dist'),
    s3deploy.Source.jsonData('config.json', {
      region: this.region,
      userPoolId: userPool.userPoolId,
      userPoolClientId: userPoolClient.userPoolClientId,
    }),
  ],
});

デプロイは、フロントを先にビルドしてから行います(BucketDeploymentfrontend/dist を配信するため)。

cd frontend && pnpm install && pnpm build && cd ..     

002

続いてCDKのデプロイです。

cd cdk && pnpm install && pnpm cdk deploy && cd ..            

003

確認が終わったら、最終的には、destroyしてください。放置すると、Secrets Manager や CloudFront などに費用が発生します。

cd cdk && pnpm cdk destroy

4 セットアップ(テストユーザーと共有シークレットの登録)

デプロイができたら、ログインの前に setup/setup_totp.py を1回だけ実行します。これで、テストユーザーの作成・TOTP の登録・共有シークレット(seed)の Secrets Manager への保存までが済みます。

GitHub: setup/setup_totp.py

cd setup
python -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt
python setup_totp.py --password 'Passw0rd!' --email you@example.com

実行すると demo-user が作成され、以降のログインで使う共有シークレットが Secrets Manager に保管されます(パスワードは任意の値に変更できます)。

5 手動操作

自動化の前に、操作対象のアプリを手動で確認してみます。

アプリは S3 + CloudFront で配信した SPA になっています。

(1) ログインと MFA

CloudFront の URL を開くと、Amplify UI のログイン画面が出ます。ユーザー名とパスワードを入れると、続けて TOTP の6桁コードを求められます。

004

この6桁は、本来は認証アプリに表示される値ですが、今回は共有シークレット(seed)を Secrets Manager に保管してあるので、次のスクリプトでいつでも生成できます。

なお、以降の Python スクリプト(show_totp.py / run.py / create_custom_browser.py)は、いずれも agent/ ディレクトリにあります。4 章の setup/ とは別ディレクトリなので、ここで改めて仮想環境(初回のみ)を用意し、有効化してから実行してください。

cd agent
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

準備ができたら、次のスクリプトで6桁を生成できます。

python show_totp.py            # 今の6桁を表示
python show_totp.py --loop     # 残り秒数つきで毎秒更新

005

(2) ダッシュボードと申請フォーム

ログインすると、サービス別のコスト一覧が並ぶダッシュボードが表示されます。1行だけ、Amazon RDS が予算超過(コスト ¥137,000 / 予算 ¥90,000)になるよう仕込んであります。

006

「予算増額を申請する」から申請フォームに進み、サービス名・現在の予算・申請額・理由・承認者を入力して送信すると、申請 ID が発行される、という流れです。

007
008

次はこれを Claude に任せます。

6 自動操作(Live View)

同じ操作を AgentCore Browser 上の Claude に実行させ、その様子を AWS コンソールの Live View でリアルタイムに観察します。Live View は、AgentCore Browser 上で動いている隔離ブラウザの画面を、そのまま覗き見できる機能です。

実行スクリプトには、操作開始前に一度停止する --watch を用意しました。停止中にコンソールで Live View を開いておけば、ログインからフォーム入力までの動きをそのまま追えます。

python run.py --watch

Live View で眺めるだけなら、これだけで大丈夫です(デフォルトのブラウザでもコンソールの Live View に表示されます)。操作の記録を S3 に残したい場合は、後述の「9 セッションリプレイによる操作記録」で作るカスタムブラウザの ID を --browser-id に渡します。

観察できるのは次の一連の流れです。

  • ログイン画面にユーザー名・パスワードが入力され、続けて TOTP の6桁が自動で入る
  • ダッシュボードに遷移し、予算超過サービス(Amazon RDS)が特定される
  • 申請フォームに、サービス名・現在の予算・申請額などが自動入力される

010

そして、送信の直前でスクリプトが停止します。ここが human-in-the-loop(人間を処理の途中に組み込み、要所で承認・介入させる設計)で、人間が内容を確認して承認するまで送信しません。承認すると送信され、完了画面の申請 IDまで自動で到達します。

ターミナル側の出力は次のようになります。

011

手動でやったことを、ログインの MFA も含めてそのまま自動で再現できます。以降は、この裏側を個別に見ていきます。

7 認証(Cognito・TOTP・共有シークレット)

7.1 共有シークレットの取得と保管

テストユーザーの作成で使用した setup_totp.py では、次の流れで共有シークレットを取り出して保存しています。

  1. admin_initiate_auth でログインを開始します。MFA がまだ未設定なので、Cognito から MFA_SETUP チャレンジ(と Session)が返ります。
  2. その Sessionassociate_software_token に渡すと、TOTP の共有シークレット(SecretCode)が払い出されます。受け取れるのは、この登録時の一度きりです。
  3. 受け取った共有シークレットで pyotp.TOTP(seed).now() を計算し、その6桁を verify_software_token に渡して登録を確定します(認証アプリに初回コードを入力するのと同じ操作を、コードで行っています)。
  4. 最後に、共有シークレットを Secrets Manager に保存します。以降はここから何度でも6桁を再現できるので、これが自動化の起点になります。

GitHub: setup/setup_totp.py

# setup/setup_totp.py(要点抜粋)
auth = idp.admin_initiate_auth(...)                 # -> MFA_SETUP チャレンジ
assoc = idp.associate_software_token(Session=auth["Session"])
seed = assoc["SecretCode"]                          # ← 登録時に提示される seed を受領
code = pyotp.TOTP(seed).now()                       # 今の6桁を計算
idp.verify_software_token(Session=assoc["Session"], UserCode=code)
# ... MFA_SETUP を確定し、seed を Secrets Manager へ保存

7.2 標準ライブラリだけの TOTP

TOTP はブラックボックスではありません。counter = floor(unix_time / 30) を Base32 の共有シークレットで HMAC-SHA1 にかけ、dynamic truncation で6桁を取り出すだけです。dynamic truncation とは、HMAC の結果(20バイト)から、位置を動的に選んで4バイトを抜き出し、6桁に畳み込む標準手順です(RFC 4226)。

GitHub: agent/totp_manual.py

def totp(secret_base32: str, interval: int = 30, digits: int = 6) -> str:
    key = base64.b32decode(secret_base32.upper() + "=" * (-len(secret_base32) % 8))
    counter = struct.pack(">Q", int(time.time()) // interval)
    digest = hmac.new(key, counter, hashlib.sha1).digest()
    offset = digest[-1] & 0x0F
    code = struct.unpack(">I", digest[offset:offset + 4])[0] & 0x7FFFFFFF
    return str(code % (10 ** digits)).zfill(digits)

8 AgentCore Browser への接続と画面操作

8.1 接続とログイン

接続は bedrock-agentcore SDK が標準的な方法を用意しています(公式: Using AgentCore Browser with Playwright)。browser_session がセッション作成・後始末・SigV4 ヘッダー生成を担い、Playwright(ブラウザを自動操作する OSS ライブラリ)の connect_over_cdp() で接続します。identifier にカスタムブラウザ ID を渡すと、記録有効のブラウザを使えます。

GitHub: agent/run.py

with browser_session("ap-northeast-1", identifier=browser_id) as client:
    ws_url, headers = client.generate_ws_headers()   # SigV4 署名付きヘッダー
    with sync_playwright() as pw:
        browser = pw.chromium.connect_over_cdp(ws_url, headers=headers)
        page = browser.contexts[0].pages[0]

ログインは、Amplify UI が描画する画面に対して、フィールド名やラベル「Code」・ボタン「Confirm」を狙って操作します。

page.fill("input[name='username']", username)
page.fill("input[name='password']", password)
page.click("button[type='submit']")
page.get_by_label("Code").fill(pyotp.TOTP(seed).now())   # seed から6桁を生成
page.get_by_role("button", name="Confirm").click()

8.2 ログイン後の自律操作(概要)

ログイン後は、Claude が画面を読んで操作します。おおまかな流れは次のとおりです。

  1. 画面を aria_snapshot() でテキスト化し、Bedrock(Claude)に渡す(送るのは画像ではなくテキスト)
  2. ダッシュボードから予算超過サービスを特定(B)し、続けて申請フォームに入れる値を生成(A)する
  3. 生成された値を、あらかじめ決めたセレクタ(#service など)に入力する
  4. 送信の直前でスクリプトが止まり、人間が承認してから送信する(human-in-the-loop)

aria_snapshot() は Playwright の機能で、画面のアクセシビリティ情報(各要素の役割と名前)をテキストにしたものです。B・A の詳しい実装はコードを参照してください。

GitHub: agent/run.py

このなかで、送信直前の停止(human-in-the-loop) だけ抜粋します。「入力までは自律、送信の一線だけ人間」という安全設計の肝です。

if not args.auto:
    input("内容を確認し、送信してよければ Enter を押してください... ")  # 承認
page.click("#submit-request-button")                 # 送信はここだけ人間トリガー
request_id = page.locator("[data-testid='request-id']").inner_text()
assert request_id.startswith("REQ-")                 # 完了画面で検証

9 セッションリプレイによる操作記録

記録を有効にしたカスタムブラウザを使うと、操作・DOM の変化・ネットワークイベントを S3 に記録できます。AI が自律で入力・送信した内容を、後から再生して確認できる証跡です(手元で Playwright を動かす場合は、記録の有効化や保管を自分で用意する必要があります)。

手順は2ステップです。いずれも agent/(venv 有効化済み)で実行します。

(1) 記録用のカスタムブラウザを作成する

python create_custom_browser.py
# 出力例: created browserId : cognito_totp_recording_browser-xxxxxxxx

表示された browserId を控えておきます。記録先の S3 バケットと実行ロールは CDK の出力から自動取得されるので、指定は不要です。

GitHub: agent/create_custom_browser.py

(2) そのブラウザでデモを実行する

--browser-id に、上で表示された browserId を渡します。

python run.py --browser-id cognito_totp_recording_browser-xxxxxxxx

これで、このセッションの操作が S3(browser-recordings/<session-id>/)に記録されます。

(3) 記録を再生する

AWS コンソールの Bedrock AgentCore → Built-in tools → 該当ブラウザ → 対象セッション → View recording から、タイムライン付きで再生できます。

012

10 MFA の自動化可否

今回自動化したのは TOTP ですが、MFA には種類があり、自動化できるものとできないものがあります。

MFA 種別 自動化 理由
TOTP(ソフトウェアトークン) 可能 共有シークレットから、通信なしで6桁を計算できる
バックアップコード 可能(補助的) 事前発行の使い捨てコード。保管すれば入力できるが、枯渇する
SMS / メール OTP 不可 コードが電話網・メールに届く。受信の自動化が別途必要
プッシュ承認 不可 スマホアプリでの承認操作が必要
FIDO2 / パスキー / ハードウェアキー 不可 FIDO2 / パスキー / ハードウェアキー

ポイントは、TOTP は「共有シークレットさえあれば片側だけで6桁を再現できる」点です。別チャネルに届くもの(SMS・メール・プッシュ)や物理デバイス前提のもの(FIDO2・パスキー)は、この方式では自動化できません。

11 最後に

Amazon Bedrock AgentCore Browser で、Cognito + TOTP 認証つきの自作アプリを Claude に自律操作させる通しデモを作成してみました。

全てのMFAではありませんが、TOTPなら、共有シークレットを取得できれば、問題なく自動化できそうです。

この仕組みで、API を持たない社内管理画面の定型作業や、認証つきアプリの E2E テストにも応用できそうです。

GitHub: agentcore-browser-cognito-totp

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事