Skip to content

OAuth 2.1 server: authorize rejects RFC 8252 loopback ephemeral-port redirect_uri (exact-match, no port exemption) #2703

Description

@ralph-cmyk

Bug report

  • I confirm this is a bug with Supabase, not with my own application.
  • I confirm I have searched the Docs, GitHub Discussions, and Discord.

Describe the bug

The OAuth 2.1 server's authorization endpoint validates redirect_uri with plain exact string matching and no loopback exemption, so native clients that follow RFC 8252 §7.3 / OAuth 2.1 §2.3.1 — registering a portless loopback redirect (http://127.0.0.1/callback) and binding a random ephemeral port per authorization request — can never complete authorization.

internal/api/oauthserver/authorize.go (master @ 0fb56ca):

func (s *Server) isValidRedirectURI(client *models.OAuthServerClient, redirectURI string) bool {
	registeredURIs := client.GetRedirectURIs()
	for _, registeredURI := range registeredURIs {
		// exact string matching per OAuth2 spec
		if registeredURI == redirectURI {
			return true
		}
	}
	return false
}

OAuth 2.1 (draft-ietf-oauth-v2-1-15, §2.3.1) makes the loopback exemption mandatory:

Authorization servers MUST reject authorization requests that specify a redirect URI that doesn't exactly match one that was registered, with an exception for loopback redirects, where an exact match is required except for the port URI component.

The token endpoint's exchange-time comparison (internal/api/oauthserver/handlers.go, params.RedirectURI != authorization.RedirectURI) compares against the authorize-time value, so it is consistent once the authorize-time check accepts the loopback variant.

This bites every MCP native client: the MCP authorization spec (2026-07-28) builds on OAuth 2.1 and Client ID Metadata Documents, and real clients (e.g. Claude Code's CIMD document at https://claude.ai/oauth/claude-code-client-metadata) register exactly the portless loopback shape:

"redirect_uris": ["http://localhost/callback", "http://127.0.0.1/callback"]

Observed failure (hosted project, OAuth 2.1 server enabled): GET /auth/v1/oauth/authorize?...&redirect_uri=http%3A%2F%2F127.0.0.1%3A3118%2Fcallback400 {"error_code":"validation_failed","msg":"invalid redirect_uri"} for a client whose registered list contains http://127.0.0.1/callback.

To Reproduce

  1. Register an OAuth server client (DCR or admin API) with redirect_uris: ["http://127.0.0.1/callback"].
  2. Start an authorization request with redirect_uri=http://127.0.0.1:3118/callback (any ephemeral port).
  3. Authorize returns 400 invalid redirect_uri instead of proceeding.

Expected behavior

Per OAuth 2.1 §2.3.1 / RFC 8252 §7.3, when the registered redirect URI is http on a loopback literal (127.0.0.1, [::1] — and practically localhost), the authorization server should match the requested URI ignoring only the port component (scheme, host, path, query still exact).

System information

  • Supabase hosted project (OAuth 2.1 server feature), also present in supabase/auth master @ 0fb56ca (2026-08-12).

Additional context

Downstream we proxy the authorize endpoint and currently work around this by registering a fixed HTTPS relay callback on the client row and relaying the code to the client's ephemeral loopback port; a native fix in GoTrue would remove that complexity for every MCP-style native client.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions