Skip to content

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

Merged
liplus-lin-lay merged 1 commit into
mainfrom
209-oauth-web-revert
Apr 20, 2026
Merged

refactor(oauth): replace device flow with Worker-hosted web OAuth + fix refresh desync#210
liplus-lin-lay merged 1 commit into
mainfrom
209-oauth-web-revert

Conversation

@liplus-lin-lay
Copy link
Copy Markdown
Member

Closes #209
Closes #195

概要

v0.11.0 で導入した device authorization grant (RFC 8628) を撤去し、Worker がホストする web OAuth flow に戻した。GitHub 標準のログイン + 2FA の見慣れた UX を取り戻しつつ、v0.10.x の chronic auth loop (#195) を引き起こしていた 2 つの root cause を構造的に根絶する。

解消した auth loop の root cause

RC1: refresh rotation desync

  • MCP bridge 側で refresh が invalid_grant を返した際、直ちに全面 re-auth に遷移せず tokens file を再読み込みする。
  • 別プロセスが既に rotation を完了していれば、その最新 refresh_token を採用して再試行。
  • file lock は導入しない(過剰)。re-read のみで同一 tokens file を共有する複数 Claude Code instance のケースを吸収する。

RC2: localhost ephemeral port unreachability

  • Worker 側で GitHub の redirect_urihttps://<worker>/oauth/callback に固定。localhost listener 依存を完全に消去。
  • process 再起動で port を失うバグは構造的に消滅。

変更点

Worker (worker/src/oauth.ts, worker/src/oauth-store.ts)

  • GET /oauth/authorize?client_id=<cid>&state=<state>web_auth_state:{state} レコード pending 作成 → https://github.com/login/oauth/authorize に 302(redirect_uri=https://<worker>/oauth/callback 固定)
  • GET /oauth/callback?code=<gh_code>&state=<state> → GitHub に confidential client として code→token 交換 → fetchGitHubProps() で user profile + installations 取得 → Worker 独自 bearer pair 発行 → state を approved に遷移 → ユーザに「タブを閉じてください」HTML 表示
  • POST /oauth/tokengrant_type=urn:ietf:params:oauth:grant-type:web_authorization_poll を追加。RFC 8628 §3.5 のエラー形式(authorization_pending / access_denied / expired_token)を踏襲
  • refresh_token rotation は既存ロジックをそのまま流用(無変更)
  • OAUTH_KV schema: web_auth_state:{state} レコード追加、device:{device_code} / user_code:{user_code} キーは撤去
  • /oauth/device_authorization / /oauth/device ルートは削除

MCP bridges (mcp-server/server/index.js, local-mcp/src/index.ts)

  • device flow 関連コードを全削除: performOAuthFlow / requestDeviceAuthorization / pollForDeviceToken / AuthRequiredError / formatAuthRequiredResponse / getAccessTokenForToolCall の device-flow ブランチ
  • web flow 用に再実装: random state 生成 → authorize URL を生成してブラウザオープン → /oauth/token(web_authorization_poll grant)を poll
  • openBrowser() は web flow の authorize URL を開く用途で流用
  • tokens file の flow marker を "device""web" に更新。旧 marker のファイルは無視され、初回ツール呼び出しで新しい web flow に合流
  • RC1 修正: refreshAccessToken()invalid_grant を返したとき tryRefreshViaDiskReread() が tokens file を再読み込みし、別プロセスが rotation していればその最新 refresh_token を採用
  • auth-required ツール応答は authorize URL と残り有効秒数を含む構造化テキスト(isError: true

docs

  • docs/0-requirements.ja.md / docs/0-requirements.md の F7 全面書き直し(F7.1〜F7.11)。architecture 図に /oauth/authorize / /oauth/callback / /oauth/token 経路を追加。files セクションの oauth-store.ts 説明を web_auth_state 基準に更新
  • docs/installation.ja.md / docs/installation.md の初回認証節と GitHub App 設定節を web flow 用に書き直し(Enable Device Flow のチェック不要、Callback URL 登録必須、Client Secret 必須)
  • mcp-server/README.md の認証説明と troubleshooting を更新

tests

  • worker/test/oauth.test.ts: 新 web flow 向けに書き直し。authorize → callback → poll の happy path、state 消費後の expired_token、access_denied、refresh rotation、process-restart、metadata shape、unsupported grant_type
  • mcp-server/test/auth-required.test.mjs: device flow 用だったため削除
  • mcp-server/test/web-auth-required.test.mjs: 新 flow の auth-required 応答形式を検証
  • mcp-server/test/migration.test.mjs: flow: "web" マーカーベースに更新。localhost flow / device flow 両方の legacy ファイルが reject されることを確認。invalid_grant 検出 predicate のテストも追加

version

v0.11.1 維持(mcp-server/manifest.json / mcp-server/package.json / mcp-server/server.json すべて 0.11.1 のまま)。β方針で device flow UX patch のマージ履歴と本 revert を同じ v0.11.1 タグに載せる。

Master の手動作業(マージ後)

  • GitHub App liplus-webhook-mcpCallback URLhttps://github-webhook.smgjp.com/oauth/callback を登録
  • self-host ドキュメント上では「Worker URL の /oauth/callback を登録」と案内済み

CI / 手動検証

  • ローカル: worker npm test / mcp-server npm test / local-mcp npx tsc --noEmit いずれもグリーン
  • 手動 end-to-end は GitHub App の Callback URL 登録後に Master 側で確認(preview instance)

v0.11.0 で導入した device authorization grant (RFC 8628) を撤去し、
Worker がホストする web OAuth flow に戻す。GitHub 標準のログイン +
2FA の UX に回帰しつつ、v0.10.x で慢性的に発生していた auth loop
の 2 つの root cause を構造的に解消する。

RC1 (refresh rotation desync): MCP bridge 側で refresh が
invalid_grant を返したとき、直ちに re-auth に移行せず tokens file
を再読み込みする。別プロセスが rotation 済みなら最新 refresh_token
を採用して再試行。file lock は導入しない。

RC2 (localhost ephemeral port): Worker 側で `redirect_uri` を
`https://<worker>/oauth/callback` に固定し、localhost listener 依存
を完全に消した。GitHub の callback は常に Worker に戻り、tenant は
GitHub user_id で分離する既存の TenantRegistry DO に委ねる。

- Worker: /oauth/authorize で state を発行して GitHub に 302、
  /oauth/callback で confidential client として code→token 交換、
  /oauth/token に web_authorization_poll grant_type を追加。
  OAUTH_KV schema に web_auth_state レコードを追加、device:/user_code:
  キーは撤去。
- mcp-server (JS) / local-mcp (TS) bridges: device flow 関連関数
  (performOAuthFlow / requestDeviceAuthorization / pollForDeviceToken /
  AuthRequiredError / formatAuthRequiredResponse / getAccessTokenForToolCall
  の device-flow ブランチ) を全削除し、web flow poll に書き直し。
  openBrowser は web flow の authorize URL を開く用途で流用。
  tokens file の flow marker は "device" → "web" に更新。
- docs: F7 を全面書き直し、architecture 図更新、installation.ja.md /
  installation.md の初回認証節と GitHub App 設定節を web flow 用に
  書き直し。mcp-server/README.md の該当節も更新。
- tests: worker/test/oauth.test.ts を web flow 用に書き直し、
  mcp-server/test/auth-required.test.mjs を削除し新しく
  web-auth-required.test.mjs を追加、migration.test.mjs は flow="web"
  ベースに更新。

version は 0.11.1 を維持(β方針で device flow UX patch のマージ履歴
と revert を同じ v0.11.1 タグに載せる)。

GitHub App `liplus-webhook-mcp` に
`redirect_uri=https://github-webhook.smgjp.com/oauth/callback` の
登録が必要(Master 側の手動作業)。

Closes #195
Refs #209
@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
github-webhook-mcp a4719c1 Apr 20 2026, 03:01 PM

@liplus-lin-lay liplus-lin-lay self-assigned this Apr 20, 2026
Copy link
Copy Markdown
Member Author

@liplus-lin-lay liplus-lin-lay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-review: PASS

Acceptance check (issue #209)

Worker (worker/src/oauth.ts, oauth-store.ts, index.ts)

  • /oauth/authorize and /oauth/callback restored with Worker-fixed redirect_uri — RC2 (ephemeral localhost port) structurally eliminated.
  • New grant_type urn:ietf:params:oauth:grant-type:web_authorization_poll added to /oauth/token; pending/approved/denied/expired states map to RFC 8628 §3.5 error shapes.
  • Device flow code fully removed; zero device_* references remain.
  • KV schema adds web_auth_state:{state}; removes device: / user_code: keys.
  • issueTokensForNewGrant(), handleTokenRefresh(), fetchGitHubProps() reused verbatim.

Bridges (mcp-server/server/index.js, local-mcp/src/index.ts)

  • Device flow functions (performOAuthFlow, requestDeviceAuthorization, pollForDeviceToken, legacy AuthRequiredError/formatAuthRequiredResponse, device-branch getAccessTokenForToolCall) removed.
  • Web flow implemented: open authorize URL → background poll /oauth/token with web_authorization_poll grant → save tokens with flow: "web" marker.
  • RC1 fix: on invalid_grant during refresh, loadTokens() re-reads the tokens file before giving up. If a sibling process already rotated, the fresh refresh_token is adopted.
  • openBrowser() repurposed for authorize URL, non-fatal on failure (stderr warning only).

Docs

  • docs/0-requirements.ja.md / .md: F7 fully rewritten (F7.1–F7.11). F7.11 codifies the RC1 re-read behaviour. Architecture diagram updated; /oauth/device path removed.
  • docs/installation.ja.md / .md: first-auth section rewritten around web flow; GitHub App "Enable Device Flow" now explicitly documented as not required. Legacy device-flow mentions retained only in migration guidance.
  • mcp-server/README.md realigned.

Tests

  • mcp-server/test/auth-required.test.mjs deleted; web-auth-required.test.mjs added.
  • mcp-server/test/migration.test.mjs updated to verify flow="web" marker + invalid_grant detection predicate.
  • worker/test/oauth.test.ts rewritten (13 tests).
  • Bridge tests 11/11 pass; CI all green.

Version

  • manifest.json / package.json / server.json remain at 0.11.1 as per issue β strategy (this PR rides along on the same tag).

Scope deviations

None. Implementation follows the issue's Worker-first → bridges → docs/tests sequence.

Later / non-blocking

None identified during self-review.

Post-merge manual work required (Master)

  • Register Callback URL https://github-webhook.smgjp.com/oauth/callback in the liplus-webhook-mcp GitHub App settings. Without this, GitHub will reject the redirect and callback will 404. Documented in the PR body.

Next

Mode = auto: no external human review required. Proceeding to merge (squash).

@liplus-lin-lay liplus-lin-lay merged commit 55da074 into main Apr 20, 2026
3 checks passed
@liplus-lin-lay liplus-lin-lay deleted the 209-oauth-web-revert branch April 20, 2026 15:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant