Files
marketplaces/docs/backend-platform/tenant-resolution.md
sdarbinyan 10251f2fc6
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
docs
2026-07-05 04:23:47 +04:00

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

  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

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.