Concepts
Dedicated database tier
Authio runs every customer on a shared multi-tenant Postgres by default. Enterprise tenants can opt in to a fully isolated database — same code path, a different pool, with the per-store routing enforced at runtime.
Why a dedicated tier exists
Enterprise buyers regularly ask for “our data in our own database, not co-mingled with the rest of your customers.” WorkOS does not publish an equivalent tier. Authio ships one as part of the Enterprise plan: provisioning is operator-driven, the cutover is online, and the JWT shape your application reads doesn’t change.
Two tiers, one code path
- Shared (default) — tenants live on a multi-tenant Postgres cluster behind the standard Authio pgbouncer. Row-level security pins every query to
authio.tenant_id/authio.project_id. - Dedicated (Enterprise) — an isolated Postgres provisioned by Authio for the tenant. Same migration set, same RLS posture, same audit-log retention. Routed via a per-tenant pgxpool selected at the call site.
How routing works (T3.6 + T3.6.1, demo-ready)
Every store method now reaches for DB.PoolFromContext(ctx). The HTTP middleware stamps the resolved project_id (and, where available, tenant_id) into the request context using the public store.WithProjectID / store.WithTenantID helpers. The router resolves the ctx to one of:
- The shared pool — when the tenant is on
db_strategy = 'shared'. - The per-tenant pool — when the tenant is on
db_strategy = 'dedicated'. - The shared pool, fail-closed — when the tenant resolves to
kind = 'platform'. Platform-tenant data never lands on a dedicated pool, even if a misconfigured row setsdb_strategy = 'dedicated'alongsidekind = 'platform'; the runtime returnsErrPlatformTenantNoDedicatedPool.
T3.6 (2026-05-21) shipped the router + provisioning binary + operator UI; the call-site refactor (T3.6.1, 2026-05-23) wired every internal/store/*.go method through DB.PoolFromContext. The Dedicated DB tier is now demo-ready: the next paying Enterprise tenant provisioning that flips db_strategy to 'dedicated' exercises the per-tenant pool end-to-end without any further code change.
Cross-tenant call sites that intentionally stay on shared
A small set of pre-resolution lookups must run on the shared pool because the project / tenant identifier is not yet known at query time. They are documented inline in authio_auth-core/internal/store/ and listed in the SHIP_REPORT_T3.6.1-2026-05-23.md report. The set is:
- Custom-domain → project resolver (
CustomDomainStore.GetActiveByHost). - Magic-link / OAuth state / email-challenge token consumers (the token is the only context).
- Per-IP rate-limit counters that pre-date project resolution.
- Allowed-origin / redirect-URI lookups (the very purpose is to tell us which tenant owns the inbound origin).
- M2M client lookup at
/v1/auth/token. - The
signing_keystable — platform-level by architecture; rows pin to shared even for dedicated tenants.
What customers see
- A “Request dedicated DB” toggle on the Enterprise billing surface in
/billing. Operator-driven cutover today; self-serve provisioning is a follow-up. - No JWT shape change. The same access tokens that worked on shared continue to work on dedicated. Customer SDKs neither notice nor need to be re-versioned.
- A dedicated
operationsrunbook entry (runbooks/dedicated-db-operations.md) covering backup, restore, and the row-pinning matrix for the pre-resolution shared-pool queries listed above.
Verifying invariants
Three runtime-level tests pin the platform-tenant invariant (T6.0) at the routing seam every store method reaches for:
TestRouter_PoolForOrErr_PlatformTenantReturnsSharedWithErrTestRouter_PoolForOrErr_PlatformTenantWithDedicatedStrategyStillRefusesTestDB_PoolFromContext_PlatformTenantEvenWithDedicatedStrategy
Audit-RLS / immutability (T4.5) is preserved end-to-end because every multi-statement transaction opens via PoolFromContext(ctx).Begin — single pool, single connection, the set_config('authio.project_id', ..., true) GUC binds correctly on whichever physical database the INSERT lands on.
Read next
- Security model — RLS, tenant isolation, JWT-kind separation.
- Platform vs customer tenants — the T6.0 JWT-kind isolation that the dedicated-DB router fail-closes on.
- Pricing tiers — Dedicated DB is on the Enterprise plan.
