Bug report
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%2Fcallback → 400 {"error_code":"validation_failed","msg":"invalid redirect_uri"} for a client whose registered list contains http://127.0.0.1/callback.
To Reproduce
- Register an OAuth server client (DCR or admin API) with
redirect_uris: ["http://127.0.0.1/callback"].
- Start an authorization request with
redirect_uri=http://127.0.0.1:3118/callback (any ephemeral port).
- 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.
Bug report
Describe the bug
The OAuth 2.1 server's authorization endpoint validates
redirect_uriwith 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):OAuth 2.1 (draft-ietf-oauth-v2-1-15, §2.3.1) makes the loopback exemption mandatory:
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:Observed failure (hosted project, OAuth 2.1 server enabled):
GET /auth/v1/oauth/authorize?...&redirect_uri=http%3A%2F%2F127.0.0.1%3A3118%2Fcallback→400 {"error_code":"validation_failed","msg":"invalid redirect_uri"}for a client whose registered list containshttp://127.0.0.1/callback.To Reproduce
redirect_uris: ["http://127.0.0.1/callback"].redirect_uri=http://127.0.0.1:3118/callback(any ephemeral port).400 invalid redirect_uriinstead of proceeding.Expected behavior
Per OAuth 2.1 §2.3.1 / RFC 8252 §7.3, when the registered redirect URI is
httpon a loopback literal (127.0.0.1,[::1]— and practicallylocalhost), the authorization server should match the requested URI ignoring only the port component (scheme, host, path, query still exact).System information
supabase/authmaster @ 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.