Multi-Tenancy
Multi-tenancy is the foundation of B2B SaaS. In Optare, every user belongs to one or more organizations (tenants).
What is Multi-Tenancy?
Instead of having separate databases or deployments for each customer, multi-tenancy allows one application to serve many customers with data isolation.
┌─────────────────────────────────────────────────────────┐
│ Your Application │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Acme Co │ │ Beta Inc│ │ Gamma │ │
│ │ (Org 1) │ │ (Org 2) │ │ (Org 3) │ │
│ ├─────────┤ ├─────────┤ ├─────────┤ │
│ │ Users │ │ Users │ │ Users │ │
│ │ Data │ │ Data │ │ Data │ │
│ │ Settings│ │ Settings│ │ Settings│ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────┘Key Concepts
Organizations
An Organization is a customer account. It has:
- A unique ID and slug
- Settings and configuration
- Members with roles
- Subscriptions and licenses
interface Organization {
id: string; // "org_abc123"
name: string; // "Acme Corporation"
slug: string; // "acme"
logoUrl?: string;
settings: OrgSettings;
}Members
Members are users who belong to an organization with a specific role.
| Role | Permissions |
|---|---|
| Owner | Full control, billing, delete org |
| Admin | Manage members, settings |
| Member | Use the product |
| Guest | Limited read access |
The User-Organization Relationship
User (john@gmail.com)
├── Member of "Acme Corp" (Admin role)
└── Member of "Beta Inc" (Member role)A single user can belong to multiple organizations with different roles in each.
How It Works in Code
Get User's Organizations
import { useOptare } from '@optare/optareid-react';
function OrgSwitcher() {
const { organizations, switchOrganization } = useOptare();
return (
<select onChange={(e) => switchOrganization(e.target.value)}>
{organizations.map(org => (
<option key={org.id} value={org.id}>
{org.name} ({org.role})
</option>
))}
</select>
);
}Current Organization Context
Tokens include the current organization:
{
"sub": "user_abc123",
"org_id": "org_xyz789",
"org_name": "Acme Corp",
"role": "admin"
}Server-Side: Scope Data by Organization
app.get('/api/projects', requireAuth, async (req, res) => {
const orgId = req.user.organizationId;
// Always filter by organization!
const projects = await db.projects.findMany({
where: { organizationId: orgId }
});
res.json(projects);
});Organization Isolation
Data Isolation
Each organization's data is isolated. Users can only access data belonging to their current organization.
// ✅ Correct: Always scope queries
await db.users.findMany({
where: { organizationId: currentOrgId }
});
// ❌ Wrong: Never query without org scope
await db.users.findMany(); // Security risk!Feature Isolation
Organizations can have different feature access based on their subscription:
const { hasLicense } = useOptare();
{hasLicense('advanced-analytics') && (
<AnalyticsDashboard />
)}Common Patterns
1. Organization Signup Flow
1. User signs up
2. User creates an organization
3. User becomes Owner of that organization
4. User can invite team members2. Invitation Flow
1. Admin invites user by email
2. Invitee receives email
3. Invitee creates account (or logs in)
4. Invitee is added as Member3. Organization Switching
1. User is viewing Org A
2. User selects Org B from switcher
3. New tokens issued with Org B context
4. UI refreshes with Org B dataBest Practices
Always Include Organization in Queries
// Every database query should include organizationId
const data = await db.table.findMany({
where: {
organizationId: req.user.organizationId,
// ... other filters
}
});Validate Organization Access on Every Request
async function validateOrgAccess(userId: string, orgId: string) {
const membership = await db.memberships.findFirst({
where: { userId, organizationId: orgId }
});
if (!membership) {
throw new UnauthorizedError('Not a member of this organization');
}
return membership;
}Use Role-Based Access Control
import { requirePermission } from '@optare/rbac';
app.delete('/api/users/:id', requireAuth, async (req, res) => {
await requirePermission(req.user, 'members.remove');
// Delete user...
});Next Steps
- Organizations API - Manage organizations programmatically
- RBAC - Role-based access control
- SCIM Provisioning - Enterprise user provisioning