multi-tenant-saas-architecture/SKILL.md
Production-grade multi-tenant SaaS platform architecture with three-panel separation, zero-trust security, strict tenant isolation, and comprehensive audit trails. Use for designing multi-tenant systems, implementing tenant-scoped permissions...
npx skillsauth add peterbamuhigire/skills-web-dev multi-tenant-saas-architectureInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
multi-tenant-saas-architecture or would be better handled by a more specific companion skill.references, documentation only as needed.SKILL.md first, then load only the referenced deep-dive files that are necessary for the task.references/ directory for deep detail after reading the core workflow below.documentation/ directory for supporting implementation detail or migration notes.Production-grade multi-tenant SaaS with strict tenant isolation, zero-trust security, and three-panel separation.
Core Principles: Zero-trust, Tenant isolation by default, Least privilege, Audit everything.
Security Baseline (Required): Load and apply the Vibe Security Skill for all web app/API work. Database Standards (Required): All database work MUST follow mysql-best-practices skill patterns.
Environments:
| Environment | OS | Database | Web Root |
|---|---|---|---|
| Development | Windows 11 (WAMP) | MySQL 8.4.7 | C:\wamp64\www\{project}\ |
| Staging/Production | Ubuntu/Debian VPS | MySQL 8.x | /var/www/html/{project}/ |
Cross-platform: utf8mb4_unicode_ci everywhere. Match file case exactly (Linux is case-sensitive).
┌──────────────────────────────────────────────────────────────┐
│ Shared Infrastructure Layer │
│ Data (Tenant Isolated) | Business Logic | Session System │
└──────────────────────────────────────────────────────────────┘
│ │ │
┌────────▼────────┐ ┌─────────▼──────┐ ┌────────▼────────┐
│ /public/ │ │ /adminpanel/ │ │ /memberpanel/ │
│ (ROOT) │ │ │ │ │
│ Franchise Admin │ │ Super Admin │ │ End User │
│ Workspace │ │ System │ │ Portal │
│ owner, staff │ │ super_admin │ │ member/student │
└─────────────────┘ └────────────────┘ └─────────────────┘
CRITICAL: /public/ root = Franchise admin workspace — NOT a member panel!
public/
├── index.php # Landing page
├── sign-in.php # Login
├── dashboard.php # Franchise admin dashboard
├── adminpanel/ # Super admin panel
│ └── includes/
├── memberpanel/ # End user portal
│ └── includes/
├── includes/ # Shared includes
└── assets/
/public/ root) — THE MAIN WORKSPACEUsers: owner, staff | Scope: Single franchise only
WHERE franchise_id = ?/public/adminpanel/)Users: super_admin | Scope: Cross-franchise with audit trails
franchise_id CAN be NULL for super admins/public/memberpanel/)Users: member, student, customer, patient | Scope: Own records only
User Type → franchise_id rule:
super_admin → franchise_id CAN be NULL
owner → franchise_id REQUIRED, NOT NULL
staff → franchise_id REQUIRED, NOT NULL
member/student/customer/patient → franchise_id REQUIRED, NOT NULL
-- Every franchise-scoped table has franchise_id
CREATE TABLE students (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
franchise_id BIGINT UNSIGNED NOT NULL,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
email VARCHAR(100),
FOREIGN KEY (franchise_id) REFERENCES tbl_franchises(id) ON DELETE CASCADE,
INDEX idx_franchise (franchise_id),
INDEX idx_franchise_email (franchise_id, email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ALL queries MUST include franchise_id
SELECT * FROM students WHERE franchise_id = ? AND id = ?;
// Session Prefix System (CRITICAL — prevents collisions in multi-tenant sessions)
define('SESSION_PREFIX', 'saas_app_'); // Customize per SaaS: 'school_', 'restaurant_', etc.
setSession('user_id', $userId); // Sets $_SESSION['saas_app_user_id']
$userId = getSession('user_id'); // Gets $_SESSION['saas_app_user_id']
// ALWAYS extract franchise from session — NEVER from user input
$franchiseId = getSession('franchise_id'); // ✅
// $franchiseId = $_POST['franchise_id']; // ❌ NEVER
// Regular users: filter by franchise_id
$stmt = $db->prepare("SELECT * FROM students WHERE franchise_id = ? AND id = ?");
$stmt->execute([getSession('franchise_id'), $studentId]);
// Super admin: allowed cross-franchise access, but ALWAYS log it
if (getSession('user_type') === 'super_admin') {
auditLog('CROSS_FRANCHISE_ACCESS', [
'admin_user_id' => getSession('user_id'),
'target_franchise_id' => $requestedFranchiseId,
'action' => 'VIEW_STUDENTS'
]);
}
// HTTPS auto-detection (CRITICAL for localhost development)
$isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] == 443;
ini_set('session.cookie_secure', $isHttps ? '1' : '0');
Session settings: HttpOnly: true | SameSite: Strict | Lifetime: 30 min | Regenerate on login
JWT (mobile/API): Access token: 15 min | Refresh token: 30 days | Rotation on refresh | Revocation table
// Priority: User deny > User grant > Franchise override > Role permission > Default DENY
function hasPermission(userId, tenantId, permission) {
if (user.type === 'super_admin') return true;
if (userPermissions.denied(userId, tenantId, permission)) return false;
if (userPermissions.granted(userId, tenantId, permission)) return true;
const roles = getUserRoles(userId, tenantId);
for (const role of roles) {
if (roleHasPermission(role, permission, tenantId)) return true;
}
return false; // Default deny
}
franchise_id in every query (except super_admin with audit)// ❌ Client-controlled franchise_id
$franchiseId = $_POST['franchise_id'];
// ✅ Server-side session (with prefix)
$franchiseId = getSession('franchise_id');
// ❌ Missing franchise scope (data leakage)
$stmt = $db->prepare("SELECT * FROM students WHERE id = ?");
// ✅ Always franchise-scoped
$stmt = $db->prepare("SELECT * FROM students WHERE franchise_id = ? AND id = ?");
$stmt->execute([getSession('franchise_id'), $studentId]);
// ❌ Super admin without audit
deleteStudent($studentId);
// ✅ Super admin WITH audit
auditLog('ADMIN_DELETE_STUDENT', ['admin_user_id' => getSession('user_id'), ...]);
deleteStudent($studentId);
# RESTful conventions (all tenant-scoped)
GET /api/v1/orders → List (tenant-scoped)
POST /api/v1/orders → Create (tenant-scoped)
GET /api/v1/orders/{id} → Show (tenant-scoped, 404 if wrong tenant — not 403)
DELETE /api/v1/orders/{id} → Delete (tenant-scoped)
# Admin (cross-tenant)
GET /api/v1/admin/tenants → List all tenants
POST /api/v1/admin/impersonate → Impersonate (logged)
Response format: {"success": true, "data": {...}, "meta": {"page": 1, "total": 100}}
Error format: {"success": false, "error": {"code": "PERMISSION_DENIED", "message": "..."}}
Always audit: All super_admin actions | Impersonation | Permission changes | Tenant creation/suspension | Data exports | Failed auth attempts | Cross-tenant access attempts
{
"id": "uuid",
"timestamp": "2026-04-07T10:30:00Z",
"actor_user_id": 123,
"actor_type": "super_admin",
"action": "IMPERSONATE_USER",
"target_tenant_id": 456,
"justification": "Support request #12345",
"ip_address": "203.0.113.1",
"changes": {"before": {...}, "after": {...}}
}
Retention: Security logs: 1 year | Audit trails: 7 years | Operational logs: 90 days
Critical alerts:
PENDING → ACTIVE → SUSPENDED → ARCHIVED
franchise_id in ALL franchise-scoped tablessetSession()/getSession()franchise_id extracted from session/JWT — NEVER from client inputSaaS Seeder Specifics: Password: Argon2ID + salt(32 chars) + pepper(64+ chars) | Use super-user-dev.php to create admin users | Collation: utf8mb4_unicode_ci
See Also:
references/database-schema.md — Complete database design, indexes, partitioningreferences/permission-model.md — RBAC implementation, caching, middlewaredocumentation/migration.md — Adding franchise_id, zero-downtime migrationsdata-ai
Use when adding AI-powered analytics to a SaaS platform — semantic search over business data, natural language queries, trend detection, anomaly alerts, and AI-generated insights for dashboards. Covers embeddings, NL2SQL, and per-tenant analytics...
data-ai
Design AI-powered analytics dashboards — what metrics to show, how to display AI predictions and confidence, drill-down patterns, KPI cards, trend visualisation, AI Insights panels, export design, and role-based dashboard variants. Invoke when...
development
Use when designing, building, reviewing, or upgrading production software systems that must be secure, performant, maintainable, scalable, and user-centered. Apply before writing specs, code, architecture, APIs, databases, mobile apps, SaaS platforms, or ERP systems.
development
Professional web app UI using commercial templates (Tabler/Bootstrap 5) with strong frontend design direction when needed. Use for CRUD interfaces, dashboards, admin panels with SweetAlert2, DataTables, Flatpickr. Clone seeder-page.php, use...