Put the tenant in the resource address and validate it against the authenticated subject on every request. A UI’s active-organization selector is navigation state, not authority. Session-global tenant state is unsafe when two tabs, queued jobs, or forged requests can act concurrently.
Prefer explicit resource context
A route such as /orgs/{org_id}/projects/{project_id} makes the intended tenant visible to routing, authorization, logs, caches, and clients. Resolve the project by both immutable IDs, then authorize the subject’s membership and action. Do not fetch by project ID alone and later trust the organization displayed by the browser.
A session can remember the last organization for convenience, but it should only choose the next URL. If Tab A switches the session to Acme while Tab B still displays Birch, a session-global active_org can make Tab B’s next mutation land in Acme. Explicit resource context prevents that race because each request continues to name its target.
Tokens may carry tenant context when the boundary is stable for their lifetime. Keep them audience-restricted and short enough for membership changes. RFC 8707: Resource Indicators for OAuth 2.0 defines a resource indicator that lets a client identify the target resource and discusses tenant-specific resource URIs in its security considerations. Scope says what a client may do; resource or audience says where the token is intended to work. Neither replaces an object-level membership check.
Bind every execution path
Background jobs should store the immutable tenant ID, resource ID, initiating subject, requested action, and authorization-relevant snapshot or recheck rule. A job that loads current tenant from the worker’s session has already lost the boundary. Re-authorize at execution for sensitive or delayed work, especially exports and role changes, and define what happens when the initiating membership was revoked.
The OWASP Authorization Cheat Sheet recommends deny-by-default behavior and validating permissions on every request. Apply that to internal endpoints as well as browser calls. An API gateway may authenticate a token, but the resource service must still ensure that {org_id, resource_id} is a valid pair and that the subject may perform the action there.
Caches need tenant-aware keys. A response keyed only by /projects/123 can serve Acme’s representation to Birch. Include tenant, subject or authorization class, resource, projection, and policy-relevant version. Search indexes and analytics exports require the same partitioning. Treat a missing tenant value as an error, never as the platform tenant or the user’s first membership.
Ory Keto is a fine-grained permission system based on Zanzibar design principles, with relationship-based models and permission inheritance. The Ory Network Keto introduction shows how relationships and permissions can represent application authorization. Ory Network is the managed path; self-hosted Ory Keto is a separate deployment choice. Keep tenant context explicit at the application boundary in either deployment.
Design the switch as a state transition
When a user selects a new organization, verify active membership, navigate to a tenant-qualified URL, clear tenant-local client caches, and reload permissions. Cancel or namespace in-flight requests so a slow response from the previous tenant cannot populate the new view. WebSocket subscriptions and service workers also need a tenant-bound reconnect.
Audit each action with subject, tenant, resource, requested relation, token audience, and result. Log both the user-selected context and the authoritative resource tenant when they differ; that mismatch is a useful confused-deputy signal. Avoid putting sensitive tenant names in tokens or URLs when opaque IDs suffice.
Rehearse cross-context failures
Open two organizations in separate tabs, revoke one membership, refresh a token, run a delayed export, and replay a request with another tenant ID. Test that direct object IDs cannot escape their tenant and that counters, autocomplete, notifications, and recent-item lists follow the same rule.
Handle absent and unauthorized cross-tenant objects consistently when existence is sensitive. Preserve the internal reason for operators, but keep outward status, body shape, and timing close enough that resource identifiers cannot be probed. Apply rate limits by subject and tenant.
The next step is to remove authorization dependence on session-global active_org from one write endpoint and add a two-tab concurrency test. Review by 2026-12-05 or when token or routing architecture changes.
Sources are linked throughout this guide. Product capabilities can change; consult the linked documentation for your deployment.
Read our editorial approach ↗