Redirect-URI matching for the authorization endpoint (RFC 6749 §3.1.2.3, RFC 8252 §7.3).
RFC 6749 §3.1.2.3 matches a request redirect_uri against the client's
registered set by simple string comparison: byte-for-byte, no
normalization and no prefix matching. That is the default here, and it is what
the OpenID Connect and FAPI profiles assume.
RFC 8252 (BCP 212) defines one narrow exception. A native application that cannot use a private-use URI scheme (§7.1) instead binds an ephemeral port on the loopback interface (§7.3) and only learns that port at runtime, so the port cannot be registered ahead of time. §7.3 therefore requires the authorization server to "allow any port to be specified at the time of the request for loopback IP redirect URIs", while comparing the rest of the URI exactly.
Matching modes
:exact(default) - RFC 6749 §3.1.2.3 simple string comparison, and nothing else.:exact_allow_loopback_port- exact comparison first; failing that, the RFC 8252 §7.3 loopback exception. The exception applies only when BOTH the request URI and the registered URI are loopback redirect URIs, meaning each one:begins with the byte-exact scheme
http://(anhttpsURI, a private-use scheme, and an upper-caseHTTP://are all outside the exception and stay exact-match);has an authority of exactly
127.0.0.1or[::1], optionally followed by:<port>and nothing else. Any userinfo, any other host, and any other spelling of the loopback address are outside the exception.localhostis deliberately excluded. RFC 8252 §8.3 makes the literal IP preferable precisely becauselocalhostis a name, resolved by the device's host-name configuration, and so is not guaranteed to be the loopback interface. Extending port flexibility to a name whose resolution the server cannot reason about would widen the exception past what §7.3 asks for, sohttp://localhost:PORT/...never matches under it.RFC 9700 §2.1 and §4.1.3 phrase the same exception as applying to "
localhostredirection URIs of native apps", which reads as a wider allowance. Both, however, define it by reference - "as described in Section 7.3 of [RFC8252]" - and §7.3 constructs the URI from the loopback IP literal, not the name.localhostthere is shorthand for "the loopback interface", so the normative content is §7.3's and this module follows it. A client that has registered alocalhostURI is unaffected in the ordinary case: it still matches exactly, it just gets no port flexibility.This scopes the exception, not registration: a client that has registered a
localhostredirect URI still reaches it by exact match, since that URI is one the host deliberately registered and §8.3's "NOT RECOMMENDED" is guidance to the client about which URI to choose, not a requirement that the server refuse it. Such a client simply gets no port flexibility;carries no fragment (a redirect URI must not, RFC 6749 §3.1.2).
The two sides differ in one respect. The request URI names an endpoint the server is about to redirect a browser to, so a port it carries must be decimal
1..65535or absent; an empty (http://127.0.0.1:/cb) or out-of-range port is not a reachable endpoint and falls back to exact comparison. The registered URI is a pattern whose port is discarded, so any port stands there - including the conventional:0placeholder for "an ephemeral port chosen at runtime".Two loopback URIs match when their scheme, host literal, path, and query are all identical; only the port is ignored. IPv4 and IPv6 loopback are distinct hosts and never match each other.
Enabling the exception is a deliberate deployment decision: a profile that mandates exact redirect-URI matching forbids it. It is off by default so the matching behavior is unchanged unless a host asks for it.
Registration convention
A client registers its loopback redirect URI with whatever port it likes
(http://127.0.0.1/cb, http://127.0.0.1:0/cb, or a fixed port) and any
usable request port then matches. Only the port is variable; a request that
differs in path or query is still rejected.
Failure is never a redirect
This module answers a boolean. A request URI that matches nothing is not a trusted redirect target, and the caller MUST report the failure directly to the user agent rather than redirecting to the supplied URI (OIDC Core §3.1.2.6) - otherwise the endpoint is an open redirect.
Summary
Types
The redirect-URI matching mode (see the moduledoc).
Functions
Normalize a caller-supplied matching mode, raising ArgumentError on an
unrecognized value.
The supported matching modes. Exposed so a caller can validate host configuration against the same list this module enforces.
Whether uri matches one of the client's registered redirect URIs under
matching (RFC 6749 §3.1.2.3, RFC 8252 §7.3).
Types
Functions
Normalize a caller-supplied matching mode, raising ArgumentError on an
unrecognized value.
A misspelled mode must never silently degrade into a different matching
policy, in either direction: quietly falling back to :exact would hide a
host's deliberate opt-in, and quietly enabling anything else would relax
matching nobody asked for. Raising makes the misconfiguration visible.
@spec matching_modes() :: [matching()]
The supported matching modes. Exposed so a caller can validate host configuration against the same list this module enforces.
Whether uri matches one of the client's registered redirect URIs under
matching (RFC 6749 §3.1.2.3, RFC 8252 §7.3).
A non-binary entry in registered is ignored rather than raising: the
registered set comes from the host, and one malformed entry must not make an
otherwise valid request crash the endpoint.