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

Skip to content

fix(coderd/oauth2provider): accept any registered redirect_uri, not only the first - #28914

Closed
matifali wants to merge 2 commits into
mainfrom
fix/oauth2-multiple-redirect-uris
Closed

fix(coderd/oauth2provider): accept any registered redirect_uri, not only the first#28914
matifali wants to merge 2 commits into
mainfrom
fix/oauth2-multiple-redirect-uris

Conversation

@matifali

@matifali matifali commented Sep 2, 2026

Copy link
Copy Markdown
Member

Problem

Cursor desktop cannot complete OAuth2 against the remote MCP server. Cursor registers two redirect_uris in one DCR request (cursor://anysphere.cursor-mcp/oauth/callback and https://www.cursor.com/agents/mcp/oauth/callback) and then authorizes with the second. Coder stored both but validated redirect_uri only against callback_url, which is redirect_uris[0], so /oauth2/authorize returned:

400 - Invalid Query Parameters
Query param "redirect_uri" must exactly match cursor://anysphere.cursor-mcp/oauth/callback

app.RedirectUris was written by registration and never read again.

Fix

  • registeredRedirectURL(app, presented): if the presented redirect_uri is in app.RedirectUris, it becomes the URL the request must match exactly; otherwise fall back to app.CallbackURL so unregistered URIs are rejected as before. Used by ShowAuthorizePage, ProcessAuthorize, and Tokens.
  • Making redirect_uris authoritative surfaced a gap: UpdateApp kept the list while changing callback_url, so an admin edit would no longer revoke old targets on a DCR app. It now replaces the list with the new callback when the callback changes.
  • Per RFC 6749 §3.1.2.3, /oauth2/authorize requires redirect_uri when the client registered several. Defaulting to the primary would send the code to the desktop deep link when a web flow omits the parameter.
  • An unregistered redirect_uri is reported against the registered set rather than "must exactly match ", and logCorruptCallback logs the URI that failed.
  • Apps created through the UI/API have an empty redirect_uris, so their behavior is unchanged.

Validation

TestOAuth2MultipleRegisteredRedirectURIs registers Cursor's exact two URIs and covers: consent page (GET) and code issuance with the second URI; Location points at the presented URI; unregistered URI rejected on GET, POST, and token exchange with the new message; omitted redirect_uri rejected; exchange citing the other registered URI rejected (invalid_grant); admin callback edit revokes the replaced URI while the new one keeps working. Each fix was reverted in isolation to confirm the corresponding subtest fails.

go test ./coderd/oauth2provider/... ./coderd/httpapi/... and go test ./coderd -run TestOAuth2 pass; golangci-lint clean.

Verified end to end from Cursor desktop against a dev server on this branch (--experiments=oauth2,mcp-server-http, DCR enabled, *.try.coder.app tunnel): Cursor registered both callbacks, the browser showed the consent page instead of the 400, and the coder MCP server connected and answered coder_get_authenticated_user.

Deep review summary

Five independent reviewers (Security, Contract, Edge Case, Test Auditor, Go Architect) on the first revision. All agreed exact matching was preserved with a single enforcement point, no open-redirect path, the code-to-URI binding was untouched, and the test was authentic. Findings addressed in the second commit:

Severity Finding Resolution
P1 Admin callback edit no longer revoked DCR redirect targets UpdateApp replaces redirect_uris when the callback changes; test
P2 GET /oauth2/authorize path untested GET subtests
P2 Cross-URI and unregistered-URI token exchange untested Two exchange subtests
P3 Omitted redirect_uri with several registered defaulted to primary Now 400 per RFC 6749 §3.1.2.3
P3 Rejection message named only the primary Message names the registered set
P3 logCorruptCallback logged the wrong URL Logs the failing URI

Not addressed here: the admin API/UI still exposes only callback_url, not redirect_uris. Worth a follow-up so admins can see the full allowlist.

Closes #28910
Related to #19581

🤖 This PR was created with the help of Coder Agents, and needs a human review. 🧑💻

@matifali matifali added the experimental Changes that might not necessarily be merged, until its approved to proceed with. label Sep 2, 2026
…nly the first

Dynamic client registration stores every redirect_uris entry, but authorize
and token validated redirect_uri only against callback_url, the first entry.
Cursor registers a desktop deep link and a web callback and uses the second,
so /oauth2/authorize answered 400.

- Match the presented redirect_uri against redirect_uris; fall back to
  callback_url for API-created apps and unregistered values.
- Require redirect_uri on /oauth2/authorize when several are registered
  (RFC 6749 3.1.2.3) and report misses against the registered set.
- UpdateApp replaces redirect_uris when an admin changes the callback, so the
  edit revokes the previous targets now that the list is authoritative.
@matifali
matifali force-pushed the fix/oauth2-multiple-redirect-uris branch from 441602b to 9f5f260 Compare September 4, 2026 18:36
@matifali
matifali requested a review from BobbyHo September 8, 2026 19:08
@linear-code
linear-code Bot marked this pull request as ready for review September 8, 2026 19:08
@coderagents

coderagents Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Documentation Check

This PR makes redirect_uris authoritative for DCR (RFC 7591) clients: a client that registers several redirect URIs can now authorize with any of them, redirect_uri becomes required when more than one is registered (RFC 6749 §3.1.2.3), the mismatch error names the registered set, and an admin callback_url edit now replaces the redirect_uris allowlist. docs/admin/integrations/oauth2-provider.md documents this surface in detail and currently describes single-URI exact matching, so it is now inaccurate.

Updates Needed

  • docs/admin/integrations/oauth2-provider.md - The "Invalid redirect_uri" troubleshooting entry says the redirect URI must "exactly match the one registered" (singular). For DCR clients, a redirect_uri matching any of the app's registered redirect_uris is now accepted. Reword to reflect matching against the registered set.
  • docs/admin/integrations/oauth2-provider.md - The "invalid_request" for a rejected parameter section states a redirect_uri fails unless it matches "the one registered for the application." Update the same singular assumption here.
  • docs/admin/integrations/oauth2-provider.md - Document the new rule that a client which registered more than one redirect_uri must send redirect_uri on /oauth2/authorize; omitting it now returns invalid_request (Query param "redirect_uri" is required because the client registered more than one redirect URI) rather than defaulting to the primary. A troubleshooting entry or a note in the DCR / redirect URI section would fit.
  • docs/admin/integrations/oauth2-provider.md - Note that editing an app's callback_url (admin UI / management API) now replaces the DCR-registered redirect_uris allowlist, so previously registered targets stop being accepted after a callback edit.

New Documentation Needed

  • docs/admin/integrations/oauth2-provider.md - Add a short statement that DCR clients may register multiple redirect_uris and use any of them (the registration example and prose currently imply a single callback), so integrators like Cursor understand the multi-URI behavior.

Automated review via Coder Agents

@BobbyHo

BobbyHo commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Hi @matifali, thank you for preparing a fix for this issue!

Just wanted to let you know that I’ve also prepared a fix in #29014, and those changes need to be built on top of another fix in #29013.

If it’s okay with you, we could close this PR for now. I’ll keep you posted and update the ticket once #29014 is merged. Thanks!

matifali commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

Thats perfect. I will close it.

@matifali matifali closed this Sep 8, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 8, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

experimental Changes that might not necessarily be merged, until its approved to proceed with.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: OAuth2 authorize rejects redirect_uri that is not the first registered redirect_uris entry (Cursor MCP)

2 participants