ELPOD / DOCS SECURITY

security / reference

Multi-Tenant Boundaries in Elpod

Resolve tenant identity at the request boundary and enforce tenant authorization in application services and data access.

Multi-tenant Elpod services must resolve tenant identity before domain work and apply it to data access. Use native Elysia request context and explicit policies to avoid treating a URL prefix as a tenant boundary.

The idea

In a multi-tenant system, “the current customer” is part of almost every decision. A tenant ID floating around in an untyped header is easy to forget; a tenant context makes the decision visible, but it still must be checked against identity and data access.

How Elpod provides it

tenancy() resolves an explicit tenant from a request and exposes a typed tenant value to native Elysia handlers. It does not automatically isolate database rows or authorize cross-tenant access.

import { tenancy, type TenantElysia } from "@elpod/core";

const tenantPlugin = tenancy((request) => {
  const id = request.headers.get("x-tenant-id");
  return id ? { id } : null;
}, { required: true });

const server = await bootstrap(app, {
  configure: (elysia) => tenantPlugin(elysia),
});

class ReportsController {
  routes(app: TenantElysia) {
    return app.get("/", ({ tenant }) => ({ tenantId: tenant.id }));
  }
}

Use tenantKey(tenant.id, key) for namespaced cache/lock keys and tenantCache(cache, tenant) for a tenant-scoped cache view.

Common mistakes

  • Trusting a client tenant header without binding it to the authenticated principal.
  • Forgetting tenant predicates in repositories, jobs, and event consumers.
  • Using raw IDs as shared cache or lock keys.
  • Assuming typed context enforces authorization.

Production notes

Resolve tenants from a verified identity or trusted routing layer, enforce membership and resource access, isolate database queries, and carry tenant IDs explicitly into async work. Test that cross-tenant reads and writes fail.

Authorization policies · Authentication · Security overview.