Skip to content

feat: add redirect URL override for external auth - #28082

Open
code-asher wants to merge 1 commit into
mainfrom
asher/external-auth-redirect-configuration
Open

feat: add redirect URL override for external auth#28082
code-asher wants to merge 1 commit into
mainfrom
asher/external-auth-redirect-configuration

Conversation

@code-asher

@code-asher code-asher commented Aug 12, 2026

Copy link
Copy Markdown
Member

Closes https://linear.app/codercom/issue/DEVEX-759/add-external-auth-redirect-url-override

I smoke tested locally with my deployment on localhost:3000 and the redirect URL at localhost:9999 (the use case requires that localhost:9999 be redirected to the real domain, in my test I basically faked the redirect by editing the URL once GitHub sent me to 9999 rather than stand up a whole proxy).

@linear-code

linear-code Bot commented Aug 12, 2026

Copy link
Copy Markdown

DEVEX-759

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown

Docs preview

Check off each page once it's been reviewed. If a page changes in a later push, its checkbox clears automatically so it gets a fresh look. Pages not yet wired into the docs navigation aren't listed here.

@code-asher

Copy link
Copy Markdown
Member Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Chat: Review posted | View chat
Requested: 2026-08-12 20:42 UTC by @code-asher

Review history
  • R1 (2026-08-12): 16 reviewers, 3 Nit, 3 Note, 2 P1, 6 P2, REQUEST_CHANGES. Review

deep-review v0.9.0 | Round 1 | 1458d27..a55c13f

Last posted: Round 1, 14 findings (2 P1, 6 P2, 3 Nit, 3 Note), REQUEST_CHANGES. Review

Finding inventory

PR #28082 finding inventory

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P1 Open coderd/externalauth/externalauth.go:944 Cross-host RedirectURL breaks OAuth state/PKCE cookies; browser never sends them to the callback host R1 Pariston Yes
CRF-2 P1 Open coderd/externalauth/externalauth.go:944 url.Parse accepts schemeless input; produces relative redirect_uri, breaks at first login with no server-side error R1 Meruem P1, Hisoka P2, Chopper P3, Kurapika P3, Knov P3, Razor P3, Mafuuu P3, Luffy P2 Yes
CRF-3 P2 Open coderd/externalauth/externalauth.go:949 baseRedirectURL.Parse("/external-auth/...") silently drops the override's path prefix; reverse-proxy case fails R1 Meruem P2, Chopper P2, Hisoka P3, Mafuuu P3, Knov P3, Pariston P3, Luffy Note, Kurapika Note Yes
CRF-4 P2 Open codersdk/deployment.go:1179 Field name RedirectURL is a base URL; collides with oauth2.Config.RedirectURL and sibling APIBaseURL convention R1 Gon P1, Zoro Nit, Luffy Nit, Meruem Note, Mafuuu Note Yes
CRF-5 P2 Open codersdk/deployment.go:1177 Doc comment on RedirectURL is bloated, repeats "redirects" three times, never names appended path or when-to-use R1 Leorio P2, Gon P2, Meruem Note Yes
CRF-6 P2 Open docs/admin/external-auth/index.md:46 Docs paragraph shows placeholder and never diagnoses when to set the override R1 Leorio P2, Chopper Note, Mafuuu Note Yes
CRF-7 P2 Open coderd/externalauth/externalauth.go:946 Error message drops provider ID and value, names non-config-facing "base callback url" R1 Leorio P2, Chopper Nit, Mafuuu Nit, Gon Nit Yes
CRF-8 P2 Open coderd/externalauth/externalauth_test.go:1761 Only override test picks the one input shape that hides the path-drop bug; no schemeless or path-prefix coverage R1 Bisky P2, Hisoka Note Yes
CRF-10 Nit Open coderd/externalauth/externalauth_test.go:1753 CustomScopesAndEndpoint repurposed for override; fallback branch lost its assertion R1 Mafu-san Nit, Bisky Nit, Zoro Note, Luffy Note Yes
CRF-11 Nit Open coderd/externalauth/externalauth.go:942 No operator warning log when override is active; sibling OIDC path logs at cli/server.go:205 R1 Kurapika Yes
CRF-12 Nit Open codersdk/deployment.go:1177 Double space after "redirects." ships into rendered API table R1 Zoro, Razor, Luffy, Gon Yes
CRF-13 Note Open codersdk/deployment.go:1179 Field is plain string while sibling OIDC.RedirectURL is serpent.URL; different parse timing and validation R1 Meruem, Kurapika, Mafuuu, Hisoka Yes
CRF-14 Note Open coderd/externalauth/externalauth.go:1103 copyDefaultSettings clause for RedirectURL has no test; no provider-type default carries a redirect override yet R1 Bisky Yes
CRF-15 Note Open coderd/externalauth/externalauth.go:941 Structural alternative: OIDC already solves this class with RedirectAllowedHosts + dynamic per-request redirect_uri R1 Pariston Yes

Contested and acknowledged

(none yet)

Round log

Round 1

Panel round after Netero-only pre-pass. Reviewed against 1458d27..a55c13f. Netero: no findings. Panel: Bisky, Hisoka, Mafu-san, Mafuuu, Pariston, Komugi, Kurapika, Knov, Meruem, Chopper, Luffy, Ging-go, Gon, Leorio + wildcards Zoro, Razor. Ging-go and Komugi returned no findings. 14 findings raised: 2 P1, 6 P2, 3 Nit, 3 Note.

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.

@code-asher
code-asher marked this pull request as ready for review August 12, 2026 20:55

@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.

The change adds a per-provider RedirectURL override for external auth. The addition follows the sibling AuthURL/TokenURL/ValidateURL shape in ConvertConfig and copyDefaultSettings, the CLI + YAML + generated surfaces are in sync, and TestConvertYAML/CustomScopesAndEndpoint was upgraded from an empty accessURL fixture to a fully-qualified one, which Mafu-san flagged as the anti-pattern of a "confidence assertion" done right.

Two P1s block merge. Pariston: the docs example (REDIRECT_URL=https://my.tld against access URL https://example.com) sets the OAuth oauth_state/oauth_pkce_verifier/oauth_redirect cookies on the access URL host (httpmw/oauth2.go:187-200, Path: "/", no Domain) and then hands the IdP a callback URL on a different host. The browser will not send those cookies to the callback host, so the flow fails at httpmw/oauth2.go:239-244 with Cookie "oauth_state" must be provided. HTTPCookieConfig has no Domain field, so admins have no supported way to widen scope. OIDC already solved the same class with RedirectAllowedHosts + dynamic per-request redirect_uri (buildDynamicRedirectURI, httpmw/oauth2.go:162,297); this PR lands a static, weaker mechanism that skips that guardrail. Second P1: schemeless input silently parses to a path-only URL and produces a scheme-less relative redirect_uri. The PR's own CLI test at cli/server_test.go:84 sets CODER_EXTERNAL_AUTH_1_REDIRECT_URL=coder.com, which is exactly this broken shape, and the assertion never reaches ConvertConfig.

P2 cluster on the same code path. Any path prefix on the override is silently discarded (https://my.tld/coder becomes https://my.tld/external-auth/<id>/callback) because the appended callback is an absolute-path reference; this is the reverse-proxy case a subpath override exists to solve, and the docs added in this PR don't warn about it. The field name RedirectURL collides with oauth2.Config.RedirectURL (the actual redirect URI, resolved four lines away in the same function) and with the sibling APIBaseURL shape (codersdk/deployment.go:1204); a reader will reasonably set REDIRECT_URL=https://my.tld/oauth/callback and get a silently mangled result. The SDK doc comment repeats "redirects" three times, never names the /external-auth/<id>/callback suffix, and ships verbatim into swagger, generated docs, and the TypeScript type. The docs page shows a placeholder host and never diagnoses when the override is needed. The new error message drops the provider ID and offending value and names a config surface (base callback url) operators can't grep. The only positive test picks the input shape that hides the join bug.

Nits and Notes: CustomScopesAndEndpoint was repurposed for the override rather than split into two subtests, dropping the fallback assertion; no operator warning log when the override is active (sibling OIDC at cli/server.go:205 does log); double space after "redirects." ships into the rendered API table; sibling OIDC.RedirectURL is serpent.URL while this new field is plain string; the new copyDefaultSettings clause has no test.

Process note: the PR body is a private Linear link only. External reviewers cannot read it. .claude/docs/PR_STYLE_GUIDE.md asks for one to two paragraphs of context.

Netero mechanical pass came back clean.

Verbatim from Luffy: "Yes, AuthURL/TokenURL/ValidateURL don't validate either. That's a separate rot, not a license to keep adding to the pile."

Severity count: 2 P1, 6 P2, 3 Nit, 3 Note.


coderd/externalauth/externalauth_test.go:1753

Nit [CRF-10] CustomScopesAndEndpoint was repurposed for the override; the fallback branch that used to be pinned here is no longer covered. (Mafu-san Nit, Bisky Nit, Zoro Note, Luffy Note)

Before this PR the subtest asserted redirect_uri=/external-auth/gitlab/callback against an empty accessURL. After, the same subtest asserts the override. Fine as far as it goes, but nothing in the suite now pins the default branch, entry.RedirectURL == "" with a real accessURL, against an expected redirect_uri string. Split the sub-test into RedirectURLDefault (empty RedirectURL, real accessURL, assert redirect_uri=https://default.com/external-auth/gitlab/callback) and RedirectURLOverride (the current assertion). One setting, two stones, one test each.

A reader hunting the test that covers the new field will not find it by name. A reader hunting the original scenario will find an assertion that has been repurposed. Split, or at minimum rename to describe what the assertion now proves.

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/externalauth/externalauth.go
Comment thread coderd/externalauth/externalauth.go
Comment thread coderd/externalauth/externalauth.go
Comment thread codersdk/deployment.go
Comment thread codersdk/deployment.go Outdated
Comment thread coderd/externalauth/externalauth.go
Comment thread codersdk/deployment.go Outdated
Comment thread codersdk/deployment.go
Comment thread coderd/externalauth/externalauth.go
Comment thread coderd/externalauth/externalauth.go
@code-asher
code-asher force-pushed the asher/external-auth-redirect-configuration branch from a55c13f to 397860c Compare August 12, 2026 22:11
@coder-tasks

coder-tasks Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Documentation Check

Updates Needed

  • docs/admin/external-auth/index.md - Addressed in ad2145cb: the doc now states "Any path component on the redirect URL is ignored," documenting the scheme/host-only behavior for the REDIRECT_URL override.

Automated review via Coder Agents

@code-asher
code-asher force-pushed the asher/external-auth-redirect-configuration branch from 092f438 to 1d73768 Compare August 12, 2026 22:27
@datadog-coder

This comment has been minimized.

@code-asher
code-asher force-pushed the asher/external-auth-redirect-configuration branch 3 times, most recently from 53369ef to 62339af Compare August 12, 2026 23:36
@code-asher
code-asher force-pushed the asher/external-auth-redirect-configuration branch from 62339af to 2a50431 Compare August 12, 2026 23:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant