Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: gate the OAuth2 provider on CODER_OAUTH2_PROVIDER_ENABLE - #29251

Draft
BobbyHo wants to merge 4 commits into
coder-plat-492-oauth-opt-in-flagfrom
plat-492-2-gate
Draft

feat: gate the OAuth2 provider on CODER_OAUTH2_PROVIDER_ENABLE#29251
BobbyHo wants to merge 4 commits into
coder-plat-492-oauth-opt-in-flagfrom
plat-492-2-gate

Conversation

@BobbyHo

@BobbyHo BobbyHo commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

TL;DR

Second of five for PLAT-492. Every OAuth2 provider gate now reads CODER_OAUTH2_PROVIDER_ENABLE instead of the oauth2 experiment. The flag is off by default, so this is the breaking step: a deployment that relied on CODER_EXPERIMENTS=oauth2 must set the flag or its OAuth2 clients get 404 responses after upgrading.

PR What it does
#29248 Adds the flag, a public build info field, and the test default.
this Gates the OAuth2 routes and dashboard navigation on the flag.
#29257 Makes the oauth2 experiment a no-op with a startup warning.
next Logs the provider state and any registered apps at startup.
next Docs.

Implementation Details

The gate

  • httpmw.RequireOAuth2Provider answers 404 with the standard route-not-found body while the provider is disabled. A disabled provider is indistinguishable from a deployment that never had one, which is what RFC 8414 and RFC 9728 discovery clients expect. There is no RFC 6749 error code for "no authorization server here", and inventing one would confirm there could be.
  • No bypass for development builds. The experiment middleware waved devel builds through; a GA control must not. Tests get the provider through the coderdtest default from feat: add the CODER_OAUTH2_PROVIDER_ENABLE deployment flag #29248.
  • The check is a func() bool read through the deployment values on every request, so a runtime toggle can be added later without touching call sites.

Where it applies

  • Both discovery documents, the whole /oauth2 tree, and the admin apps API under /api/v2/oauth2-provider/apps.
  • /api/experimental/mcp/http needs the flag and still needs the mcp-server-http experiment. That experiment check no longer has a dev bypass either; it only had one because it shared the OAuth2 call.
  • The AI Gateway's deprecated MCP injection reads the flag and reports it by name when it is off.

Settings stay reachable

  • GET/PUT /api/v2/oauth2-provider/settings work while the provider is disabled, so an admin can turn on dynamic client registration before enabling the provider. The gate moved one level down onto /apps. The setting has no effect until the flag is set.

Dashboard

  • Both "OAuth2 Applications" navigation items read buildInfo.oauth2_provider. The experiments.includes("oauth2") check and the devel escape hatch are gone. The admin sidebar no longer needs its experiments prop.

Follow-ups this PR leaves alone

  • The oauth2 experiment is still parsed and listed by /api/v2/experiments. The next PR turns it into a no-op with a warning naming the flag. Until it merges, main should not be released.
  • No badge or notice shows the provider state in the dashboard. Decided 2026-09-11: an explicitly set config flag is sufficient for GA.
Tests
  • TestRequireOAuth2Provider covers the middleware on its own: 404 and no call to the next handler when disabled, pass-through when enabled.
  • TestOAuth2ProviderDisabled requests all eight gated routes with the flag off and asserts 404 with the exact route-not-found body, then repeats with the flag on and asserts none is 404. It also checks the settings endpoints answer with the flag off, and that the MCP endpoint still returns the experiment's 403 when only the flag is set.
  • TestOAuth2ProviderSettingsAuditDiff now runs with the provider disabled, which proves the settings write is still audited in that state.
  • The AI Gateway table test gains a provider on/off axis in place of the OAuth2 experiment.
  • Stories for both sidebars cover the enabled and disabled states. The disabled story uses a devel version so the screenshot shows the build type no longer matters.
  • No hand-built DeploymentValues fixture needed changing.

@BobbyHo BobbyHo added the release/breaking This label is applied to PRs to detect breaking changes as part of the release process label Sep 11, 2026
@linear-code

linear-code Bot commented Sep 11, 2026

Copy link
Copy Markdown

PLAT-492

Move every OAuth2 provider gate off the oauth2 experiment and onto the
deployment flag added in the previous commit. The flag is off by default,
so a deployment that relied on CODER_EXPERIMENTS=oauth2 must set it or its
OAuth2 clients receive 404 responses after upgrading.

- Add httpmw.RequireOAuth2Provider, which answers 404 while the provider is
  disabled. A disabled provider looks like a deployment that never had one.
  There is no bypass for development builds.
- Use it on both discovery documents, the /oauth2 tree, the admin apps API,
  and the MCP HTTP endpoint, which keeps its mcp-server-http experiment
  check. The AI Gateway's deprecated MCP injection reads the flag too.
- Keep /api/v2/oauth2-provider/settings reachable while disabled so an
  admin can configure dynamic client registration before enabling the
  provider.
- Gate both OAuth2 navigation items on the new build info field instead of
  the experiment, and drop the development-build escape hatch.

The oauth2 experiment is still parsed and reported; a follow-up makes it a
no-op with a specific warning.

Part of PLAT-492.
@BobbyHo

BobbyHo commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Chat: Review in progress (18/18 reviewers complete) | View chat
Requested: 2026-09-11 22:11 UTC by @BobbyHo

deep-review v0.9.0 | Round 1 | 6ad05a6..5334b19

Last posted: Round 1, 7 findings (1 P2, 4 P3, 1 Nit, 1 Note), COMMENT. Review

Finding inventory

Finding inventory: PR #29251

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P3 Open DeploymentSidebarView.stories.tsx:99 New OAuth2 enabled/disabled stories assert nothing (no play) R1 Bisky P3, Kite/Chopper/Meruem Nit, Netero Note Yes
CRF-2 P3 Open UserSettingsPage/Sidebar.stories.tsx:15 Same missing-play gap in the user-settings sidebar stories R1 Bisky P3 Yes
CRF-3 P3 Open coderd.go:1088 Closure comment promises runtime toggle "without changing call sites" but buildinfo and aibridge snapshot the bool R1 Ryosuke P3; Hisoka/Mafuuu/Pariston/Kurapika/Knov/Kite/Chopper/Meruem/Zoro/Gon Note Yes
CRF-4 P3 Open coderd.go:2012 "Settings stay reachable" comment sits above /apps (gated), not /settings R1 Leorio Yes
CRF-5 P2 Open docs/admin/integrations/oauth2-provider.md:25 Canonical setup docs still tell admins to enable the oauth2 experiment; gate now reads CODER_OAUTH2_PROVIDER_ENABLE R1 Mafuuu Yes
CRF-6 Nit Open aibridgedserver.go:145 Field doc comment first sentence restates the field name R1 Gon Yes
CRF-7 Note Open site/src/router.tsx:649 OAuth2 pages stay routable by direct URL when flag off, render ErrorAlert from 404 (pre-existing) R1 Nami Yes

Round log

Round 1

Netero clean (one Note). Panel of 17 (bisky, hisoka, mafu-san, mafuuu, pariston, komugi, gon, leorio, kurapika, knov, kite, ryosuke, chopper, nami, ging-go + wildcards meruem, zoro). Security perimeter verified complete: every prior RequireExperimentWithDevBypass(ExperimentOAuth2) gate converted, no production route left ungated, disabled-provider 404 indistinguishability confirmed by Kurapika/Knov/Hisoka. 1 P2, 4 P3, 1 Nit, 1 Note. Reviewed against 6ad05a6..5334b19.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Komugi flake/determinism
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is a clean, well-scoped security migration. Every prior RequireExperimentWithDevBypass(ExperimentOAuth2) gate now reads the deployment flag through one small RequireOAuth2Provider middleware, no production route was left ungated (grep confirms only the experiment definition survives, as the stack intends), and the disabled provider returns the exact route-not-found body so it is indistinguishable from a deployment that never had one, which is what the RFC 8414/9728 discovery clients expect. The negative path is directly tested: TestOAuth2ProviderDisabled asserts 404 on all eight gated routes with the flag off and not-404 with it on, so a typo in the gated-route list fails the enabled arm rather than passing vacuously. The incidental removal of the dev bypass on the MCP HTTP experiment is called out and pinned by tests. Kurapika, Knov, and Hisoka each probed the perimeter independently and found it complete. As Hisoka put it: "Boring, in the way a locked door is boring."

Counts: 1 P2, 4 P3, 1 Nit, 1 Note. No P0/P1.

The P2 is a cross-cutting doc break outside the diff: the canonical setup docs still tell admins to enable the oauth2 experiment, which this PR makes ineffective for the gate. The PR schedules a docs PR later in the stack but links no ticket for these specific pages, and the "do not release main until the experiment no-op PR merges" warning covers the experiment PR, not docs. That deferral needs a human decision: file a tracking ticket for the stale pages or state explicitly that the PLAT-492 stack tracking is sufficient.

The four P3s are all comment/coverage accuracy, not behavior: the frontend gating behavior has no mechanical assertion (only screenshots), one load-bearing comment annotates the wrong route, and the func() bool comment promises a runtime toggle the two sibling readers cannot honor. Each is cheap to fix and worth fixing now, since the author-agent will not revisit this code.


docs/admin/integrations/oauth2-provider.md:25

P2 [CRF-5] The canonical "Enable OAuth2 Provider" docs still tell admins to set the oauth2 experiment, which this PR makes ineffective for the gate. (Mafuuu)

after this PR every gate reads CODER_OAUTH2_PROVIDER_ENABLE [...] Setting only the experiment leaves the flag off, so RequireOAuth2Provider returns 404 for the whole /oauth2 tree, both discovery documents, and /api/v2/oauth2-provider/apps.

Confirmed: docs/admin/integrations/oauth2-provider.md lines 11, 25-34, and 401 all instruct coder server --experiments oauth2 / CODER_EXPERIMENTS=oauth2, and docs/ai-coder/mcp-server.md:186,238 repeat it. An admin who follows the documented path to turn on the provider gets 404 for every OAuth2 client, the exact breaking-upgrade failure the PR calls out, delivered to anyone reading the docs. The PR defers docs to a later PR in the stack but links no ticket for these specific pages, and the stack's release-gate warning covers the experiment no-op PR, not docs, so the pages stay wrong on main across PRs 3 and 4. This needs a human decision: file a tracking ticket for the stale pages, or state explicitly that the PLAT-492 epic tracking is sufficient. Root cause is one class: every doc reference to the oauth2 experiment as the enable mechanism; fix them together.

🤖

site/src/router.tsx:649

Note [CRF-7] With the flag off, the OAuth2 pages stay routable by direct URL and render an ErrorAlert from the 404, not a not-found page. (Nami)

The routes at /settings/oauth2-provider and /deployment/oauth2-provider/apps are registered unconditionally. When the flag is off, the nav items are hidden but a bookmarked or typed URL still mounts the page, which fetches /api/v2/oauth2-provider/apps, gets the 404, and shows ErrorAlert (degraded, not blank).

Not introduced by this PR: the API was gated before via the experiment middleware, so the same URL-reaches-disabled-page-then-errors class already existed, and behavior stays consistent with the new gate. Flagging so a human can decide whether a disabled provider should redirect to a not-found page rather than surface the raw 404; hiding the nav does not gate the route.

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread site/src/modules/management/DeploymentSidebarView.stories.tsx
Comment thread site/src/pages/UserSettingsPage/Sidebar.stories.tsx
Comment thread coderd/coderd.go Outdated
Comment thread coderd/coderd.go Outdated
Comment thread coderd/aibridgedserver/aibridgedserver.go Outdated
@BobbyHo

BobbyHo commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Re the P2 in review 5183941183 (CRF-5, stale oauth2 experiment instructions in docs/admin/integrations/oauth2-provider.md and docs/ai-coder/mcp-server.md):

PR 5 of this stack owns the docs. It rewrites both pages plus docs/install/releases/feature-stages.md to enable the provider with CODER_OAUTH2_PROVIDER_ENABLE, and it merges in the same sitting as this PR. The stack table in the description tracks it, so no separate ticket. Until it lands, #29257 makes a deployment that follows the old instructions log a startup warning naming the flag instead of failing silently.

RequireOAuth2Provider takes a bool: the flag is read once at startup in
three places, so the closure promised a runtime toggle that would ship
half-working. The settings comment moves onto the route it describes,
and both enabled sidebar stories set the flag explicitly instead of
relying on the fixture default.
The MCP HTTP endpoint no longer has a development-build bypass for the
mcp-server-http experiment, so tests that reach it must opt in.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release/breaking This label is applied to PRs to detect breaking changes as part of the release process

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant