Tenant context propagation
Establishes verified workspace context once and carries it explicitly through application boundaries without unsafe default fallbacks.
tenant-context-propagation
Team members using this skill
When is it useful?
Use it when tenant identity passes between HTTP handlers, jobs, realtime channels or command-line operations.
What does it help you do?
- Separates authentication, membership and resource authorization.
- Rejects missing or unverified workspace context without default fallback.
- Carries immutable context through requests, jobs and realtime connections.
Try asking this
Trace workspace identity from HTTP request through a queued export worker. Queue an export from workspace A while the user switches to B. The worker must retain verified A context or fail explicitly, never inherit the new selection.
Detailed documentation
Read the skill’s original instructions, examples and checks in their authored language.
Show less
Tenant Context Propagation
When to Use
Use when designing middleware, request context stores, repository layers, background job dispatchers, WebSocket handlers, or CLI commands in a multi-tenant application.
Core Principle: Separate Identity, Membership, and Permission
Every tenant-scoped operation must verify three distinct questions in order:
- Authentication (Who is the actor?): Validated
userId, service principal, or signed webhook identity. - Workspace Membership (Does this actor belong to
tenantId?): Verified active membership inworkspace_membershipsfor the requestedtenantId(and verified workspace state isactive). - Action & Resource Authorization (Can they perform this action on this resource?): Role/permission check plus verification that the target resource's
tenant_idequals the activetenantId.
Fail-Closed Context Resolution
1. Never Trust Unverified Client Tenant Hints or Fallback Defaults
- A subdomain, URL parameter (
/workspaces/:workspaceId), header (X-Workspace-Id), or body field is only a candidate hint until the server verifies active membership for the authenticated actor. - Fail-Closed Rule: If
tenantIdis missing, malformed, or unverified when a tenant-scoped repository, cache, or storage method is called, throw an explicit authorization/context error immediately. - Never fall back to a "default workspace," "first workspace of user," or omit the
WHERE tenant_id = ?filter whentenantIdisnullorundefined! (Watch out for ORMs where{ where: { tenantId: undefined } }silently strips theWHEREclause and returns all tenants' rows!)
2. Scoped Execution Context (AsyncLocalStorage / contextvars)
- Never store per-request
currentTenantIdon a singleton service instance, module-level global variable, or shared DB client property—concurrent requests will overwrite each other's tenant context! - Pass an explicit, immutable
TenantContext({ tenantId, actorId, actorType, membershipRole, requestId }) or bind it strictly to the async execution tree (AsyncLocalStoragein Node.js,contextvarsin Python,context.Contextin Go, request-scoped container in PHP/Laravel).
3. Distinguish Tenant-Scoped Calls from Explicit System Scopes
Legitimate cross-tenant operations (e.g., billing reconciliation cron, system migrations, global health metrics) must not use a backdoor tenantId = null:
- Require an explicit
SystemExecutionContext(scope: 'system',reason,actor) via a dedicated entry point so accidental omission of tenant context in user code still fails closed.
4. Rehydrate & Re-Verify Context in Async & Long-Lived Workers
When work crosses an execution boundary (BullMQ/SQS/RabbitMQ job, scheduled task, event listener, WebSocket message):
- At Enqueue Time: Serialize
{ tenantId, actorId, actorType, enqueuedAt }into the job envelope. - At Worker Execution Time:
- Initialize a fresh, isolated context scope for that single job and clear it in
finally. - Re-verify that the workspace is still
active(and, for user-delegated delayed actions, that the user's membership and permission have not been revoked since enqueue time) before executing mutations.
- Initialize a fresh, isolated context scope for that single job and clear it in
Included files 1
SKILL.md
