Skip to content

refactor(oauth): replace device flow with Worker-hosted web OAuth + fix refresh desync #209

@liplus-lin-lay

Description

@liplus-lin-lay

目的

v0.11.0 で導入した device authorization grant (RFC 8628) を撤去し、**v0.10.x 系と同等の web OAuth 体験(GitHub 標準ログイン + 2FA の見慣れた UX)**に戻す。同時に v0.10.x で chronic auth loop を生んでいた 2 つの root cause を構造的に根絶する:

  • RC1: refresh rotation desync(access token 期限切れ起点の時限式ループ)
  • RC2: localhost callback の ephemeral port 到達不能

device flow はユーザから見た認証コード体験が標準的でなく、外部ユーザの UX として不適切だった(8 文字英数の GitHub 標準 user_code、github.com/login/device 直接遷移など)。一方で v0.10.x UX は馴染みある web OAuth のためこちらへ戻す。RC2 は redirect_uri を Worker 自前ドメインに固定 することで localhost 依存を一切消して解決する。

前提(検証済)

採用する新フロー

MCP サーバ:
  1. state = random(32) 生成
  2. ブラウザで https://<worker>/oauth/authorize?client_id=<cid>&state=<state> を開く
  3. https://<worker>/oauth/token?grant_type=<new>&state=<state> を polling

Worker GET /oauth/authorize:
  1. pending record {state, client_id, status: pending, expires_at} を OAUTH_KV に put
  2. GitHub OAuth web authorize URL へ 302 redirect:
     https://github.com/login/oauth/authorize?
       client_id=${GITHUB_CLIENT_ID}&
       redirect_uri=https://<worker>/oauth/callback&
       state=<state>&
       scope=<scope>

ユーザ: GitHub 標準ログイン画面 + 2FA(Google Authenticator 6 桁など見慣れた UX)

Worker GET /oauth/callback:
  1. ?code=<gh_code>&state=<state> 受信
  2. GitHub access_token を confidential client として交換(GITHUB_CLIENT_SECRET 使用)
  3. 既存の fetchGitHubProps() で user profile + installations 取得
  4. 既存の issueTokensForNewGrant() 相当で Worker 独自 bearer token 発行
  5. state をキーに { access_token, refresh_token } を短命 KV に保存
  6. HTML で「承認完了、このタブを閉じてください」

Worker POST /oauth/token (新 grant_type, 例: 'web_authorization_poll'):
  - pending: 400 authorization_pending(既存 device_code と同形式)
  - approved: 200 + access_token + refresh_token、state KV 消費
  - expired: 400 expired_token

既存 Worker コードの流用

  • worker/src/oauth.tsissueTokensForNewGrant() / handleTokenRefresh() / fetchGitHubProps() はそのまま流用可
  • worker/src/oauth-store.ts の KV schema は web_auth_state レコードを追加するだけ(既存 client / grant / token / refresh は不変)
  • /oauth/authorize/oauth/callback は F7.6 で 410 Gone に退避済み。この issue で正式復活させる(実装は v0.10.x 時代と異なる — Worker 自前の state KV + polling endpoint が加わる)

RC1 修正方針

  • MCP サーバ側: invalid_grant 受信時、即座に full re-auth に進まず、tokens file を再読み込み して別プロセスが先に refresh 済みかチェック。最新トークンがあればそれを採用して再試行。無ければ re-auth
  • 複数 Claude Code instance が同一 tokens file を共有するケースで、一方の refresh 成功が他方の in-memory state を古くする問題に対応
  • file lock は導入しない(過剰)。re-read only で十分なケースが多い

RC2 解消の構造

  • redirect_uri = https://<worker>/oauth/callback(Worker 固定)
  • localhost listener 不要 → process 再起動で port 失うバグ消滅
  • smgjp.com Worker は全ユーザの callback を受け、tenant は GitHub user_id で分離(既存 TenantRegistry DO)
  • self-host ユーザは自分の Worker URL を使う(installation docs 既定)

制約

  • GitHub App liplus-webhook-mcp の設定に redirect_uri(smgjp.com 用、self-host 例示用)を登録する必要あり → Master の手動作業
  • Worker 側の OAUTH_KV schema に web_auth_state レコード追加(migration 不要、新キーを put するだけ)
  • 既存 v0.11.0 / v0.11.1 UX patch で発行された device flow トークンは、本改修で無効化(legacy 扱い、client 側が invalid_grant を受けたら新しい web flow に合流)
  • アーキテクチャ図(docs/0-requirements.ja.md 冒頭)は Local MCP Bridge が Worker プロキシである点は維持。/oauth/device 経路と device flow 関連記述を削除して /oauth/authorize + /oauth/callback + /oauth/token (web poll) に置換
  • mcp-server と local-mcp の両方を揃える(TypeScript 側と JS 側で同じ設計)
  • 仕様書 F7 を全面書き直し、installation.ja.md / installation.md の初回認証節を書き直し
  • version bump: v0.11.1(Master 決定、prerelease として tag 再定義。β 方針)。既存コミット(device flow UX patch のマージ)は履歴として保存、revert コミットを積んで v0.11.1 tag は両方込み

target files

Worker:

  • worker/src/oauth.ts — handleOAuthRequest ルーティング書き換え、/device_authorization 削除、/authorize + /callback 実装、/token 新 grant_type 追加
  • worker/src/oauth-store.ts — web_auth_state レコード型追加
  • worker/test/oauth.test.ts — 更新

MCP bridges:

  • mcp-server/server/index.js — device flow コード(performOAuthFlow / requestDeviceAuthorization / pollForDeviceToken / AuthRequiredError / formatAuthRequiredResponse / openBrowser(部分流用可)など)を削除、Worker-poll flow 実装、RC1 修正
  • local-mcp/src/index.ts — 同上
  • mcp-server/test/auth-required.test.mjs 削除、新 test 追加

docs:

  • docs/0-requirements.ja.md / docs/0-requirements.md — F7 全面書き直し、architecture 図調整
  • docs/installation.ja.md / docs/installation.md — 初回認証節書き直し(device flow → web OAuth)

version:

  • mcp-server/manifest.json — 0.11.1 維持(既に 0.11.1)
  • mcp-server/package.json — 0.11.1 維持
  • mcp-server/server.json — 0.11.1 維持

実装順序の提案

  • Worker 先行(接続先エンドポイント確定してから bridge 実装)
  • → bridge 両方を並行修正
  • → docs / tests
  • 単一 PR、single parent 方針(sub-issue 不要と判断。target files は重複せず parallel 化も可能だが、全体ボリューム的には single subagent で順次処理する方が context が通じやすい)

関連

Metadata

Metadata

Labels

readybody converged for implementationspeclanguage or system specification

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions