Skip to main content

D15 — Client (tenant-user) auth: Sanctum SPA + tenant-scoped ClientUser (RESOLVED)

Architecture decision record, relocated verbatim from the retired single-file docs/tenancy/ decision log. Status, thematic clusters, and how to record a new ADR: the decision log index.

The TanStack client app authenticates via Sanctum SPA (cookie/session):

  • App\Models\ClientUser — a tenant-scoped table (client_users, tenant_id FK → auto RLS), separate from the central operator App\Models\User. Login and session→user resolution run under the tenant's RLS scope, so a user can only authenticate against, and act within, their own tenant. Email is unique per tenant.
  • client session guard (config/auth.php) + sanctum.guard = ['client']. The tenant API is protected with auth:sanctum + the tenant.member guard.
  • Middleware order (routes/tenant.php): EnsureFrontendRequestsAreStateful → throttle → bindings → InitializeTenancyByRequestData → (auth:sanctumtenant.member).
  • tenancy.cache.scope_sessions = false — one central SPA session per browser; isolation via RLS + the membership guard. This also avoids the scoped-session vs Sanctum stateful/CSRF ordering trap, so the default central /sanctum/csrf-cookie route works unmodified.
  • personal_access_tokens stays central (SPA uses sessions, not tokens). If client API tokens are introduced later, add tenant_id to that table for RLS.
  • SPA flow: GET /sanctum/csrf-cookiePOST /api/login (+X-Tenant) → cookie-authed /api/*. CORS (config/cors.php, supports_credentials) + SANCTUM_STATEFUL_DOMAINS + SESSION_DOMAIN are env-driven (set to the SPA origin per environment).

← Decision log index