Skip to content

RFC 8252: Implement loopback redirect URI port flexibility for native apps #41

@leodip

Description

@leodip

Summary

The authorization server does not comply with RFC 8252 Section 7.3, which requires allowing any port for loopback redirect URIs. Native apps using dynamic port selection for OAuth callbacks are currently rejected.

RFC 8252 Requirement

Section 7.3 states:

"The authorization server MUST allow any port to be specified at the time of the request for loopback IP redirect URIs"

This is because native apps typically:

  1. Bind to an OS-assigned available port (cannot predict which port)
  2. Start a temporary HTTP server on that port to receive the callback
  3. Need the authorization server to accept any port for loopback addresses

Current Behavior (Non-Compliant)

src/core/validators/authorize_validator.go:156-164:

for _, r := range client.RedirectURIs {
    if input.RedirectURI == r.URI {  // Exact string match only
        clientHasRedirectURI = true
    }
}

Example:

  • Registered URI: http://127.0.0.1/callback
  • Request URI: http://127.0.0.1:54321/callback
  • Result: ❌ Rejected (should be accepted)

Expected Behavior (RFC 8252 Compliant)

For loopback addresses only (127.0.0.1, [::1], localhost):

  • Match scheme, hostname, and path
  • Ignore port differences
Registered Requested Result
http://127.0.0.1/callback http://127.0.0.1/callback ✅ Accept
http://127.0.0.1/callback http://127.0.0.1:12345/callback ✅ Accept
http://127.0.0.1:8080/callback http://127.0.0.1:54321/callback ✅ Accept
http://[::1]/callback http://[::1]:9999/callback ✅ Accept
https://example.com/callback https://example.com:8080/callback ❌ Reject (not loopback)

Proposed Solution

func isLoopbackURI(uri string) bool {
    parsed, err := url.Parse(uri)
    if err != nil {
        return false
    }
    host := parsed.Hostname()
    return host == "127.0.0.1" || host == "::1" || host == "localhost"
}

func loopbackURIsMatch(registered, requested string) bool {
    regParsed, err1 := url.Parse(registered)
    reqParsed, err2 := url.Parse(requested)
    if err1 != nil || err2 != nil {
        return false
    }
    // Compare scheme, host (without port), and path
    return regParsed.Scheme == reqParsed.Scheme &&
           regParsed.Hostname() == reqParsed.Hostname() &&
           regParsed.Path == reqParsed.Path
}

// In ValidateClientAndRedirectURI:
for _, r := range client.RedirectURIs {
    if input.RedirectURI == r.URI {
        clientHasRedirectURI = true
        break
    }
    // RFC 8252 Section 7.3: Allow port variance for loopback
    if isLoopbackURI(r.URI) && loopbackURIsMatch(r.URI, input.RedirectURI) {
        clientHasRedirectURI = true
        break
    }
}

Files Affected

  • src/core/validators/authorize_validator.go - Main fix location
  • src/core/validators/authorize_validator_test.go - Add test cases

Test Cases Needed

  1. Loopback with no port registered, port in request → Accept
  2. Loopback with port registered, different port in request → Accept
  3. Loopback with mismatched path → Reject
  4. Loopback with mismatched scheme (http vs https) → Reject
  5. Non-loopback with port difference → Reject (exact match required)
  6. IPv6 loopback [::1] with port variance → Accept

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions