# How CoSec Supports Multi-Tenancy: Architecture and Implementation Guide

> Discover how CoSec supports multi-tenancy through tenant identifiers policy filtering and context propagation Explore the architecture and implementation with this comprehensive guide

- Repository: [Ahoo Wang/cosec](https://github.com/ahoo-wang/cosec)
- Tags: architecture
- Published: 2026-02-23

---

**CoSec implements multi-tenancy by embedding tenant identifiers into security principals, filtering policies by tenant ID, and propagating tenant context through tokens and the security context pipeline.**

CoSec (Comprehensive Security) is an open-source security framework designed to handle complex authorization scenarios in SaaS environments. Understanding how CoSec supports multi-tenancy is essential for architects building applications that require strict data isolation between tenants while sharing a single deployment.

## Core Tenant Abstraction

### The Tenant Model

At the heart of CoSec’s multi-tenancy architecture lies the `Tenant` interface defined in [`cosec-api/src/main/kotlin/me/ahoo/cosec/api/tenant/Tenant.kt`](https://github.com/ahoo-wang/cosec/blob/main/cosec-api/src/main/kotlin/me/ahoo/cosec/api/tenant/Tenant.kt). This abstraction provides a unique `tenantId` and helper predicates such as `isPlatformTenant()`, `isDefaultTenant()`, and `isUserTenant()` to distinguish between system-level and customer-level tenants.

The concrete implementation resides in [`cosec-core/src/main/kotlin/me/ahoo/cosec/tenant/SimpleTenant.kt`](https://github.com/ahoo-wang/cosec/blob/main/cosec-core/src/main/kotlin/me/ahoo/cosec/tenant/SimpleTenant.kt), which acts as a simple value object holding the tenant identifier. This design allows the framework to treat tenant identification as a first-class citizen throughout the security lifecycle.

### Principal-Tenant Coupling

CoSec embeds tenant information directly into authentication principals via the `TenantCapable` interface located in [`cosec-api/src/main/kotlin/me/ahoo/cosec/api/tenant/TenantCapable.kt`](https://github.com/ahoo-wang/cosec/blob/main/cosec-api/src/main/kotlin/me/ahoo/cosec/api/tenant/TenantCapable.kt). By mixing this capability into users, services, and other security entities, the framework ensures that every authenticated subject carries its tenant affiliation. This coupling guarantees that tenant context is never lost as requests traverse the security pipeline.

## Tenant Context Propagation

### Security Context Integration

The `SecurityContext` interface in [`cosec-api/src/main/kotlin/me/ahoo/cosec/api/context/SecurityContext.kt`](https://github.com/ahoo-wang/cosec/blob/main/cosec-api/src/main/kotlin/me/ahoo/cosec/api/context/SecurityContext.kt) serves as the central holder for both the current principal and its associated tenant. When a request enters the system, the security context captures the tenant via `securityContext.tenant`, making it readily available to all downstream authorization checks, policy evaluators, and audit loggers.

### Token-Based Tenant Embedding

CoSec propagates tenant identity through JWT tokens using [`cosec-jwt/src/main/kotlin/me/ahoo/cosec/jwt/JwtTokenConverter.kt`](https://github.com/ahoo-wang/cosec/blob/main/cosec-jwt/src/main/kotlin/me/ahoo/cosec/jwt/JwtTokenConverter.kt). During token generation, the framework embeds the tenant identifier as a claim using the key `Tenant.TENANT_ID_KEY`. When parsing tokens, the converter creates a `SimpleTokenTenantPrincipal` (defined in [`cosec-core/src/main/kotlin/me/ahoo/cosec/token/SimpleTokenTenantPrincipal.kt`](https://github.com/ahoo-wang/cosec/blob/main/cosec-core/src/main/kotlin/me/ahoo/cosec/token/SimpleTokenTenantPrincipal.kt)) that implements `TenantCapable`, ensuring the tenant survives stateless authentication.

## Policy Isolation and Evaluation

### Tenant-Scoped Policies

Every policy record in CoSec stores a `tenantId` field within [`cosec-core/src/main/kotlin/me/ahoo/cosec/policy/PolicyData.kt`](https://github.com/ahoo-wang/cosec/blob/main/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/PolicyData.kt). This storage strategy ensures that authorization rules are physically or logically partitioned by tenant at the data layer.

### Policy Evaluation Flow

During authorization, the policy evaluator first filters the policy repository by matching `policy.tenantId` against `securityContext.tenant.tenantId`. Only policies belonging to the requesting tenant are considered for rule evaluation, guaranteeing strict isolation between tenant authorization domains.

## Advanced Multi-Tenancy Features

### Dynamic Tenant Switching

For administrative scenarios requiring impersonation or tenant hopping, CoSec provides [`cosec-core/src/main/kotlin/me/ahoo/cosec/authentication/token/AbstractSwitchTenantAuthentication.kt`](https://github.com/ahoo-wang/cosec/blob/main/cosec-core/src/main/kotlin/me/ahoo/cosec/authentication/token/AbstractSwitchTenantAuthentication.kt). This abstraction allows an already-authenticated principal to change its tenant context on-the-fly without requiring re-authentication, enabling support desks and platform administrators to operate across tenant boundaries securely.

### Serialization and Observability

Tenant persistence is handled by [`cosec-core/src/main/kotlin/me/ahoo/cosec/serialization/JsonPolicySerializer.kt`](https://github.com/ahoo-wang/cosec/blob/main/cosec-core/src/main/kotlin/me/ahoo/cosec/serialization/JsonPolicySerializer.kt), which automatically writes and reads the `tenantId` field during JSON serialization. For operational visibility, [`cosec-opentelemetry/src/main/kotlin/me/ahoo/cosec/opentelemetry/CoSecInstrumenter.kt`](https://github.com/ahoo-wang/cosec/blob/main/cosec-opentelemetry/src/main/kotlin/me/ahoo/cosec/opentelemetry/CoSecInstrumenter.kt) attaches the tenant identifier as an attribute to OpenTelemetry spans and metrics, enabling per-tenant monitoring and audit trails.

## Implementation Example

The following Kotlin example demonstrates the end-to-end tenant flow in CoSec:

```kotlin
// 1️⃣ Parse token → embed tenant
val jwt = Jwts.parserBuilder().build().parseClaimsJws(tokenString)
val tenantId = jwt.body.get(Tenant.TENANT_ID_KEY, String::class.java)
val tenant = SimpleTenant(tenantId)
val principal = SimpleTokenTenantPrincipal(tokenPrincipal, tenant)

// 2️⃣ Build security context
val securityContext = SimpleSecurityContext(principal)

// 3️⃣ Policy evaluation (tenant‑aware)
val applicablePolicies = policyRepository.findByTenantId(securityContext.tenant.tenantId)
val decision = policyEvaluator.evaluate(applicablePolicies, securityContext, request)

```

This pattern ensures that tenant context flows from the initial token parsing through to final authorization decisions.

## Summary

- **First-class Tenant abstraction**: The `Tenant` interface in [`Tenant.kt`](https://github.com/ahoo-wang/cosec/blob/main/Tenant.kt) provides clear semantics for platform, default, and user tenants.
- **Embedded tenant context**: `TenantCapable` mixes tenant information into every principal, ensuring the tenant identifier travels with the authentication subject.
- **Policy-level isolation**: [`PolicyData.kt`](https://github.com/ahoo-wang/cosec/blob/main/PolicyData.kt) stores `tenantId` alongside rules, and the evaluator filters policies by tenant before execution.
- **Cross-layer propagation**: Tenant identifiers flow through JWT claims, security contexts, JSON serialization, and OpenTelemetry instrumentation for complete observability.

## Frequently Asked Questions

### How is the tenant ID stored in JWT tokens?

CoSec stores the tenant identifier as a JWT claim using the constant `Tenant.TENANT_ID_KEY`. The `JwtTokenConverter` class extracts this claim during token parsing and constructs a `SimpleTokenTenantPrincipal` that carries the tenant forward into the security context.

### Can a user switch tenants after authentication?

Yes. The `AbstractSwitchTenantAuthentication` class supports dynamic tenant switching for scenarios such as administrative impersonation or support desk operations. This allows authenticated principals to change their effective tenant context without re-authenticating.

### How does CoSec ensure policy isolation between tenants?

CoSec ensures isolation by storing a `tenantId` field in every `PolicyData` record. During policy evaluation, the framework filters the policy repository to match only policies where `policy.tenantId` equals the current `securityContext.tenant.tenantId`, preventing cross-tenant authorization leakage.

### Is tenant information available in logs and metrics?

Yes. The `CoSecInstrumenter` class automatically attaches the tenant identifier to OpenTelemetry spans and metrics as an attribute. This enables per-tenant observability in distributed tracing systems and monitoring dashboards.