Skip to content

fix(oauth): allow absolute OAuth authorization consent URLs#2417

Open
SergioChan wants to merge 2 commits intosupabase:masterfrom
SergioChan:fix/oauth-absolute-authorization-path
Open

fix(oauth): allow absolute OAuth authorization consent URLs#2417
SergioChan wants to merge 2 commits intosupabase:masterfrom
SergioChan:fix/oauth-absolute-authorization-path

Conversation

@SergioChan
Copy link

Summary

  • treat GOTRUE_OAUTH_SERVER_AUTHORIZATION_PATH as either a relative path or an absolute URL when building the consent redirect target
  • preserve existing relative-path behavior (/oauth/consent joins against SITE_URL) while allowing hosted users to point consent flows at a dedicated external UI
  • add focused unit coverage for relative joins and absolute URL passthrough in buildAuthorizationURL

Testing

  • go test ./internal/api/oauthserver -run 'TestBuildAuthorizationURL|TestValidateRequestOrigin'

Related

@SergioChan SergioChan requested a review from a team as a code owner March 10, 2026 10:43
@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: de76ea81-d654-4cf5-a451-3aafc7e8804f

📥 Commits

Reviewing files that changed from the base of the PR and between 10e58ea and 26ac7c0.

📒 Files selected for processing (2)
  • internal/api/oauthserver/authorize.go
  • internal/api/oauthserver/authorize_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/api/oauthserver/authorize_test.go

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved authorization URL handling: absolute URLs are returned unchanged; relative paths are joined reliably with base URLs. Redirect URLs now correctly append the authorization_id query parameter while preserving existing query parameters and fragments, with graceful fallback for malformed inputs.
  • Tests

    • Added tests covering URL construction and redirect behavior across various base URL, path, query, and fragment combinations.

Walkthrough

The PR adds a new helper that builds the authorization redirect URL by appending authorization_id as a query parameter while preserving existing query parameters and fragments, and falls back on a simple format if URL parsing fails. It also updates buildAuthorizationURL to return absolute pathToJoin unchanged and to correctly join relative paths with baseURL. Unit tests covering relative/absolute paths and redirect URL composition were added.

Assessment against linked issues

Objective Addressed Explanation
Support absolute URLs for AuthorizationPath to enable custom consent URIs [#2408]
Enable redirects to Edge Functions or custom branded UIs without misconfiguring global SiteURL [#2408]

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/api/oauthserver/authorize.go`:
- Around line 625-629: The issue is that absolute URLs returned by
buildAuthorizationURL are being concatenated with "?authorization_id=..." as raw
strings so existing query strings or fragments are lost; to fix, update
buildAuthorizationURL and the redirect construction to treat absolute URLs as
proper url.URL objects: in buildAuthorizationURL, when parsed.IsAbs() return
parsed.String() (preserving parsed.RawQuery and parsed.Fragment), and change the
code that appends the authorization_id (the place that currently does raw string
concatenation like "+\"?authorization_id=\"+id") to parse the returned URL with
url.Parse, use u.Query().Add("authorization_id", id), set u.RawQuery =
q.Encode(), and then use u.String() so query params and fragments are preserved.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: a9c080d4-2a60-4f59-9bc9-f3bc37edbddd

📥 Commits

Reviewing files that changed from the base of the PR and between a2e357f and 10e58ea.

📒 Files selected for processing (2)
  • internal/api/oauthserver/authorize.go
  • internal/api/oauthserver/authorize_test.go

Comment on lines +625 to +629
// buildAuthorizationURL safely joins a base URL with a path, handling slashes correctly.
// If pathToJoin is an absolute URL, it is returned as-is.
func (s *Server) buildAuthorizationURL(baseURL, pathToJoin string) string {
if parsed, err := url.Parse(pathToJoin); err == nil && parsed.IsAbs() {
return pathToJoin
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Absolute consent UIs still fail the follow-up API calls in the browser flow.

This redirect can now send the user to an external consent page, but GET /oauth/authorizations/{authorization_id} and POST /oauth/authorizations/{authorization_id}/consent still reject origins that are not allowed by validateRequestOrigin. So a browser-hosted consent UI on the configured absolute URL still 400s with unauthorized request origin unless that origin is separately duplicated in the allow-list, which means the new config alone does not complete the hosted flow this PR is trying to unblock.


⚠️ Potential issue | 🟠 Major

Preserve query/fragment handling when passing absolute URLs through.

With this new absolute-URL mode, the redirect built at Line 170 is now wrong for valid values like https://consent.example.com/ui?tenant=acme or URLs with a fragment, because ?authorization_id=... is appended by raw string concatenation. In those cases the consent UI will not receive a usable authorization_id.

Proposed fix
 	baseURL := s.buildAuthorizationURL(config.SiteURL, config.OAuthServer.AuthorizationPath)
-	redirectURL := fmt.Sprintf("%s?authorization_id=%s", baseURL, authorization.AuthorizationID)
+	u, err := url.Parse(baseURL)
+	if err != nil {
+		errorRedirectURL := s.buildErrorRedirectURL(params.RedirectURI, oAuth2ErrorServerError, "invalid oauth authorization path", params.State)
+		http.Redirect(w, r, errorRedirectURL, http.StatusFound)
+		return nil
+	}
+	q := u.Query()
+	q.Set("authorization_id", authorization.AuthorizationID)
+	u.RawQuery = q.Encode()
+	redirectURL := u.String()
 
 	http.Redirect(w, r, redirectURL, http.StatusFound)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// buildAuthorizationURL safely joins a base URL with a path, handling slashes correctly.
// If pathToJoin is an absolute URL, it is returned as-is.
func (s *Server) buildAuthorizationURL(baseURL, pathToJoin string) string {
if parsed, err := url.Parse(pathToJoin); err == nil && parsed.IsAbs() {
return pathToJoin
baseURL := s.buildAuthorizationURL(config.SiteURL, config.OAuthServer.AuthorizationPath)
u, err := url.Parse(baseURL)
if err != nil {
errorRedirectURL := s.buildErrorRedirectURL(params.RedirectURI, oAuth2ErrorServerError, "invalid oauth authorization path", params.State)
http.Redirect(w, r, errorRedirectURL, http.StatusFound)
return nil
}
q := u.Query()
q.Set("authorization_id", authorization.AuthorizationID)
u.RawQuery = q.Encode()
redirectURL := u.String()
http.Redirect(w, r, redirectURL, http.StatusFound)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/api/oauthserver/authorize.go` around lines 625 - 629, The issue is
that absolute URLs returned by buildAuthorizationURL are being concatenated with
"?authorization_id=..." as raw strings so existing query strings or fragments
are lost; to fix, update buildAuthorizationURL and the redirect construction to
treat absolute URLs as proper url.URL objects: in buildAuthorizationURL, when
parsed.IsAbs() return parsed.String() (preserving parsed.RawQuery and
parsed.Fragment), and change the code that appends the authorization_id (the
place that currently does raw string concatenation like
"+\"?authorization_id=\"+id") to parse the returned URL with url.Parse, use
u.Query().Add("authorization_id", id), set u.RawQuery = q.Encode(), and then use
u.String() so query params and fragments are preserved.

@SergioChan
Copy link
Author

Thanks for the review — I pushed an update that addresses the URL-concatenation concern.

What changed

  • Switched redirect building to URL parsing/query mutation instead of string concatenation when adding authorization_id.
  • This now preserves existing query parameters and fragments for absolute AuthorizationPath values.
  • Added focused tests for:
    • URL without query
    • URL with existing query
    • URL with query + fragment

Validation

  • go test ./internal/api/oauthserver -run 'TestBuildAuthorization(URL|RedirectURL)'

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.

OAuth 2.1 authorize redirects to invalid /oauth/consent path on hosted projects

1 participant