Skip to main content

guest-auth

Not a replacement for real authentication. A static-allowlist invite-token gate for pre-production demos and invite-only previews. Pure-ASGI middleware that plugs into any Starlette / FastAPI app in ~5 lines.

Give a tester a link like https://your-app.example.com/?token=tok_abc123; the middleware validates the token against an allowlist you own, exchanges it for an httpOnly cookie, and attaches an identity (token + human-readable recipient label) to the request via a ContextVar that reaches sync endpoints in the threadpool as well.

The library was extracted from Pitchcraft and is consumed there in production; Rulebook and JobScout are scheduled to adopt it.

Adopting this in a new app? See docs/integration.md for the DI pattern, the app_state.py template, gotchas (init order, pure-ASGI vs BaseHTTPMiddleware, ContextVar propagation), and the constructor reference.


What this is NOT

Naming a library *-auth invites expectations it doesn't meet. To be explicit:

  • No password handling, no MFA, no OAuth / OIDC, no account lifecycle. The credential is an opaque token you generate and hand to a tester.
  • No token rotation, expiry, revocation-list, or signed cookies. The cookie is httpOnly + Secure + SameSite=Lax with a 30-day convenience lifetime; revoking access means removing the token from the allowlist and redeploying.
  • No rate limiting. Compose one separately (e.g. llm-guardrails ships an IP rate limiter).
  • Not audited for adversarial threat models. This is a gate to keep pre-production URLs off the open web and attribute per-tester activity, not a substitute for real identity infrastructure. If you're gating production PII or payment flows, use something else.

The value the library provides — a well-scoped ASGI middleware that publishes a per-request identity ContextVar that reaches sync endpoints — is genuinely useful and hard to get right (the "pure-ASGI vs BaseHTTPMiddleware" trap is subtle). Everything above is deferred, not planned.


Install

pip install guest-auth

Requires Python 3.11+. The only runtime dependency is starlette, which any ASGI host already has.


Quick example

from dataclasses import dataclass, field
from fastapi import FastAPI
from guest_auth import InviteAuthMiddleware, get_current_guest


@dataclass
class Settings:
    demo_mode: bool = True
    invite_tokens: dict = field(
        default_factory=lambda: {"tok_abc123": "Jane Tester"}
    )


settings = Settings()
app = FastAPI()


@app.get("/")
def home():
    guest = get_current_guest()
    return {"welcome": guest.recipient if guest else "anonymous"}


app.add_middleware(
    InviteAuthMiddleware,
    config=settings,
    # Optional — pre-rendered HTML for the 401 / welcome page.
    # Omit to use the built-in "This site is currently invite-only." body.
    welcome_html="<h1>Preview build</h1><p>Ask jane@example.com for a link.</p>",
)

Now:

  • GET /?token=tok_abc123 → 302 to /, sets guest_session cookie.
  • GET / with the cookie → returns {"welcome": "Jane Tester"}.
  • GET / without a cookie → 401 with the welcome page.
  • Flip settings.demo_mode = False → gate becomes a complete pass-through with no restart.

Core concepts

GuestAuthConfig (Protocol)

The middleware takes a config object that satisfies:

class GuestAuthConfig(Protocol):
    demo_mode: bool
    invite_tokens: Mapping[str, str]  # token → recipient label

Both attributes are read at request time, so mutating a live config instance (a pydantic BaseSettings, a dataclass, whatever) takes effect on the next request without rebuilding the middleware.

GuestIdentity + get_current_guest()

On a successful cookie match, the middleware sets a request-scoped ContextVar with GuestIdentity(token=..., recipient=...). Anywhere downstream — sync or async, including code paths in Starlette's threadpool — get_current_guest() returns it or None.

Because the middleware is pure-ASGI (not BaseHTTPMiddleware), the ContextVar survives into the threadpool that runs def (sync) endpoints. See the integration doc for why this matters.

PathScopedContextVarMiddleware (bonus)

An adjacent generic that ships in the same package: match a regex against scope["path"], publish an extracted value on a caller-supplied ContextVar for the duration of the request. Same pure-ASGI rationale as the auth middleware. Use for /api/things/{id}/… style path-scoped ContextVars (session IDs, tenant IDs, whatever).


Development

git clone https://github.com/ecoop/guest-auth
cd guest-auth
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest
ruff check src tests

CI runs on Python 3.11 and 3.12 via GitHub Actions.

Versioning

Currently v0.1.1. Semver from v1.0.0 onward; anything before is "shipped but pre-stable API — expect breaking changes."

Contributing

Issues and pull requests welcome. For substantive changes, open an issue first — this library has a deliberately small surface and staying small is a feature.

License

MIT. See LICENSE.


Last updated: 2026-08-06

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

guest_auth-0.1.1.tar.gz (17.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

guest_auth-0.1.1-py3-none-any.whl (12.9 kB view details)

Uploaded Python 3

File details

Details for the file guest_auth-0.1.1.tar.gz.

File metadata

  • Download URL: guest_auth-0.1.1.tar.gz
  • Upload date:
  • Size: 17.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for guest_auth-0.1.1.tar.gz
Algorithm Hash digest
SHA256 7d5b3f34d55735b617b6b4039fe91cfd119ec612c426b50b2d4ee8ed4b3203f5
MD5 1f069473928596eccffdb0e07bf8bd62
BLAKE2b-256 56ba5dcd15b90e700927a2ace02da8c9843aa29b511831503681f2a490cb2a5f

See more details on using hashes here.

Provenance

The following attestation bundles were made for guest_auth-0.1.1.tar.gz:

Publisher: release.yml on ecoop/guest-auth

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file guest_auth-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: guest_auth-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 12.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for guest_auth-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 7c4517cdbdcf5bf79e36ea1a60044ba88969dd695b82797578ae1675636dddb6
MD5 c5f07dba360272b3bca3e9d09a533f95
BLAKE2b-256 b1ae6624cf5a71374f27fe10dbd4eb5a3d7a0a89a4db273e6b469f7fee535622

See more details on using hashes here.

Provenance

The following attestation bundles were made for guest_auth-0.1.1-py3-none-any.whl:

Publisher: release.yml on ecoop/guest-auth

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.2.1

2 files

0.2.0

2 files

This release

0.1.1 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page