Multi-Tenancy Architecture
Authrim’s multi-tenancy architecture allows you to manage multiple isolated customer environments (tenants) within a single deployment. This page explains the relationship between environments, tenants, and clients, along with the domain naming patterns.
Architecture Overview
Authrim’s multi-tenancy model is based on three key concepts:
flowchart TB
subgraph env["Environment (Separate Instance)"]
subgraph tenant1["Tenant: acme"]
client1a["Client: web-app"]
client1b["Client: mobile-app"]
client1c["Client: api-service"]
end
subgraph tenant2["Tenant: widget-co"]
client2a["Client: dashboard"]
client2b["Client: api"]
end
subgraph tenant3["Tenant: default"]
client3a["Client: admin"]
end
end
style env fill:#f0f8ff
style tenant1 fill:#fff8dc
style tenant2 fill:#fff8dc
style tenant3 fill:#fff8dc
1. Environment (Infrastructure Level)
Each environment runs as a completely separate Workers instance.
- Purpose: Lifecycle stage separation (development, testing, staging, production)
- Isolation: Separate codebase deployment, separate databases, separate KV stores
- Domain: Each environment has its own
BASE_DOMAIN - Independence: Environments never share data or configuration
Example:
Production: BASE_DOMAIN = "authrim.com"Staging: BASE_DOMAIN = "staging.authrim.com"Test: BASE_DOMAIN = "test.authrim.com"Development: BASE_DOMAIN = "dev.authrim.com"2. Tenant (Isolation Boundary)
A tenant is an isolated customer workspace within an environment.
- Purpose: Customer/organization data isolation
- Scope: All data (users, sessions, tokens, policies) is scoped to a tenant
- Domain: Identified by subdomain (e.g.,
acme.authrim.com) - Isolation: Complete logical separation — tenants cannot see or access each other’s data
Key Characteristics:
- Multi-tenant mode is always enabled when
BASE_DOMAINis set - Each tenant has isolated:
- User directory
- Client applications
- Sessions and tokens
- Audit logs
- Authorization policies
- No cross-tenant data sharing (unless explicitly federated)
3. Client (Application)
A client is an application registered within a tenant.
- Purpose: Represents an application that authenticates users
- Scope: Belongs to exactly one tenant
- Types: Web apps, mobile apps, SPAs, backend services, M2M applications
- Protocols: OAuth 2.0, OIDC, SAML 2.0
Example:
- Tenant:
acme- Client:
web-dashboard(OIDC, authorization_code + PKCE) - Client:
mobile-app(OIDC, authorization_code + PKCE) - Client:
api-service(OAuth 2.0, client_credentials) - Client:
legacy-app(SAML 2.0 SP)
- Client:
Domain Patterns
Valid Patterns
Authrim uses subdomain-based tenant isolation. Only single-level subdomains are allowed.
Production Environment (BASE_DOMAIN = "authrim.com")
| Domain | Tenant ID | Description |
|---|---|---|
authrim.com | PRIMARY_TENANT_ID or default | Naked domain (primary tenant) |
acme.authrim.com | acme | Simple tenant name |
acme-corp.authrim.com | acme-corp | Hyphenated tenant name |
acme-prod.authrim.com | acme-prod | Tenant with suffix |
tenant123.authrim.com | tenant123 | Alphanumeric tenant |
a.authrim.com | a | Single-character tenant |
Staging Environment (BASE_DOMAIN = "staging.authrim.com")
| Domain | Tenant ID | Description |
|---|---|---|
staging.authrim.com | default | Naked domain |
acme.staging.authrim.com | acme | Tenant in staging |
widget-co.staging.authrim.com | widget-co | Another tenant |
Test Environment (BASE_DOMAIN = "test.authrim.com")
| Domain | Tenant ID | Description |
|---|---|---|
test.authrim.com | default | Naked domain |
acme.test.authrim.com | acme | Tenant in test environment |
Invalid Patterns (Rejected)
❌ Sub-subdomains are NOT allowed
Multi-level subdomains are rejected to prevent ambiguity and maintain security.
| Domain | Error | Reason |
|---|---|---|
dev.acme.authrim.com | invalid_format (400) | Sub-subdomain not allowed |
api.acme.authrim.com | invalid_format (400) | Sub-subdomain not allowed |
auth.tenant.staging.authrim.com | invalid_format (400) | Sub-subdomain not allowed |
acme.other.com | tenant_not_found (404) | BASE_DOMAIN mismatch |
-acme.authrim.com | invalid_format (400) | Hyphen at start |
acme-.authrim.com | invalid_format (400) | Hyphen at end |
tenant_name.authrim.com | invalid_format (400) | Underscore not allowed |
Why sub-subdomains are prohibited:
- Ambiguity:
dev.acme.authrim.com— isdeva service prefix or part of the tenant name? - Simplicity: Single-level subdomains are easier to understand and manage
- Security: Prevents potential DNS-based attacks and misconfiguration
- Architecture: Environments are separate instances, not subdomains
Solution: Use hyphens in tenant names instead:
- ❌
dev.acme.authrim.com→ ✅acme-dev.authrim.com - ❌
api.tenant.authrim.com→ ✅tenant-api.authrim.com
Environment Configuration
Environment Variables
Each environment instance requires these variables:
# Base domain for multi-tenant modeBASE_DOMAIN="authrim.com"
# Default tenant ID (used when no tenant specified)DEFAULT_TENANT_ID="default"
# Primary tenant for naked domain access (optional)PRIMARY_TENANT_ID="main"
# User ID format (uuid or nanoid)USER_ID_FORMAT="nanoid"Naked Domain Routing
When accessing the naked domain (e.g., authrim.com), Authrim uses this priority:
PRIMARY_TENANT_IDif set → routes to that tenantDEFAULT_TENANT_IDif set → routes to that tenant- Fallback to
"default"tenant
Example:
BASE_DOMAIN="authrim.com"PRIMARY_TENANT_ID="main"DEFAULT_TENANT_ID="default"authrim.com→ routes to tenant"main"acme.authrim.com→ routes to tenant"acme"widget.authrim.com→ routes to tenant"widget"
Relationship Examples
Example 1: SaaS Platform
Scenario: A SaaS company with multiple customers
Production Environment (BASE_DOMAIN = "myplatform.com"):├── Tenant: acme│ ├── Client: web-dashboard (OIDC)│ ├── Client: mobile-app (OIDC)│ └── Client: reporting-api (OAuth M2M)├── Tenant: widget-co│ ├── Client: admin-panel (OIDC)│ └── Client: public-api (OAuth M2M)└── Tenant: default └── Client: platform-admin (OIDC)
Staging Environment (BASE_DOMAIN = "staging.myplatform.com"):├── Tenant: acme│ ├── Client: web-dashboard (testing)│ └── Client: mobile-app (testing)└── Tenant: test-customer └── Client: demo-app (testing)Domains:
- Production:
acme.myplatform.com,widget-co.myplatform.com - Staging:
acme.staging.myplatform.com,test-customer.staging.myplatform.com
Example 2: Single Tenant (Company Internal)
Scenario: A company using Authrim for internal authentication
Production Environment (BASE_DOMAIN = "auth.company.com"):└── Tenant: company (via PRIMARY_TENANT_ID) ├── Client: employee-portal (OIDC) ├── Client: mobile-app (OIDC) ├── Client: legacy-intranet (SAML) └── Client: api-gateway (OAuth M2M)
Test Environment (BASE_DOMAIN = "auth-test.company.com"):└── Tenant: company └── Client: employee-portal (testing)Configuration:
# ProductionBASE_DOMAIN="auth.company.com"PRIMARY_TENANT_ID="company"
# Users access via naked domain: auth.company.com → tenant "company"Example 3: Multi-Environment Development
Scenario: A development team with multiple environments
Development (BASE_DOMAIN = "dev.authrim.com"):├── Tenant: alice-dev│ └── Client: test-app└── Tenant: bob-dev └── Client: test-app
Test/CI (BASE_DOMAIN = "test.authrim.com"):├── Tenant: ci-runner-1│ └── Client: automated-tests└── Tenant: ci-runner-2 └── Client: automated-tests
Staging (BASE_DOMAIN = "staging.authrim.com"):└── Tenant: demo ├── Client: web-app └── Client: mobile-app
Production (BASE_DOMAIN = "authrim.com"):├── Tenant: customer-a│ ├── Client: web-app│ └── Client: mobile-app└── Tenant: customer-b ├── Client: dashboard └── Client: apiBest Practices
1. Environment Separation
✅ Do:
- Use separate
BASE_DOMAINfor each environment - Deploy each environment as a separate Workers instance
- Use environment-specific databases and KV stores
❌ Don’t:
- Mix test and production data in the same environment
- Use sub-subdomains to represent environments (e.g.,
test.acme.authrim.com)
2. Tenant Naming
✅ Do:
- Use lowercase alphanumeric characters and hyphens
- Keep tenant names short and descriptive
- Use hyphens for multi-word names:
acme-corp,widget-co
❌ Don’t:
- Start or end with hyphens:
-acme,acme- - Use special characters:
acme_corp,acme@company - Create sub-subdomains:
dev.acme.authrim.com
3. Client Management
✅ Do:
- Register each application as a separate client
- Use appropriate client types (confidential for backend, public for SPA/mobile)
- Enable PKCE for all public clients
- Use client_credentials grant for M2M services
❌ Don’t:
- Share client credentials across tenants
- Use the same client_id in multiple environments (use separate clients)
- Disable PKCE for public clients
Security Considerations
Tenant Isolation
- Database-level isolation: All queries are automatically scoped to tenant ID
- Session isolation: Sessions cannot be shared across tenants
- Token isolation: Access tokens are bound to the issuing tenant
- Audit isolation: Audit logs are separated by tenant
Domain Validation
- Host header validation: Authrim validates the Host header on every request
- BASE_DOMAIN enforcement: Only requests matching
*.BASE_DOMAINare accepted - Sub-subdomain rejection: Multi-level subdomains are rejected with 400 Bad Request
- CORS enforcement: CORS policies are tenant-scoped
Cross-Tenant Access
By default, cross-tenant access is prohibited. If you need federated access:
- Use external IdP bridge: Configure social login or enterprise SSO
- SAML federation: Set up SAML trust between tenants
- Token exchange: Implement OAuth 2.0 token exchange (RFC 8693)
Migration Scenarios
From Single-Tenant to Multi-Tenant
If you started with a single-tenant deployment and want to migrate:
- Set BASE_DOMAIN: Configure your base domain (e.g.,
authrim.com) - Set PRIMARY_TENANT_ID: Point naked domain to existing tenant
- Migrate users: All existing users belong to the primary tenant
- Add new tenants: Create new tenants via Admin API
- Update DNS: Configure wildcard DNS for
*.authrim.com
Example:
# Before (single-tenant)ISSUER_URL="https://auth.company.com"
# After (multi-tenant)BASE_DOMAIN="auth.company.com"PRIMARY_TENANT_ID="company"# Naked domain (auth.company.com) still works → routes to "company" tenantAdding New Tenants
To add a new tenant:
- Create tenant via Admin API:
POST /admin/tenants{ "tenantId": "new-customer", "displayName": "New Customer Inc."}- Configure DNS: Ensure
new-customer.authrim.comresolves to your Workers - Register clients: Create OAuth/OIDC clients for the new tenant
- Test access: Verify login at
new-customer.authrim.com
Troubleshooting
Common Issues
Issue: tenant_not_found error (404)
Causes:
- Domain doesn’t match BASE_DOMAIN
- Using sub-subdomain (e.g.,
dev.acme.authrim.com) - Tenant doesn’t exist in the database
Solution:
- Verify BASE_DOMAIN configuration
- Use single-level subdomain:
acme.authrim.com - Create tenant via Admin API
Issue: invalid_format error (400)
Causes:
- Sub-subdomain used (e.g.,
api.tenant.authrim.com) - Invalid characters in subdomain
- Hyphen at start or end
Solution:
- Use hyphens instead:
tenant-api.authrim.com - Use only alphanumeric and hyphens:
[a-z0-9-]+ - Ensure no leading/trailing hyphens
Issue: missing_host error (400)
Cause: Host header is missing from the request
Solution:
- Ensure HTTP client sends Host header
- Check reverse proxy configuration
- Verify Cloudflare settings
Further Reading
- Architecture - Overall system architecture
- Edge Computing - Edge deployment and global distribution
- Getting Started - Initial setup and configuration
- Admin API - Programmatic tenant and client management