Multi-Tenancy
Auris is a multi-tenant identity and access management platform. Every deployment serves one or more tenants, each representing an isolated organizational unit with its own users, applications, roles, permissions, security policies, and branding. This page explains how tenancy works at every layer of the system.
The Auris Tenant Model
A tenant in Auris represents a single organization, company, or environment that uses Auris for authentication and authorization. Each tenant gets:
- Its own set of users (no user is shared across tenants by default)
- Its own applications (each with its own OAuth client credentials)
- Its own roles and permissions (RBAC, FGA models, custom claims)
- Its own security policies (MFA enforcement, session policies, rate limits, IP rules, CAPTCHA)
- Its own branding (logo, colors, company name, custom domain)
- Its own billing subscription (plan, payment method, invoices)
- Its own audit logs (authentication events, admin actions, policy changes)
Tenants are the top-level isolation boundary in Auris. Nothing leaks between tenants.
One Tenant = One hard isolation boundary
Each Auris tenant is a hard isolation boundary keyed by opaque Tenant.id. A routing slug (historical column keycloakRealm) may appear in token iss as /realms/<slug> — it is for routing/display only, not authz. APIs derive tenant from verified tenant_id (requireTenant).
Auris Tenant id "cl…acme" ←→ slug "acme-corp" (routing)
Auris Tenant id "cl…beta" ←→ slug "beta-inc"Users, clients, roles, sessions, and credentials belonging to tenant A are not authorized in tenant B. Isolation is enforced by token claims and server-side tenant binding — not by trusting a client realm header.
Data Isolation in Detail
Data isolation operates at multiple layers:
Identity / token layer
| Data | Isolation Level |
|---|---|
| User credentials (password hashes) | Per-realm (separate database rows) |
| User sessions | Per-realm |
| OAuth clients (applications) | Per-realm |
| Realm-level roles | Per-realm |
| Identity provider configurations | Per-realm |
| Authentication flows | Per-realm |
Auris Prisma Layer
All Auris-specific data models include a tenantId field that scopes queries:
| Data | Isolation Mechanism |
|---|---|
| Roles and permissions (V2 RBAC) | tenantId field on Role, Permission, RolePermission |
| FGA authorization models | tenantId on AuthorizationModel |
| FGA relationship tuples | tenantId on RelationshipTuple |
| Actions (custom hooks) | tenantId on Action |
| Custom claims | Via applicationId (which belongs to a tenant) |
| Webhooks and log streams | tenantId on LogStream |
| Organizations (B2B) | tenantId on Organization |
| SCIM connections | tenantId on ScimConnection |
| Billing subscriptions | tenantId on TenantSubscription |
| Attack protection settings | tenantId on AttackProtectionSetting, IpRule, etc. |
Every server-side service method in Auris accepts or derives tenantId from the authenticated request context and uses it to scope database queries. There are no cross-tenant queries in the application code.
API Layer
The API enforces tenant isolation at the request boundary. Every authenticated request must include a valid tenant identifier (via the x-tenant header or derived from the access token). If the authenticated user does not belong to the requested tenant, the API returns 403 Forbidden.
Tenant Identification
Applications and API clients identify which tenant they are operating against using the x-tenant HTTP header.
The x-tenant Header
GET /api/users
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...
x-tenant: acme-corpThe x-tenant header value is the tenant’s slug — a URL-safe identifier set during tenant creation. It is resolved server-side to Tenant.id and compared to the token tenant_id.
How the SDKs Handle It
The Auris SDKs automatically include the x-tenant header on every request based on the configured domain or tenant ID:
// @auris/js
const auris = new AurisClient({
domain: 'acme-corp.auris.example.com',
clientId: 'your-client-id',
})
// SDK extracts tenant from domain and sends x-tenant header automaticallyConsole API Client
The Auris Console’s internal API client (api-client.ts) sends the x-tenant header on every request, derived from the logged-in admin’s tenant context:
// Simplified from apps/console/lib/api-client.ts
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
'x-tenant': currentTenant,
}Hosted Login and Tenant-Specific Branding
When a user is redirected to the Auris Hosted Login page, the page is branded according to the tenant’s configuration. Each tenant can customize:
| Setting | Effect |
|---|---|
| Company Name | Shown in the login page title and email templates |
| Logo | Displayed in the login page header |
| Primary Color | Applied to buttons, links, and focus indicators |
| Background Color | Login page background |
| Favicon | Browser tab icon |
These settings are configured in the Console under Settings → Branding.
How Branding Is Resolved
When the hosted login page loads, Auris resolves branding in this order:
- Application-level overrides: If the
client_idin the OAuth authorize request maps to an application with custom branding, those values are used. - Tenant-level settings: For any branding field not overridden at the application level, the tenant’s default branding is used.
- Platform defaults: If neither application nor tenant has configured a setting, the Auris platform defaults are used.
This means a single tenant can have different-looking login pages for different applications while sharing a consistent base brand.
Custom Domains Per Tenant
Each tenant can configure one or more custom domains to white-label the authentication experience. With a custom domain, the hosted login page, OIDC Discovery document, JWKS endpoint, and all email links use the tenant’s domain instead of the Auris platform domain.
Without custom domain: https://auris.example.com/hosted/login?client_id=...
With custom domain: https://auth.acme-corp.com/hosted/login?client_id=...Setting Up Custom Domains
- Add the domain in Console → Settings → Custom Domains
- Configure a CNAME DNS record pointing to the Auris platform
- Auris verifies DNS ownership via a verification token (CNAME or TXT record)
- SSL/TLS is provisioned automatically via Let’s Encrypt
- Set the domain as primary to use it for all hosted pages and email links
Multiple domains can be registered per tenant (for example, auth.acme-corp.com for production and auth-staging.acme-corp.com for staging), but only one is primary at a time.
Domain Verification
Auris supports two verification methods:
| Method | How It Works |
|---|---|
| CNAME | Add a CNAME record for the domain pointing to the Auris platform hostname |
| TXT | Add a TXT record with a verification token at the domain |
CNAME is recommended because it also handles routing. TXT is available for cases where CNAME records are not possible (for example, apex domains).
Billing Per Tenant
Each tenant has its own billing subscription managed through Stripe Billing integration.
Subscription Plans
Auris offers tiered plans with different feature sets:
| Plan | Price | Key Features |
|---|---|---|
| Free | $0/mo | Basic auth, up to 100 users, 1 application |
| Pro | $29/mo | Unlimited users, social login, MFA, webhooks, organizations |
| Enterprise | $99/mo | SSO (SAML/OIDC), FGA, SCIM provisioning, custom domains, SLA |
How Billing Works
- Each tenant has a
TenantSubscriptionrecord linking to a Stripe subscription - The
TenantStripeCustomerrecord maps the tenant to a Stripe customer ID - Plan changes (upgrade/downgrade) are handled via Stripe Checkout sessions
- Cancellations take effect at the end of the current billing period (
cancelAtPeriodEnd) - Stripe webhooks update the subscription status in real time
Feature Gating
Features are gated based on the tenant’s active plan. The feature-gate.ts module provides:
// Server-side feature check
const allowed = await isFeatureAllowed(tenantId, 'fga')
if (!allowed) {
return Response.json(
{ ok: false, error: { code: 'PLAN_LIMIT', message: 'FGA requires Enterprise plan' } },
{ status: 403 }
)
}Feature gate checks use an in-memory cache with a 5-minute TTL to avoid hitting the database on every request.
Security Settings Per Tenant
Every security feature in Auris is scoped to the tenant level. Different tenants can have completely different security postures:
Per-Tenant Security Controls
| Setting | Description |
|---|---|
| MFA Policy | Which MFA methods are enabled (TOTP, SMS, WebAuthn), whether MFA is optional or required |
| Session Policies | Sliding expiry, absolute expiry, refresh token lifetime |
| Rate Limits | Configured at the infrastructure level but applied per-tenant per-IP |
| IP Rules | Allow/block lists with tenant-wide or per-application scope |
| Brute-Force Protection | Lockout threshold, duration, escalation multiplier |
| Suspicious Login Detection | Which detectors are enabled, what action each triggers |
| CAPTCHA | Provider, trigger policy, which pages are protected |
| Adaptive MFA | Risk scoring thresholds, custom risk rules |
| Password Policy | Minimum length, complexity requirements (configured per tenant) |
Tenant-Level vs Application-Level
Some settings can be further scoped to individual applications within a tenant:
| Scope | Settings |
|---|---|
| Tenant-wide only | MFA policy, session policies, brute-force protection, suspicious login |
| Tenant or per-application | IP rules, CAPTCHA, custom claims, social login providers |
Creating and Managing Tenants
Creating a Tenant
Tenant creation provisions the full infrastructure stack:
- A new Tenant row is created with opaque id + routing slug
- Default realm settings are applied (password policy, session timeouts, required actions)
- The Auris database records are created (tenant configuration, default roles, security defaults)
- A default admin user is created (or an invitation is sent)
- Default permission definitions are seeded
Managing Tenants in the Console
Tenant management is available under Console → Settings → Tenant:
| Action | Description |
|---|---|
| Edit tenant name | Change the display name (the slug/realm name is immutable after creation) |
| Configure security defaults | Set default MFA policy, session settings, password requirements |
| View tenant metadata | Creation date, user count, application count, current plan |
| Delete tenant | Permanently removes the tenant, all users, all data (requires confirmation) |
Deleting a tenant is irreversible. All users, applications, sessions, roles, permissions, FGA models, tuples, audit logs, and billing history for the tenant are permanently removed. Associated identity and session state for that tenant is removed.
Default Tenant vs Multi-Tenant Deployments
Auris supports two deployment modes:
Single-Tenant (Default)
For many deployments, a single tenant is sufficient. The dashboard and website use Auris with a single 'altovar' tenant. In this mode:
- All users belong to the same tenant
- The
x-tenantheader is always the same value - Tenant selection UI is unnecessary
- The deployment is simpler to operate and reason about
Multi-Tenant
Multi-tenant deployments serve multiple customers from a single Auris instance. In this mode:
- Each customer gets its own Tenant with hard-isolated data (recommended MSP model)
- The hosted login page uses the tenant context from the
client_idto resolve branding - Admin console users can manage their own tenant only
- Platform-level administration (creating new tenants, viewing across tenants) requires platform admin access
- Billing, security, and branding are fully independent per tenant
Choosing the Right Mode
| Scenario | Recommendation | Isolation |
|---|---|---|
| Single product, single company | Single tenant | Hard (one boundary) |
| MSP / regulated multi-customer (NIS2/DORA diligence) | One Tenant per customer | Hard |
| Teams / departments inside one customer | Organizations inside that customer’s Tenant | Soft |
| Development + staging + production environments | Multi-tenant (one tenant per environment) | Hard |
| White-label authentication for agencies | Multi-tenant with custom domains | Hard |
| Internal application suite | Single tenant with multiple applications | Hard |
Org-in-tenant is not customer isolation. Modeling multiple external customers as Organizations inside one Tenant is soft isolation at best. The recommended MSP path is tenant-per-customer. Org-in-tenant for multi-customer is disallowed as the default product path unless the platform enforces FGA and org_id on tokens (not app-only filters). Create-organization requests that signal org-as-customer (isolationIntent: "customer" or customer-shaped metadata) are rejected with MSP_USE_TENANT_PER_CUSTOMER (SOV-6); see operations note docs/operations/sov-6-org-as-customer-guard.md.
Tenant Data in JWTs
The access token issued by Auris does not contain a tenantId claim by default. Instead, the iss (issuer) claim encodes the tenant context:
{
"iss": "https://auth.acme-corp.com",
"sub": "usr_abc123",
"aud": "your-client-id"
}If you need the tenant ID explicitly in the token (for multi-tenant resource servers), add it as a Custom Claim or use an Action to inject it:
// Post-Login Action: add tenant to token claims
return {
allow: true,
claims: {
tenant_id: context.tenant.id,
tenant_name: context.tenant.name,
},
}Related Concepts
- OAuth 2.0 & OIDC — The protocol layer that tenant-specific tokens operate within
- Branding & Customization — Configure tenant-specific branding
- Custom Domains — White-label authentication under your domain
- Billing & Plans — Manage tenant subscriptions
- Organizations (B2B) — Soft teams/orgs within a single tenant (not MSP customer isolation)
- Enterprise SSO — Per-organization SAML/OIDC federations