# Tenant Resolution ## 1. Goal Resolve tenant from request domain and enforce strict tenant isolation for all config and business endpoints. ## 2. Middleware Flow 1. Parse request host (`Host`/`X-Forwarded-Host` as trusted by ingress policy). 2. Normalize host (lowercase, strip port, normalize punycode if needed). 3. Resolve tenant record from host mapping store. 4. Validate tenant status (`active`, not suspended/expired). 5. Attach tenant context to request. 6. Continue to route handlers with tenant-scoped services. ## 3. Request Context Attachment Attach immutable context object, e.g.: - `req.ctx.tenantId` - `req.ctx.tenantSlug` - `req.ctx.host` - `req.ctx.defaultLocale` - `req.ctx.allowedLocales` All downstream services must read tenant from context, not from query params. ## 4. Pseudocode Middleware Example ```ts async function tenantResolver(req, res, next) { const host = normalizeHost(req.headers['x-forwarded-host'] || req.headers.host); if (!host) return res.status(400).json({ error: 'INVALID_HOST' }); const cacheKey = `tenant:host:${host}`; let tenant = await cache.get(cacheKey); if (!tenant) { tenant = await tenantRepository.findByHost(host); if (tenant) await cache.set(cacheKey, tenant, { ttlSeconds: 300 }); } if (!tenant) return res.status(404).json({ error: 'TENANT_NOT_FOUND' }); if (!tenant.active) return res.status(403).json({ error: 'TENANT_INACTIVE' }); req.ctx = { ...(req.ctx || {}), tenantId: tenant.id, tenantSlug: tenant.slug, host, defaultLocale: tenant.defaultLocale, allowedLocales: tenant.supportedLocales }; return next(); } ``` ## 5. Caching Strategy Recommended: - L1 in-memory cache per API instance for hot host lookups. - L2 distributed cache (Redis) for cross-instance consistency. - Cache key by host. - Short TTL (60-300s) + explicit invalidation on tenant changes. Do not cache authorization decisions globally; only cache tenant mapping metadata. ## 6. Security Rules - Never trust tenant from client payload. - Always derive tenant from validated host/context. - Enforce tenant filter at repository layer for every query. - Reject cross-tenant IDs even if resource exists globally. - Emit audit logs for tenant mismatch attempts. ## 7. Edge Cases ### Unknown Domain - Behavior: return `404 TENANT_NOT_FOUND`. - Optional: redirect only if explicit global fallback policy exists. ### Inactive Tenant - Behavior: return `403 TENANT_INACTIVE`. - Optional: include support contact metadata in response. ### Host Header Poisoning - Use trusted proxy chain rules. - Ignore untrusted forwarded host headers. ### Local Development Domains - Keep explicit local host mapping table. - No projectName shortcuts for tenant selection.