Cross-Tenant Authentication
OctoMesh supports a hierarchical tenant model where a parent tenant can authenticate users in child tenants. This allows organizations to maintain a single user directory while granting access to multiple isolated tenants.
How It Works
Parent Tenant (e.g., "octosystem")
├── User: alice (local user)
├── User: bob (local user)
│
└── Child Tenant (e.g., "customer-project")
├── OctoTenantIdentityProvider → points to "octosystem"
├── ExternalTenantUserMapping: alice → roles: [Development, DashboardViewer]
└── ExternalTenantUserMapping: bob → roles: [TenantManagement]
- alice logs into
customer-projectusing heroctosystemcredentials - The Identity Service finds the OctoTenantIdentityProvider pointing to
octosystem - It validates alice's credentials against the parent tenant
- It finds an ExternalTenantUserMapping for alice, granting her the
DevelopmentandDashboardViewerroles - A JWT token is issued with
tenant_id: customer-project,home_tenant_id: octosystem, and the mapped roles
Key Concepts
OctoTenant Identity Provider
An OctoTenant identity provider delegates authentication to a parent tenant. When a user tries to log in and no local user is found, the Identity Service walks up the tenant hierarchy (via OctoTenantIdentityProviders) to find and authenticate the user.
- The hierarchy supports up to 10 levels of depth
- Cycle detection prevents infinite loops
- The provider can be configured with
AllowSelfRegistrationandDefaultGroupRtId
External Tenant User Mappings
An external tenant user mapping links a user from a parent tenant to a set of roles (and optionally groups) in the child tenant. Without a mapping, an authenticated cross-tenant user has no roles in the child tenant.
Each mapping contains:
| Property | Description |
|---|---|
| SourceTenantId | The parent tenant where the user originates |
| SourceUserId | The user's ID in the parent tenant |
| SourceUserName | The user's name in the parent tenant |
| RoleIds | Roles assigned in the child tenant |
| GroupNames | Groups the user belongs to in the child tenant |
Cross-Tenant User Prefix
When a cross-tenant user logs in for the first time, the Identity Service creates a local user record in the child tenant with the prefix xt_{parentTenantId}_{username}. This local record is linked to the external tenant user mapping.
Setting Up Cross-Tenant Access
Step 1: Create an OctoTenant Identity Provider
In the child tenant, create a provider pointing to the parent:
# Switch to child tenant context
octo-cli -c Config -tid "customer-project"
octo-cli -c LogIn -i
# Add the OctoTenant provider
octo-cli -c AddOctoTenantIdentityProvider \
-n "Parent Tenant" \
-ptid "octosystem" \
-e true
Step 2: Create External Tenant User Mappings
Map parent tenant users to roles in the child tenant:
octo-cli -c CreateExternalTenantUserMapping \
-stid "octosystem" \
-suid "<user-id-from-parent>" \
-sun "alice" \
-rids "Development,DashboardViewer"
Step 3: Verify
# List all mappings
octo-cli -c GetExternalTenantUserMappings
# Filter by source tenant
octo-cli -c GetExternalTenantUserMappings -stid "octosystem"
Sharing Users from Parent to Child Tenant
There are two approaches for granting a parent tenant user access to a child tenant:
Approach 1: Direct Mapping (Requires Child Tenant Access)
If you already have access to the child tenant, create an ExternalTenantUserMapping directly (see Step 2 above). This gives you full control over which roles are assigned.
Approach 2: Admin Provisioning (No Child Tenant Access Required)
Admin provisioning solves a chicken-and-egg problem: to create user mappings in a child tenant, you need allowed_tenants for that tenant. But you only get allowed_tenants after a mapping exists.
Admin provisioning allows any user with an octo_api scope in their access token to pre-create user mappings in a target tenant without needing prior access to that tenant.
Provision Current User
The simplest approach — provision yourself in a target tenant. This:
- Creates an ExternalTenantUserMapping with all available roles in the target tenant
- Adds the mapping to the TenantOwners group (granting all default roles via group inheritance)
- If the target tenant is still initializing (CK model not loaded), retries automatically
# From the system tenant context
octo-cli -c Config -tid "octosystem"
octo-cli -c LogIn -i
# Provision yourself in the target tenant
octo-cli -c ProvisionCurrentUser -ttid "customer-project"
After provisioning, log out and log back in to receive the updated allowed_tenants claim in your token.
Provision a Specific User
For more granular control, create a mapping for a specific user with selected roles:
octo-cli -c CreateAdminProvisioningMapping \
-ttid "customer-project" \
-stid "octosystem" \
-suid "<user-id>" \
-sun "alice" \
-rids "Development,DashboardViewer"
List and Delete Mappings
# List mappings in target tenant
octo-cli -c GetAdminProvisioningMappings -ttid "customer-project"
# Delete a mapping
octo-cli -c DeleteAdminProvisioningMapping -ttid "customer-project" -mid "<mapping-rtid>"
Complete Workflow: New Tenant with User Access
# 1. Create a new child tenant (from system tenant)
octo-cli -c Config -tid "octosystem"
octo-cli -c LogIn -i
octo-cli -c Create -tid "new-project" -db "new_project_db"
# 2. Provision yourself in the new tenant
octo-cli -c ProvisionCurrentUser -ttid "new-project"
# 3. Log out and log back in (to refresh allowed_tenants)
octo-cli -c Config -tid "new-project"
octo-cli -c LogIn -i
# 4. Optionally provision other users from the parent tenant
octo-cli -c CreateAdminProvisioningMapping \
-ttid "new-project" \
-stid "octosystem" \
-suid "<bob-user-id>" \
-sun "bob" \
-rids "DashboardViewer,ReportingViewer"
When a new child tenant is created, an OctoTenant identity provider pointing to the parent is automatically created. You do not need to create it manually.
Allowed Tenants Resolution
At token issuance, the Identity Service resolves which tenants a user may access. The algorithm has four phases:
- Login tenant — always included
- Home tenant — for cross-tenant users (username starts with
xt_), their parent tenant is extracted and included - Ancestor tenants — walks up the tenant hierarchy by following OctoTenantIdentityProvider parent references (up to 10 levels, with cycle detection)
- Descendant tenants — BFS traversal down through child tenants, checking for ExternalTenantUserMapping entries matching the user. This cascades through multiple levels (e.g.,
octosystem → customer-project → sub-project)
The resulting list is emitted as allowed_tenants claims in the JWT access token. Services validate these claims to authorize tenant access.
Example
octosystem (parent)
├── customer-project (child)
│ └── sub-project (grandchild)
When user alice from octosystem has an ExternalTenantUserMapping in customer-project, and her cross-tenant identity xt_octosystem_alice has a mapping in sub-project, her allowed_tenants will include all three tenants.