2.7 KiB
2.7 KiB
Tenant Resolution
1. Goal
Resolve tenant from request domain and enforce strict tenant isolation for all config and business endpoints.
2. Middleware Flow
- Parse request host (
Host/X-Forwarded-Hostas trusted by ingress policy). - Normalize host (lowercase, strip port, normalize punycode if needed).
- Resolve tenant record from host mapping store.
- Validate tenant status (
active, not suspended/expired). - Attach tenant context to request.
- Continue to route handlers with tenant-scoped services.
3. Request Context Attachment
Attach immutable context object, e.g.:
req.ctx.tenantIdreq.ctx.tenantSlugreq.ctx.hostreq.ctx.defaultLocalereq.ctx.allowedLocales
All downstream services must read tenant from context, not from query params.
4. Pseudocode Middleware Example
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.