refactor(oauth): replace device flow with Worker-hosted web OAuth + fix refresh desync#210
Merged
Merged
Conversation
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
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
github-webhook-mcp | a4719c1 | Apr 20 2026, 03:01 PM |
liplus-lin-lay
commented
Apr 20, 2026
Member
Author
liplus-lin-lay
left a comment
There was a problem hiding this comment.
Self-review: PASS
Acceptance check (issue #209)
Worker (worker/src/oauth.ts, oauth-store.ts, index.ts)
/oauth/authorizeand/oauth/callbackrestored with Worker-fixed redirect_uri — RC2 (ephemeral localhost port) structurally eliminated.- New grant_type
urn:ietf:params:oauth:grant-type:web_authorization_polladded 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}; removesdevice:/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, legacyAuthRequiredError/formatAuthRequiredResponse, device-branchgetAccessTokenForToolCall) removed. - Web flow implemented: open authorize URL → background poll
/oauth/tokenwithweb_authorization_pollgrant → save tokens withflow: "web"marker. - RC1 fix: on
invalid_grantduring 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/devicepath 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.mdrealigned.
Tests
mcp-server/test/auth-required.test.mjsdeleted;web-auth-required.test.mjsadded.mcp-server/test/migration.test.mjsupdated to verifyflow="web"marker +invalid_grantdetection predicate.worker/test/oauth.test.tsrewritten (13 tests).- Bridge tests 11/11 pass; CI all green.
Version
manifest.json/package.json/server.jsonremain 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/callbackin theliplus-webhook-mcpGitHub 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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
invalid_grantを返した際、直ちに全面 re-auth に遷移せず tokens file を再読み込みする。refresh_tokenを採用して再試行。RC2: localhost ephemeral port unreachability
redirect_uriをhttps://<worker>/oauth/callbackに固定。localhost listener 依存を完全に消去。変更点
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/tokenにgrant_type=urn:ietf:params:oauth:grant-type:web_authorization_pollを追加。RFC 8628 §3.5 のエラー形式(authorization_pending/access_denied/expired_token)を踏襲refresh_tokenrotation は既存ロジックをそのまま流用(無変更)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)performOAuthFlow/requestDeviceAuthorization/pollForDeviceToken/AuthRequiredError/formatAuthRequiredResponse/getAccessTokenForToolCallの device-flow ブランチ/oauth/token(web_authorization_poll grant)を pollopenBrowser()は web flow の authorize URL を開く用途で流用"device"→"web"に更新。旧 marker のファイルは無視され、初回ツール呼び出しで新しい web flow に合流refreshAccessToken()がinvalid_grantを返したときtryRefreshViaDiskReread()が tokens file を再読み込みし、別プロセスが rotation していればその最新 refresh_token を採用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_typemcp-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 の手動作業(マージ後)
liplus-webhook-mcpの Callback URL にhttps://github-webhook.smgjp.com/oauth/callbackを登録/oauth/callbackを登録」と案内済みCI / 手動検証
worker npm test/mcp-server npm test/local-mcp npx tsc --noEmitいずれもグリーン