Skip to main content

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]
  1. alice logs into customer-project using her octosystem credentials
  2. The Identity Service finds the OctoTenantIdentityProvider pointing to octosystem
  3. It validates alice's credentials against the parent tenant
  4. It finds an ExternalTenantUserMapping for alice, granting her the Development and DashboardViewer roles
  5. 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 AllowSelfRegistration and DefaultGroupRtId

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:

PropertyDescription
SourceTenantIdThe parent tenant where the user originates
SourceUserIdThe user's ID in the parent tenant
SourceUserNameThe user's name in the parent tenant
RoleIdsRoles assigned in the child tenant
GroupNamesGroups 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:

  1. Creates an ExternalTenantUserMapping with all available roles in the target tenant
  2. Adds the mapping to the TenantOwners group (granting all default roles via group inheritance)
  3. 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"
info

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:

  1. Login tenant — always included
  2. Home tenant — for cross-tenant users (username starts with xt_), their parent tenant is extracted and included
  3. Ancestor tenants — walks up the tenant hierarchy by following OctoTenantIdentityProvider parent references (up to 10 levels, with cycle detection)
  4. 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.