floodgate@localhost:/docs/multi-tenancy
File: floodgate.info,Node: Multi-tenancy,Next: Presence,Prev: Configuration,Up: docs

Multi-tenancy

Floodgate supports any number of tenants, each with its own pair of rotating JWT secrets — dynamic tenant management, not just the one tenant/secret pair the environment configures. Tenants ride the same storage backend selection as documents (FLOODGATE_STORAGE_BACKEND): shelf persists them in DETS alongside document data, memory keeps them in the same ephemeral store used by tests.

startup tenant compatibility

FLOODGATE_TENANT_ID + FLOODGATE_JWT_SECRET still work exactly as before: at boot, that tenant is created if it does not already exist, with FLOODGATE_JWT_SECRET as its first secret slot. If the tenant already exists — a restart against persistent shelf storage, or one already created via the admin API below — the boot step leaves it untouched; it will not roll back a secret the admin API has since rotated. To rotate the startup tenant's own secret deliberately, use the admin API rather than editing the env var and restarting.

two secret slots and rotation

Every tenant has two secret slots. JWT verification tries both; token minting always uses slot 1. Regenerating one slot leaves the other valid, so a client can be migrated to a freshly rotated secret without an outage.

admin UI and authentication

Floodgate serves the same Gleam/Lustre admin SPA as Levee at /admin; it does not use Phoenix, Elixir, or Mix. The container builds both Floodgate's Erlang shipment and the SPA's JavaScript output with the Gleam compiler.

Create a GitHub OAuth App with the callback https://your-floodgate.example/auth/github/callback, then set FLOODGATE_GITHUB_CLIENT_ID, FLOODGATE_GITHUB_CLIENT_SECRET, and FLOODGATE_ADMIN_GITHUB_USERS. The allow-list is required: an unset or empty list denies all new OAuth users, avoiding a first-login-wins bootstrap race. Users and opaque sessions persist in the selected storage backend. Sessions use an HttpOnly, SameSite=Lax cookie, with Secure enabled for HTTPS. OAuth state is expiring and single-use.

tenant management API

Accepts either a valid Floodgate admin session or FLOODGATE_ADMIN_KEY (Authorization, compared in constant time). The key remains available for automation and headless deployments.

  • GET /api/tenants — list tenants, no secrets
  • POST /api/tenants — create, body {"name"}
  • GET /api/tenants/:id — show, with both secret slots
  • DELETE /api/tenants/:id — delete registration/secrets only, not the tenant's documents
  • POST /api/tenants/:id/secrets/:slot — regenerate slot 1 or 2

These shapes match what server/levee_admin's Lustre admin UI already expects, so the same frontend that manages Levee's tenants can call Floodgate's API without any decoder changes.