authgear / authgear/authgear-server

Feature Request: Support loopback redirect URIs with arbitrary ports

Open
#5,692 1 comment 1 reaction 1 assignee Claimed by @tung2744 View on GitHub
pitch
Dominant language
Go
Stars
2k
Forks
125
Avg merge
2d 17h
Merged PRs (30d)
32

Description

## Summary

Allow OAuth clients configured with a loopback redirect URI (e.g. `http://127.0.0.1/callback` or `http://localhost/callback`) to receive the authorization response on **any port** chosen at runtime, without needing to enumerate every possible port in the client's Authorized Redirect URIs.

## Motivation

Native and CLI applications that use the OAuth Authorization Code flow typically:

1. Start an ephemeral local HTTP server on an OS-assigned free port (port `0`),
2. Construct a `redirect_uri` of the form `http://127.0.0.1:/callback`,
3. Open the system browser pointed at the authorization endpoint, and
4. Receive the authorization code on that loopback port.

Picking a random free port is the standard practice — it avoids collisions when the user has another instance running, when other software is already bound to a fixed port, or when corporate environments restrict specific ports. This is exactly the pattern recommended by **[RFC 8252 — OAuth 2.0 for Native Apps](https://datatracker.ietf.org/doc/html/rfc8252)**, which also instructs the authorization server to permit it:

> **§7.3 Loopback Interface Redirection**
> *"The authorization server MUST allow any port to be specified at the time of the request for loopback IP redirect URIs, to accommodate clients that obtain an available ephemeral port from the operating system at the time of the request."*

Currently Authgear performs an exact-string match in `validateRedirectURI` (`pkg/lib/oauth/handler/resolve.go:60`), so a CLI tool integrating with Authgear has to either:

- Hardcode a single port and hope it is free (fragile), or
- Pre-register a large range of ports in the Authgear client config (ugly, brittle, and effectively unbounded).

Both workarounds make Authgear noticeably harder to integrate than identity providers that already follow RFC 8252 (Google, Auth0, Okta, Microsoft Entra, Keycloak, etc.).

## Proposal

When validating an incoming `redirect_uri` against a client's Authorized Redirect URIs, treat a registered loopback URI as matching any port:

- A registered URI is considered a "loopback" entry if its host is `127.0.0.1`, `[::1]`, or `localhost`, **and** its scheme is `http`.
- For such entries, the port component is ignored when comparing against the incoming `redirect_uri`. Scheme, host, path, and query must still match exactly.
- Non-loopback entries continue to require strict equality (no behavior change).

### Example

Authorized Redirect URIs registered on the client:

```
http://127.0.0.1/callback
https://app.example.com/callback
```

Accepted at runtime:

| Incoming `redirect_uri` | Result |
|---|---|
| `http://127.0.0.1:53412/callback` | allowed (loopback, port ignored) |
| `http://127.0.0.1/callback` | allowed |
| `http://localhost:8000/callback` | rejected (host differs from registered entry) |
| `https://127.0.0.1:53412/callback` | rejected (scheme is not `http`) |
| `http://127.0.0.1:53412/other` | rejected (path differs) |
| `https://app.example.com:9000/callback` | rejected (non-loopback, strict match) |

The tenant admin opts in by registering a loopback URI; clients that do not register one see no behavior change.

## Scope of change

Primary change is in:

- `pkg/lib/oauth/handler/resolve.go` — extend the comparison loop in `validateRedirectURI` with a loopback-aware match.
- `pkg/lib/oauth/handler/resolve_test.go` — add cases covering loopback host variants, port substitution, scheme/path mismatches, and non-loopback regressions.

Documentation:

- Note the loopback exception in the OAuth client redirect URI docs and the Portal "Authorized Redirect URIs" help text.

Out of scope:

- No change to `https` redirect URIs, custom-scheme redirect URIs, or non-loopback hosts.
- No change to PKCE / client authentication requirements (PKCE remains required for public clients as today).

## Security considerations

- The exception is limited to the `http` scheme on loopback hosts, where traffic never leaves the user's machine, so allowing arbitrary ports does not enable cross-origin redirection to an attacker.
- This is the exact carve-out RFC 8252 §7.3 defines and that all major IdPs implement; it is considered standard and safe for native/CLI flows.
- PKCE continues to protect against authorization code interception by other local processes.

## References

- RFC 8252 — OAuth 2.0 for Native Apps, §7.3 Loopback Interface Redirection
- Current implementation: `pkg/lib/oauth/handler/resolve.go:60-122`

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.