Skip to content

Starlette for the HTTP Adapter (not FastAPI)

Context and Problem Statement

The project needs a driving HTTP adapter — the component that receives HTTP requests, extracts credentials, runs authentication and authorization through the existing domain services, and proxies requests to an upstream service. This is the "Phase 3" that makes the proxy usable.

Which ASGI framework should implement this adapter? The two candidates are Starlette (lightweight ASGI toolkit) and FastAPI (built on Starlette, adds dependency injection, validation, and OpenAPI).

Decision Drivers

  • The proxy is a transparent reverse proxy — it intercepts all HTTP methods on all paths and forwards to upstream
  • No request body validation or response model serialization is needed
  • The proxy does not expose its own API — it proxies upstream's API
  • Streaming (request and response bodies) is important for large payloads
  • Authentication and authorization are handled by existing domain services via Protocols (Authenticator, Authorizer)
  • Dependency injection already uses constructor injection in an application factory (create_app()), following the project's established pattern
  • The project prioritizes minimal dependencies and avoids unnecessary abstractions

Considered Options

  • Starlette — lightweight ASGI toolkit with routing, requests, responses, middleware
  • FastAPI — full web framework built on Starlette with dependency injection, OpenAPI, validation

Analysis: stac-auth-proxy as FastAPI reference

stac-auth-proxy is a production FastAPI-based auth proxy for STAC APIs. Analyzing its architecture reveals how FastAPI is used in a proxy context:

Aspect stac-auth-proxy approach Relevance to hdc-auth-proxy
OpenAPI Disabled (openapi_url=None), proxies upstream's schema instead We don't need OpenAPI — no API to document
FastAPI Depends() Not used for auth — auth is raw ASGI middleware We use domain services via constructor injection
Response streaming Disabled — full response buffered for body transformation (link rewriting, CQL2 filtering) We need streaming — no body transformation
Middleware Raw ASGI __call__ (not BaseHTTPMiddleware) Same pattern needed regardless of framework
Swagger UI Separate route proxying upstream's OpenAPI Not needed
Health endpoints include_router() for /healthz Can be simple Starlette routes

Key insight: stac-auth-proxy uses FastAPI but doesn't use its main features. It disables OpenAPI, skips Depends(), and implements middleware at the raw ASGI level. FastAPI's primary value in that project is the TestClient and the ability to compose with external FastAPI apps via configure_app(). The response buffering (no streaming) is required by their middleware stack that transforms JSON bodies — a requirement we don't have.

Analysis: fastapi-opa PR #81 as PKCE reference

fastapi-opa PR #81 adds PKCE (RFC 7636) and cookie-based sessions to a FastAPI/OPA middleware library. Relevant patterns for Phase 4:

Aspect fastapi-opa approach Relevance to hdc-auth-proxy
Auth interface ABC (AuthInterface) with inheritance We use Protocols — no forced inheritance
PKCE store PKCEStoreProtocol with atomic retrieve-and-delete Good pattern — will adopt as async output port
Cookie session CookieAuthMiddleware wraps OPAMiddleware (tight coupling) Independent composable middleware layers
Domain purity authenticate() reads request.query_params, builds RedirectResponse Strict separation: HTTP adapter → domain service → HTTP adapter
HTTP client Synchronous requests.get/post in async context httpx.AsyncClient for all HTTP I/O
Config OIDCConfig with 18+ fields (God object) Focused frozen dataclasses: OIDCProviderConfig, PKCEConfig, ClientConfig
Result branching isinstance(result, dict) for backward compat Single typed AuthenticationResult return

Key insight: The PKCE flow and PKCEStoreProtocol patterns are sound and will be adopted for Phase 4 (OIDC browser login), but with async interfaces, hexagonal separation (domain service doesn't import Starlette), and focused configuration objects.

Decision Outcome

Chosen option: Starlette, because the proxy use case does not benefit from FastAPI's features, while Starlette provides everything needed with less overhead.

Rationale

What the proxy needs: - Catch-all route matching all HTTP methods and paths → Route("/{path:path}", handler) - Request/response objects → starlette.requests.Request, starlette.responses.StreamingResponse - Lifespan management (httpx client setup/teardown) → @asynccontextmanager lifespan - Simple header parsing for credential extraction → pure functions on Request

What FastAPI adds that we don't need: - OpenAPI auto-generation → no API routes to document (the proxy is transparent) - Pydantic request/response validation → no body to validate (forwarded as-is) - Dependency injection via Depends() → constructor injection in create_app() is sufficient and already the project pattern - Typed path/query parameters → single catch-all route with no parameters

Streaming advantage: Starlette with StreamingResponse + httpx.AsyncClient enables true bidirectional streaming. Responses flow directly from upstream to client without buffering. stac-auth-proxy sacrifices this for response body transformation — a tradeoff we don't need to make.

Implementation

create_app(ProxySettings) -> Starlette
    └── Route("/{path:path}", ProxyHandler.handle)
            ├── extract_credential(request) → Credential | None
            ├── authenticator.authenticate(credential) → AuthenticationResult
            ├── authorizer.authorize(access_request) → AccessResult
            └── upstream_client.forward(request) → StreamingResponse

The ProxyHandler depends on Authenticator and Authorizer Protocols (input ports), not on specific implementations. All wiring happens in the create_app() factory.

Consequences

  • Good, because no unnecessary framework overhead for a transparent proxy
  • Good, because true bidirectional streaming is possible (no response buffering)
  • Good, because Starlette is already an indirect dependency (via httpx)
  • Good, because the architecture stays clean — the HTTP adapter is a thin translation layer
  • Neutral, because Starlette's test utilities (httpx.ASGITransport) are equivalent to FastAPI's TestClient
  • Bad, because adding future admin endpoints (health, metrics) requires either Starlette routes or mounting a FastAPI sub-app

When to reconsider

This decision should be revisited if: - The proxy needs to expose its own API endpoints with typed request/response models - Response body transformation is required (link rewriting, content filtering) - The proxy evolves into an API gateway with per-route configuration and OpenAPI documentation - Integration as a library into external FastAPI apps becomes a requirement

In these cases, migrating to FastAPI would be additive (wrap the existing Starlette app) rather than a rewrite.

Confirmation

  • The HTTP adapter uses starlette.applications.Starlette as the ASGI app
  • No FastAPI import exists anywhere in the codebase
  • ProxyHandler depends only on Authenticator and Authorizer Protocols
  • Response streaming works end-to-end (upstream → client without buffering)

More Information