When a web connection fails, widening the destination allowlist can look like the quickest repair. I wanted a smaller, inspectable exercise: identify exactly which values must agree before proceeding to the rest of the flow. This independently written package is a comparison exercise using synthetic values. It connects no account and issues no authorization code. Treating it as a miniature authorization server would give it responsibilities it does not implement.

Establish where trusted configuration comes from

WebReturnPolicy accepts a configured client label, a resource label and a collection of return addresses. Building that collection from the address in the current request would make the comparison circular: the request would approve itself. The package therefore provides no operation that expands the policy in response to a failed request. Load reviewed configuration first, then compare an attempt against it.

The constructor copies the supplied list into a tuple and freezes the dataclass. Appending to the caller's original list cannot silently expand an existing policy. That protects against an ordinary configuration mutation mistake. It is not a security boundary against hostile code in the same Python process. A deployed system still needs separate controls for who can edit and activate configuration.

A shared address does not merge client registrations

The compare method calculates three separate conditions. The client label must match, the resource label must match, and the supplied address must exactly equal a configured value. contract_match becomes true only when all three agree. A different client cannot borrow an otherwise matching return address. Likewise, a request for a different resource fails the combined contract. The labels are synthetic identifiers, not credentials or proof of identity.

Each component result remains visible so a learner can see which condition changed. The returned dictionary does not echo a raw address or a token. Whether a real service should expose detailed mismatch reasons to an external caller is a separate decision. This dictionary is designed for a local explanation and does not prescribe a production error-response format.

Do not repair a request into a matching address

The request path does not strip whitespace, decode escapes or canonicalize a supplied address before comparison. A changed path, an extra trailing slash, an explicitly written default port, or added query and fragment delimiters remain different values. When a request differs from reviewed configuration, I want the operator to inspect that difference rather than have a convenience helper erase it.

The configuration subset is deliberately narrow: HTTPS web addresses without explicit ports, user information, Unicode, queries, fragments, percent escapes or dot path segments. These are restrictions of this example, not a declaration that every excluded form is invalid in every OAuth implementation. The package also omits native applications with dynamic loopback ports. Its small scope makes the comparison easier to inspect; it does not establish a universal redirect registration policy.

Explore the implementation

Complete independent synthetic implementation

"""Pure, deliberately narrow comparison contract; not an OAuth server."""
from dataclasses import dataclass
from urllib.parse import urlsplit


def _plain(value, limit=2048):
    return (isinstance(value, str) and 0 < len(value) <= limit
            and value.isascii() and all(32 < ord(c) < 127 for c in value))


def _configured_web_uri(value):
    if not _plain(value):
        return False
    try:
        p = urlsplit(value)
        return (p.scheme == 'https' and p.hostname is not None
                and p.netloc == p.hostname and p.path.startswith('/')
                and not p.query and not p.fragment
                and '?' not in value and '#' not in value
                and '%' not in value and '\\' not in value
                and all(part not in ('.', '..') for part in p.path.split('/')))
    except ValueError:
        return False


@dataclass(frozen=True)
class WebReturnPolicy:
    """Load only from trusted configuration, never from the current request.

    This example's configuration subset excludes query strings, fragments,
    ports, user information, Unicode and encoded path characters. This is
    narrower than the full set of valid OAuth redirect URI configurations.
    """
    client_id: str
    resource_id: str
    redirects: tuple[str, ...]

    def __post_init__(self):
        if not _plain(self.client_id, 128) or not _plain(self.resource_id, 128):
            raise ValueError('Invalid trusted binding label')
        if not isinstance(self.redirects, (tuple, list)):
            raise ValueError('Expected a configured sequence')
        values = tuple(self.redirects)
        if not 1 <= len(values) <= 4:
            raise ValueError('Expected one to four distinct configured URIs')
        if not all(_configured_web_uri(v) for v in values):
            raise ValueError('Configured URI outside this example subset')
        if len(set(values)) != len(values):
            raise ValueError('Expected distinct configured URIs')
        object.__setattr__(self, 'redirects', values)

    def compare(self, *, client_id, resource_id, redirect_uri):
        # No parsing, stripping, decoding or canonicalizing request values.
        client = isinstance(client_id, str) and client_id == self.client_id
        resource = isinstance(resource_id, str) and resource_id == self.resource_id
        callback = _plain(redirect_uri) and redirect_uri in self.redirects
        return {
            'contract_match': bool(client and resource and callback),
            'client_match': client,
            'resource_match': resource,
            'callback_match': callback,
            'consent_verified': False,
            'authenticated': False,
            'token_issued': False,
            'scope': 'Synthetic configured web-return comparison only',
        }

Test changes to the boundary, not just a happy path

One passing example cannot establish this contract. The tests vary the path on the same host, append another domain to a host name, insert user information, and introduce encoded or dot path segments. Empty and non-string request values must fail as well. Invalid trusted configuration is rejected at construction time rather than becoming a later request-handling surprise.

Other tests mutate the original configuration list, register a second address explicitly, and request the same address under a different client or resource label. There are fourteen unittest methods with variant inputs exercised inside them. The useful result is the set of boundaries checked, rather than a large headline test count. None of these tests contacts an identity provider or exercises the original customer application.

The selector reads saved results

The selector below displays five records produced by the Python implementation: an exact match, a different path on the same host, an explicit default port, a different client, and a different resource. Selecting a record sends no authentication request and reads no visitor file. It changes which saved result is displayed.

The inline JavaScript is checked with DOM stubs for its initial display and all five selections in each language. This establishes that the saved records are wired to the selector. It does not establish rendered layout, mobile behavior, keyboard usability or a successful account connection. I keep that description next to the interaction because a moving display can otherwise look like a live login demonstration.

A read-only viewer of synthetic local records

What remains after a match

consent_verified, authenticated and token_issued remain false even when the comparison succeeds. The package does not implement human consent, session and state binding, PKCE, issuer checking, expiration, scopes, single-use codes, replay prevention or secure token storage. Those responsibilities must remain in a maintained authorization implementation. This package must not replace an existing authorization server.

The ZIP includes the complete implementation, tests, record generator, saved results, usage notes and an MIT license. The same tests are run again after extraction into a separate directory. My intended sequence is to inspect the synthetic boundaries first, then separately design the registration model and remaining responsibilities for a real product. A green comparison result is a small piece of evidence, never a completed connection.

Download code, tests and records
Python standard library, synthetic data, modification and reuse permitted