# agent に「Sign in with Google」を自分でクリックさせる——chrome-use が OAuth ポップアップログインを攻略

![chrome-use が id_token を Google popup から gsi/transform 経由で opener に返し、opener が破棄されないように保つ](https://img.leeguoo.com/media/14c3cb41-7748-42f4-b97a-0851cb30f466/chrome-use-oauth-hero.png)

hermes（{別|べつ}の{機械|きかい}で{動|うご}いている、ぼくの agent）に、自分でアカウントへログインしてほしかった。先にぼくがログインして cookie を渡すのではなく、最初から自分でログインする形だ。

ログイン方法はだいたい 2 種類しかない。1 つはアカウントとパスワード。agent がぼくの Bitwarden {金庫|きんこ}からユーザー名、パスワード、2FA {認証|にんしょう}コードを取り出して、入力すれば終わり——この経路は [bitwarden-use](https://github.com/leeguooooo/bitwarden-use) で通せた。人の手をまったく使わずに github にログインできたし、passkey までいける（{秘密鍵|ひみつかぎ}を{金庫|きんこ}から取り出してローカルで WebAuthn に{署名|しょめい}するので、スマホを取り出して{指紋|しもん}を触る必要はない）。もう 1 つは「Sign in with Google」のような{第三者|だいさんしゃ} OAuth。こっちで半日以上つまずいた。

以下では、なぜ難しいのか、そして [chrome-use](https://github.com/leeguooooo/chrome-use) が最終的にどう通したのかを説明する。

## 1 回のクリックの裏で、3 つのウィンドウが会話している

x のログインページで「Google アカウントでログイン」をクリックすると、見た目は 1 回の操作だけだが、裏ではこうなっている。

1. その Google ボタンは x が自分で描いているものではなく、`accounts.google.com` の**クロスオリジン iframe** が差し込まれている。
2. クリックすると **popup ウィンドウ** が開き、その中でアカウントを選ぶ。
3. 選び終えると、Google は token を URL に書くのではなく、この popup を中継ページ `accounts.google.com/gsi/transform` にリダイレクトする。
4. `gsi/transform` がやることは 1 つだけ。`window.opener` を通じて id_token を最初のログインページ（opener）へ **postMessage で返し**、その後自分を閉じる。
5. opener が token を受け取って、x 側のログインが成功し、`/home` に移動する。

{重要|じゅうよう}なのは 4 番目だ。token は popup から opener へ、**ウィンドウ越しに呼びかける**形で返される。その{土台|どだい}が `window.opener` の{関係|かんけい}だ。この{関係|かんけい}が切れると、token は空気に向かって叫ぶだけになる。popup は gsi/transform の上で止まったまま閉じず、x 側はいつまでも受け取れず、数秒後にログイン選択ページへ戻ってしまう。

## 自動化すると、なぜ触っただけで切れるのか

ぼくは chrome-use で**本物の Chrome** を操作している（ブラウザ拡張を通じてタブを引き受ける仕組みで、relay モードと呼んでいる）。問題は 3 か所にあった。

**1 つ目、opener を自分たちで誤って消していた。** relay は各タブのデバッグセッションに `tabId` をキーとして使っている。x のログインページが一度{遷移|せんい}すると（クロスプロセス{遷移|せんい}なので）、Chrome はこのタブに新しい tabId を割り当て、古いキーは{無効|むこう}になる。さらに悪いことに、その時点の「{復旧|ふっきゅう}」ロジックは、{無効|むこう}なセッションに当たると、そのタブを**破棄し、新しい空白タブを開いて**置き換えていた。破棄されていたのは、まさに token を待っている opener だった。ログを半日ほど見続けたが、毎回 token が戻る瞬間で止まっていた。opener が自分たちの{復旧|ふっきゅう}ロジックに消されていたのだ。

**2 つ目、relay から Google ボタンが見えなかった。** クロスオリジン iframe は Chrome 内では{独立|どくりつ}したレンダラープロセス（OOPIF）になる。chrome-use が CDP に{直接|ちょくせつ}つなぐ場合は自動的に中へ入り、iframe 内の本物の「Continue as 立」ボタンが見える。だが relay モードでは、拡張がタブの最上位にしか attach しておらず、iframe はブラックボックスだった。座標を頼りに当てずっぽうでクリックするしかなく、開けたのも「別のアカウントを使う」という{汎用|はんよう}フローで、個人化されたワンクリックログインではなかった。

**3 つ目、relay が popup に attach できなかった。** Google のアカウント選択 popup が開いた瞬間に切り替えてアカウントをクリックしようとすると、拡張が「タブが応答しない」と返した。popup のロード完了を待ってからゆっくり attach しようとしていたが、一瞬で通り過ぎる OAuth ウィンドウには遅すぎた。

さらに{隠|かく}れた落とし穴もあった。長く調べてようやく分かったのだが、拡張が使う native-messaging host は、**インストール済みの古いバイナリ**を指していて、ローカルで新しくビルドしたものではなかった。つまり、それまでの「パッチを当てたテスト」は、全部古いロジックで走っていた。その wrapper スクリプトを新しくビルドしたバイナリに向けて、ようやくパッチが本当に有効な経路に入った。

## 3 つの修正

上の 3 か所に対して、それぞれ 1 つずつ手を入れた。

**opener を守る。** セッションが{無効|むこう}になってもタブを破棄しないようにし、非{破壊|はかい}的な再接続に変えた。同じ `targetId` はまだ存在している（`sessionId` が入れ替わっただけ）ので、再 attach して新しい sessionId を取ればよい。読み取りやクリックのようなコマンドで{無効|むこう}セッションに当たっても、拡張側がセッションを{復旧|ふっきゅう}するまで数回リトライを許し、最初の 1 回で強くエラーにしないようにした。

**iframe を貫通する。** attach 時に拡張が自分から `Target.setAutoAttach({flatten:true})` を開くようにし、Google iframe という子セッションのイベントをデーモンへ転送させた。コマンド側も子 sessionId に合わせて、その iframe 内へルーティングする。これでスナップショットが中まで入れるようになり、「Continue as 立」にクリック可能な ref が付く——CDP {直接|ちょくせつ}接続と同じになった。

**popup についていく。** 拡張は `chrome.tabs.onCreated` を使い、popup が**作られた瞬間に** attach するようにした（opener が自分の管理下にあるタブのものだけを対象にし、別のウィンドウを誤って巻き込まないようにしている）。デーモンはそれを副次タブとして保持するが、メインタブは奪わない。opener はそのまま固定され、token を待ち続ける。

## 通った

パッチを入れて、拡張をリロードしてから、もう一度走らせた。

- スナップショットで、GSI iframe 内の「Continue as 立, leeguooooo@gmail.com」が見えるようになり、それをクリックする。
- アカウント選択 popup が出るので切り替える（今回は「応答しない」とは出ない）。そこでアカウントを選ぶ。
- 選んだ後は**何も触らず**、gsi/transform に token を opener へ postMessage で返させる。
- opener は破棄されず、token を受け取る——x は `/home` に移動し、ログイン状態になる。

最初から最後まで、人のクリックは 1 回もない。この硬い骨は、どうにか噛み砕けた。

## 仕上げ

agent が自分でアカウントにログインする経路は、これで 2 本とも通った。OAuth のような{第三者|だいさんしゃ}{遷移|せんい}は chrome-use が自分でクリックし切る。アカウントとパスワード、2FA、passkey のようなものは、bitwarden-use があなたの{金庫|きんこ}から取り出して入力する。

- chrome-use：[github.com/leeguooooo/chrome-use](https://github.com/leeguooooo/chrome-use)
- bitwarden-use：[github.com/leeguooooo/bitwarden-use](https://github.com/leeguooooo/bitwarden-use)

関連ドキュメント（英語）：[chrome-use · Login & Credentials](https://chrome-use.leeguoo.com/en/login-auth.html)、[インストール](https://chrome-use.leeguoo.com/en/install.html)。
