2.2 KiB
id, title, status, date, supersedes, tags
| id | title | status | date | supersedes | tags | |||||
|---|---|---|---|---|---|---|---|---|---|---|
| ADR-0004 | Route every tenant API through a same-origin gateway | active | 2026-08-20 |
|
ADR-0004: Route every tenant API through a same-origin gateway
Context
The production bundle embedded api.dexarmarket.ru and constructed unknown tenant
URLs as https://{tenant}.api.dexarmarket.ru:445. Bootstrap used a different
route (/bootstrap on the storefront origin), while auth had its own fixed API
base. A custom domain could therefore use three different backend origins.
Directly calling the backend port is not a safe fallback: production returned
403 for both a bootstrap request carrying Origin: https://gorbushka.market
and the corresponding CORS preflight. Meanwhile an unmatched /bootstrap on
the storefront nginx server fell through to index.html, producing a misleading
HTTP 200 with HTML instead of bootstrap JSON.
Decision
Every tenant frontend uses one API base derived at runtime from the browser
origin: {origin}/backend.
- Bootstrap loads from
{origin}/backend/bootstrap. - Auth receives the same base through the
AUTH_API_URLprovider. - Legacy endpoints append their existing paths to the same base.
- Versioned
/api/...endpoints retain the/apiprefix when routed. - nginx owns
/backend/, removes that prefix when proxying, and forwards the originalHost,X-Forwarded-For, andX-Forwarded-Protoheaders upstream.
The frontend contains no tenant/domain allowlist and no production backend hostname. Tenant selection remains a server-side responsibility based on the verified forwarded host.
Consequences
Browser traffic is same-origin, so custom domains do not require per-tenant CORS configuration and one bundle works for every attached domain. Bootstrap, auth, legacy routes, and versioned routes cannot silently drift to different hosts.
Every nginx tenant/catch-all configuration must include the /backend/ gateway.
Deploy verification must check that /backend/bootstrap returns JSON rather
than accepting a generic HTTP 200 from the SPA fallback. The upstream must still
reject unknown hosts; the gateway preserves Host but does not authenticate a
tenant by itself.