Skip to main content

Tenant Scoping

How tenant isolation works across all platform services.

Overview

All platform data is tenant-scoped. Users see only data for tenants they belong to. Platform superadmins can view all tenants.

How Scoping Works

Frontend

  1. User signs in via Access Control IDP.
  2. Frontend resolves tenant from:
    • User's assigned tenants (via tenantIds in JWT)
    • Selected tenant in UI (stored in localStorage per device)
  3. All API requests include tenantId query param.
  4. If no tenant selected, frontend blocks data loading until resolved.

Backend

  1. Backend extracts tenantId from:
    • Query param (?tenantId=...)
    • User's JWT (user.tenantId or user.tenantIds[0])
  2. assertTenantScope() enforces:
    • Non-superadmin without tenant scope → 403 Forbidden
    • Valid tenant scope → filters all queries by tenant
  3. Single-resource lookups (e.g., GET /users/:userId) verify the resource belongs to the scoped tenant → 404 if mismatch.

Superadmin "All Tenants" Mode

  • Users with platform.superadmin role can bypass tenant scoping.
  • UI shows "All Tenants" option in tenant selector.
  • Backend allows unscoped queries for superadmins only.
  • Auto-resets to scoped mode if user loses superadmin role.

Required Headers/Params

Endpoint TypeRequired ParamNotes
List/searchtenantId query paramRequired unless superadmin
Single resourcetenantId query paramRecommended; enforced if provided
MutationstenantId in body or queryRequired

Troubleshooting

SymptomCauseFix
403 "Tenant scope required"No tenantId and not superadminPass tenantId or assign user to tenant
404 on valid resource IDResource belongs to different tenantCheck tenant assignment or use correct tenant
Empty resultsWrong tenant selectedSwitch tenant in UI header
"All Tenants" not visibleUser lacks platform.superadminAssign role if authorized

Integration with Platform Services

Services that consume Access Control tenant scoping:

ServiceTenant SourceNotes
VoiceProJWT tenantIds + UI selectorMapped via TenantRegistry.voiceProTenantId
TeeTimeJWT tenantIdDirect tenant ID from token
CRMSlug-based (migrating to UUID)See CRM tenant migration docs
FacilitiesJWT tenantIdDirect tenant ID from token
SCLJWT tenantIdDirect tenant ID from token

Code References

  • Tenant scope enforcement: apps/access-control/access-control-backend/src/app/controllers/users-query.controller.ts
  • assertTenantScope() helper: Reusable pattern for all query controllers
  • Superadmin check: isPlatformSuperadmin() in controllers
  • TenantRegistry model: libs/prisma/access-control-client/prisma/schema.prisma