Module 12

Identity Propagation, Multi-Tenancy & Isolation

Building Systems Model Context Protocol (MCP)

Module 12 — Identity Propagation, Multi-Tenancy & Isolation

Phase 3 · Security, Auth & Entitlements (the phase capstone). You can now authenticate (Module 9), separate authZ from entitlements (Module 10), and design the policy model (Module 11). This module is about carrying verified identity and tenant context correctly across every hop — host → gateway → server → upstream — without leakage, and isolating tenants so one can never reach another’s data. This is where most real-world breaches happen and where architect-level rigor shows. It closes Phase 3 and feeds directly into the gateway design of Phase 4.

By the end you can:

  1. Propagate identity across hops without the confused-deputy/passthrough anti-pattern.
  2. Bind and enforce tenant context on every interaction.
  3. Choose isolation strategies across storage, server, and authorization layers.
  4. Design audit logging that satisfies entitlement and compliance requirements.

1. The problem: identity has to survive multiple hops

A real request crosses several systems, each a trust boundary (Module 8):

 User ─▶ Host ─▶ Gateway ─▶ MCP server ─▶ Upstream API (GitHub, DB, SaaS)
        token     PEP/PDP     executes        needs ITS OWN token
        (aud:      validates   tool            (aud: upstream)
        gateway)   token

At each hop you must answer: who is the caller, which tenant, and what token is valid here? Get any hop wrong and you either leak (tenant A sees tenant B) or over-privilege (a server acts with more authority than the user has). The governing principle from Module 8 still holds: the host/gateway is the source of truth for identity; downstream systems must re-establish trust, not assume it.


2. Propagating identity correctly (no passthrough)

The cardinal rule, restated from Module 9 and now applied across the chain: never forward an inbound token to the next hop. Each hop’s token must have that hop as its audience.

  • Host/Gateway → MCP server: the token’s aud is the MCP server; the server validates it (Module 9 step 6).
  • MCP server → upstream API: the server must not replay the inbound token. It performs OAuth 2.0 Token Exchange (RFC 8693): present the inbound token to the AS and receive a new token whose audience is the upstream and whose scope is minimized to just what this call needs. This preserves who the user is (the exchanged token still represents the user) while ensuring the right audience and least privilege.

Why this matters concretely (the confused deputy, Module 8 T2): if a server blindly forwarded tokens, a malicious or buggy server could replay a user’s token against systems it was never meant to touch, or an attacker could trick the “deputy” server into using its authority on their behalf. Token exchange with per-hop audience binding structurally prevents it.

On-behalf-of vs service identity. Sometimes the server acts as the user (use exchanged user token — most cases, preserves entitlements and audit truth). Sometimes it acts as itself for infrastructure (a service token with its own narrow rights). Be explicit about which; never let a service identity silently stand in for a user, or your audit trail lies and entitlements are bypassed.


3. Multi-tenancy: bind tenant context to everything

Tenancy is the dimension that turns “works in a demo” into “safe for many customers.” The rule: every interaction carries tenant context, and every policy decision is evaluated strictly within the tenant boundary.

  • Bind tenant to the identity — the tenant comes from the validated token/session (a claim), not from a request parameter the caller could forge. Trust the token’s tenant, never the argument’s.
  • Tenant on every PDP decision — recall Modules 10–11: every allow rule includes resource.tenant == subject.tenant. A request from tenant Acme must never resolve a Globex resource, even if the tool would technically accept the id.
  • Tenant in every log line — for audit and forensics (§5).
  • Reject cross-tenant references early — if a tool is called with a resource id belonging to another tenant, deny at the PEP before execution; don’t rely on the upstream to catch it.

The classic multi-tenant failure is an IDOR-style bug: get_document(id=999) returns another tenant’s doc because the code looked up the id without checking ownership. In MCP this is acute because the model generates the arguments — it may produce an id from injected content (Module 8 T1). So tenant/ownership checks must live in the PEP/PDP, not be assumed from “the model wouldn’t do that.”


4. Isolation strategies (decide at three layers)

Isolation must be enforced at storage, server, and authorization layers — defense in depth. Pick deliberately per layer:

Authorization layer (always):

  • Per-tenant RBAC/ABAC/ReBAC; tenant predicate in every rule; deny-by-default. This is mandatory regardless of the other layers.

Server layer (logical → physical spectrum):

  • Logical isolation — shared servers, tenant enforced in code/policy. Cheapest, most common; relies on flawless tenant checks.
  • Namespaces / server groups — group servers by business unit or environment; scope discovery and access to a tenant’s group (registry-driven, Module 14).
  • Dedicated instances — a server (or whole stack) per tenant. Strongest isolation, highest cost; used for high-sensitivity or regulated tenants.

Storage layer:

  • Shared schema with a tenant column + row-level security; or schema-per-tenant; or database-per-tenant. Higher separation = stronger isolation, more operational overhead.

The architect’s job is matching isolation strength to risk and compliance per tenant — a free-tier tenant might be logical-only; a regulated enterprise tenant might get dedicated instances and database-per-tenant. State your choice and its justification; that’s what a design review wants.


5. Auditability — an entitlement requirement, not an afterthought

Audit is part of the entitlement system: you must be able to prove who did what, in which tenant, and whether it was allowed. Log every tool/resource/prompt invocation with rich metadata:

  • subject (user/agent identity), tenant, role(s)/attributes used in the decision
  • action (tool/resource/prompt + arguments, with sensitive values redacted)
  • decision (allow/deny) and policy/rule that produced it
  • timestamp, request id / trace id, and the result/outcome (and isError)
  • for upstream calls: which exchanged token/audience was used

Why it’s non-negotiable:

  • Detect exfiltration/abuse (Module 8 T7) — without per-call logs you can’t see a siphon.
  • Forensics & compliance — SOC 2 / regulated environments require it.
  • Debugging entitlements — “why was this denied?” needs the decision + rule recorded.

Design logs as tamper-evident, tenant-tagged, and queryable, and feed them to the observability pipeline (Module 15). A denial-rate spike is a security signal; a tool-call audit trail is your evidence.


6. Putting the hop chain together (reference walk)

For two tenants calling the same logical platform:

 Alice@Acme ─▶ Host ─▶ Gateway ─────────────────▶ MCP server ─▶ GitHub API
   token aud:gateway      │ validate token (aud, sig, exp)        │ token-exchange:
   tenant=Acme (claim)    │ PEP→PDP: allow? (tenant=Acme rule)    │ new token aud:GitHub,
                          │ inject trace id + tenant into context │ scope=repo:read, subject=Alice
                          ▼                                       ▼
 Bob@Globex ─▶ Host ─▶ Gateway ─ (tenant=Globex; PDP denies any  ─▶ never resolves
                          Acme resource; logs decision)             an Acme repo

Every box: validate audience, enforce tenant, log the decision, mint the right token for the next hop. If you can narrate this for both tenants and point to where leakage would be caught, Phase 3 is complete.


7. Milestone exercise

  1. Diagram identity flow host → gateway → server → upstream for two tenants. Mark, at each hop: which token/audience is used, where the token is validated, where tenant is enforced, and where token exchange happens.
  2. Passthrough audit. Find the one place a naive design would forward the inbound token to the upstream. Replace it with token exchange; state the audience and minimized scope of the new token.
  3. IDOR drill. A get_document(id) tool is called with an id from another tenant (the model picked it up from injected content). Show exactly where and how this is denied, and why it can’t be left to the upstream.
  4. Isolation design. For a free-tier tenant and a regulated-enterprise tenant, choose isolation at the storage, server, and authorization layers, and justify the difference.
  5. Audit schema. Define the fields you’d log per tool call to satisfy both “detect exfiltration” and “explain a denial.”

Self-check:

  • No hop forwards the inbound token; upstream calls use token exchange (RFC 8693) with upstream audience + minimized scope.
  • Tenant comes from the token claim, not a request argument, and appears in every PDP rule and every log line.
  • The IDOR is denied at the PEP/PDP before execution — not assumed away because “the model wouldn’t.”
  • Your isolation choices differ by tenant risk/compliance across all three layers.
  • Your audit record includes subject, tenant, action, decision + rule, and outcome.

That completes Phase 3 — you can design the security and entitlement core of an MCP platform. Next is Phase 4 · Module 13 — The MCP Gateway pattern, where all of this converges into a single control plane.


Quick reference — Module 12 glossary

  • No passthrough — never forward an inbound token; each hop’s token has that hop as audience; upstream calls use token exchange (RFC 8693) with minimized scope.
  • On-behalf-of vs service identity — be explicit; don’t let a service token silently impersonate a user (breaks audit + entitlements).
  • Tenant binding — tenant from the token claim, enforced in every PDP rule and logged everywhere; reject cross-tenant references at the PEP.
  • IDOR risk — model-generated arguments can reference other tenants’ ids → ownership/tenant checks must live in PEP/PDP.
  • Isolation layers — authorization (always), server (logical / namespaces / dedicated), storage (row / schema / database per tenant); match strength to risk/compliance.
  • Audit — per-call logs (subject, tenant, action, decision+rule, outcome, trace id); tamper-evident, tenant-tagged, queryable; an entitlement requirement.