Skip to content

Allow RFC 8252 loopback redirect URIs for native/public OAuth clients #334

Description

@tossp

Summary

In production, MetaMCP's OAuth Dynamic Client Registration (DCR) rejects loopback redirect URIs commonly used by native/public clients. This prevents clients such as Pi Adapter from registering a local callback even though RFC 8252 defines loopback redirects as an appropriate native-app pattern.

Reproduction

Using the public /oauth/register endpoint, submit a generic DCR request such as:

POST /oauth/register
Content-Type: application/json
{
  "redirect_uris": ["http://localhost:19876/callback"]
}

The example callback is based on Pi Adapter's documented/use-in-the-wild dynamic callback pattern. This is a generic reproduction only; no private deployment is required.

Actual behavior

With NODE_ENV=production, registration returns 400 invalid_redirect_uri with an error like:

Invalid redirect URI: http://localhost:19876/callback. Must use secure scheme and valid format.

Expected behavior

Registration should accept a narrowly constrained loopback HTTP redirect for native/public clients, while continuing to reject other insecure or private-network redirect URIs. RFC 8252 §7.3 explicitly covers loopback IP literal redirect URIs and requires authorization servers to allow any port; loopback IP literals are the normative form. localhost is included here as a common compatibility case used by clients such as Pi Adapter, not as a claim that RFC 8252 requires it.

Root cause

The production-only rejection comes from the redirect validation path:

  • apps/backend/src/routers/oauth/registration.ts calls validateRedirectUri() and maps failure to 400 invalid_redirect_uri.
  • apps/backend/src/routers/oauth/utils.ts rejects non-HTTPS schemes in production and also rejects localhost/loopback/private addresses, so the native-app loopback exception is never available.

Suggested secure fix

  • In production, allow http://127.0.0.1:<ephemeral-port>/... and http://[::1]:<ephemeral-port>/....
  • Optionally allow http://localhost:<ephemeral-port>/... for compatibility with common native clients.
  • Continue rejecting other HTTP hosts and private addresses; require HTTPS for public redirect URIs.
  • Preserve PKCE, state, and exact matching against the redirect URI registered for the client.
  • Add positive and negative tests covering IPv4 loopback, IPv6 loopback, optional localhost, arbitrary ports, public HTTP rejection, and private-address rejection.

Acceptance tests

  • Production DCR accepts the constrained loopback cases above, including arbitrary callback ports.
  • Production DCR continues to reject non-loopback HTTP and private-network redirect URIs.
  • Public HTTPS redirect URI behavior remains unchanged.
  • Authorization-code redirect validation still requires an exact registered URI, with PKCE and state protections intact.

Related but different

The following public reports address MetaMCP-generated OAuth/DCR service URLs becoming localhost behind a reverse proxy. They do not modify validateRedirectUri() and are not this compatibility defect:

References

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