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_idFK → auto RLS), separate from the central operatorApp\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.clientsession guard (config/auth.php) +sanctum.guard = ['client']. The tenant API is protected withauth:sanctum+ thetenant.memberguard.- Middleware order (
routes/tenant.php):EnsureFrontendRequestsAreStateful→ throttle → bindings →InitializeTenancyByRequestData→ (auth:sanctum→tenant.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-cookieroute works unmodified.personal_access_tokensstays central (SPA uses sessions, not tokens). If client API tokens are introduced later, addtenant_idto that table for RLS.- SPA flow:
GET /sanctum/csrf-cookie→POST /api/login(+X-Tenant) → cookie-authed/api/*. CORS (config/cors.php,supports_credentials) +SANCTUM_STATEFUL_DOMAINS+SESSION_DOMAINare env-driven (set to the SPA origin per environment).