
Multitenant
- 113 installs
- 99 repo stars
- Updated August 4, 2026
- thebeardedbearsas/claude-craft
Helps with ai & agent building tasks.
About
multitenant is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- multitenant
- AI & Agent Building
- AI-coding skill
Multitenant by the numbers
- 113 all-time installs (skills.sh)
- Ranked #3,984 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thebeardedbearsas/claude-craft --skill multitenantAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 113 |
|---|---|
| repo stars | ★ 99 |
| Last updated | August 4, 2026 |
| Repository | thebeardedbearsas/claude-craft ↗ |
What it does
Helps with ai & agent building tasks.
Files
Multitenant — Quick Reference
Servir plusieurs clients (tenants) sur la même base de code avec isolation stricte et un coût d'infra contrôlé.
Trois tiers d'isolation
| Tier | Isolation | Coût | Cas d'usage |
|---|---|---|---|
| Tier 1 — Shared schema | colonne tenant_id partout, filtres SQL automatiques | Faible | Startups, free / petits clients |
| Tier 2 — Dedicated schema | un schéma PostgreSQL par tenant | Moyen | SMB, clients exigeants |
| Tier 3 — Dedicated DB | une base entière par tenant | Élevé | Enterprise, compliance stricte (HDS, FedRAMP) |
Règle de migration : commencer Tier 1, migrer un client en Tier 2/3 quand il représente > 20 % du revenu OU exige un SLA spécifique.
Cinq invariants non-négociables
1. `tenant_id` propagé à chaque requête (AsyncLocalStorage / SecurityContext / middleware). 2. PostgreSQL Row-Level Security (RLS) activé sur TOUTES les tables. Filet de sécurité contre un oubli applicatif. 3. Tests d'isolation obligatoires. Tenant A ne doit jamais lire/écrire les données de B — y compris via tri, requête nuée, agrégat. 4. Audit trail isolé par tenant. Pas de log multi-tenant cross-référencé sans permission explicite. 5. Field-level encryption sur PII / secrets sensibles (Halite PHP, Eloquent Casts, libsodium).
Pattern minimal — Shared schema + RLS
ALTER TABLE invoices ADD COLUMN tenant_id UUID NOT NULL;
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON invoices
USING (tenant_id = current_setting('app.tenant_id')::uuid);// Symfony — middleware qui set la variable session pour RLS
$conn->executeStatement(
'SET LOCAL app.tenant_id = :tid',
['tid' => $tenantId]
);Anti-patterns critiques
- ❌ Oublier le filtre
tenant_iddans une requête raw → fuite cross-tenant. - ❌ Cache Redis sans préfixe tenant → données de A retournées à B.
- ❌ Job worker async qui perd le
tenant_id→ impossible de retrouver le contexte. - ❌ Signed URLs / tokens sans
tenant_iddans le payload → utilisable cross-tenant. - ❌ Field encryption avec une clé unique partagée → compromission = exposition totale (préférer keys per tenant).
RBAC / ABAC
- RBAC : rôles globaux (
admin,member,viewer) suffisent pour 80 % des cas. - ABAC : passer à des policies (Casbin, Cerbos, OPA) quand les règles dépendent d'attributs (région, montant, statut).
Pour aller plus loin
Patterns détaillés par tier, migration tier 1 → tier 2 sans downtime, tests d'isolation (Pest + tenant fixtures), RBAC/ABAC, exemples Laravel + Symfony, checklists par phase : voir @.claude/skills/multitenant/REFERENCE.md.Multitenant — Isolation et Sécurité
Vue d'ensemble
L'architecture multitenant permet de servir plusieurs clients (tenants) avec une seule instance applicative. L'isolation et la sécurité sont critiques.
Principes :
- ✅ Isolation stricte des données entre tenants
- ✅ Choix du tier d'isolation selon le profil client
- ✅ RBAC/ABAC par tenant pour le contrôle d'accès fin
- ✅ Chiffrement field-level pour données sensibles
- ✅ Protection contre les fuites inter-tenants
---
Table des matières
1. Approche Tiered 2. Tier 1 : Shared Schema 3. Tier 2 : Dedicated Schema 4. Tier 3 : Dedicated Database 5. RBAC/ABAC par tenant 6. Field-Level Encryption 7. Sécurité et validation 8. Checklist
---
Approche Tiered
Source : Multi-Tenant Architecture Guide
Une approche tiered permet d'adapter le niveau d'isolation au profil du tenant (startup, SMB, enterprise).
| Tier | Isolation | Coût | Complexité | Cas d'usage |
|---|---|---|---|---|
| Tier 1 | Shared schema avec tenant_id | Faible | Faible | Startups, petits clients |
| Tier 2 | Dedicated schema par tenant | Moyen | Moyen | SMB, clients sensibles |
| Tier 3 | Dedicated DB par tenant | Élevé | Élevé | Enterprise, compliance stricte |
Migration inter-tier
Les tenants peuvent migrer d'un tier à l'autre selon leur croissance :
Startup (Tier 1)
↓ Croissance
SMB (Tier 2)
↓ Enterprise
Enterprise (Tier 3)Stratégie : Prévoir des scripts de migration automatisés entre tiers.
---
Tier 1 : Shared Schema
Principe
Tous les tenants partagent la même base de données et le même schéma. Isolation via colonne tenant_id.
Avantages
- ✅ Faible coût (une seule DB)
- ✅ Maintenance simplifiée
- ✅ Scaling horizontal facile
Inconvénients
- ❌ Risque de fuite de données (SQL injection, bugs)
- ❌ Performance partagée (un tenant peut impacter les autres)
- ❌ Compliance difficile (GDPR, HIPAA)
Implémentation
Symfony (Doctrine) :
// Utiliser un filtre global Doctrine
class TenantFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
{
if (!$targetEntity->reflClass->implementsInterface(TenantAwareInterface::class)) {
return '';
}
return sprintf('%s.tenant_id = %s', $targetTableAlias, $this->getParameter('tenant_id'));
}
}Laravel :
// Global scope
protected static function booted()
{
static::addGlobalScope('tenant', function (Builder $builder) {
$builder->where('tenant_id', auth()->user()->tenant_id);
});
}Validation obligatoire : Chaque requête SQL DOIT inclure WHERE tenant_id = :current_tenant.
---
Tier 2 : Dedicated Schema
Principe
Chaque tenant possède son propre schéma dans une base de données partagée.
Exemple PostgreSQL :
database: app_prod
├── schema: tenant_acme (tables pour Acme Corp)
├── schema: tenant_beta (tables pour Beta Inc)
└── schema: tenant_gamma (tables pour Gamma LLC)Avantages
- ✅ Meilleure isolation (pas de risque de fuite par
tenant_id) - ✅ Performance isolée par schéma
- ✅ Backup/restore par tenant possible
Inconvénients
- ❌ Limite de schémas par DB (PostgreSQL : ~10 000 max)
- ❌ Migrations plus complexes (itérer sur tous les schémas)
Implémentation
Symfony (Doctrine) :
// Middleware pour switcher de schéma
class TenantSchemaMiddleware
{
public function process(Request $request, RequestHandler $handler): Response
{
$tenantId = $this->resolveTenant($request);
$schemaName = "tenant_{$tenantId}";
$this->connection->executeStatement("SET search_path TO {$schemaName}");
return $handler->handle($request);
}
}Laravel :
// Dynamic connection
DB::purge('tenant');
Config::set('database.connections.tenant.schema', "tenant_{$tenantId}");
DB::reconnect('tenant');---
Tier 3 : Dedicated Database
Principe
Chaque tenant possède sa propre base de données physique.
Exemple :
database: tenant_acme_prod
database: tenant_beta_prod
database: tenant_gamma_prodAvantages
- ✅ Isolation maximale (compliance GDPR, HIPAA)
- ✅ Scaling indépendant par tenant
- ✅ Backup/restore/migration par tenant
Inconvénients
- ❌ Coût élevé (serveurs DB multiples)
- ❌ Maintenance complexe (migrations, monitoring)
Implémentation
Symfony (Doctrine) :
// Connection pooling
class TenantConnectionFactory
{
public function getConnection(string $tenantId): Connection
{
$config = [
'dbname' => "tenant_{$tenantId}_prod",
'user' => 'app_user',
'password' => getenv('DB_PASSWORD'),
'host' => "tenant-{$tenantId}.db.example.com",
];
return DriverManager::getConnection($config);
}
}Laravel :
// Multi-database config
Config::set("database.connections.tenant_{$tenantId}", [
'driver' => 'pgsql',
'host' => "tenant-{$tenantId}.db.example.com",
'database' => "tenant_{$tenantId}_prod",
]);
DB::connection("tenant_{$tenantId}");---
RBAC/ABAC par tenant
Principe
Au-delà de l'isolation par tenant_id, implémenter un contrôle d'accès fin (Role-Based / Attribute-Based).
| Modèle | Description | Exemple |
|---|---|---|
| RBAC | Permissions par rôle | admin, editor, viewer |
| ABAC | Permissions par attributs | can_edit_if_owner, can_view_if_same_department |
Implémentation RBAC
Symfony :
// Voter personnalisé
class DocumentVoter extends Voter
{
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
$user = $token->getUser();
// Vérifier tenant_id
if ($subject->getTenantId() !== $user->getTenantId()) {
return false;
}
// Vérifier rôle
return match ($attribute) {
'DOCUMENT_EDIT' => in_array('ROLE_EDITOR', $user->getRoles()),
'DOCUMENT_DELETE' => in_array('ROLE_ADMIN', $user->getRoles()),
default => false,
};
}
}Laravel (Spatie Permission) :
// Permissions scope par tenant
$user->givePermissionTo('edit-documents', 'tenant', $tenantId);
if ($user->hasPermissionTo('edit-documents', 'tenant', $tenantId)) {
// Autoriser
}---
Field-Level Encryption
Principe
Chiffrer les champs sensibles (numéro de carte bancaire, données médicales) pour protéger contre les fuites DB.
Règle : Même si un attaquant accède à la DB, les données sensibles restent illisibles.
Implémentation
Symfony (ParagonIE Halite) :
use ParagonIE\Halite\Symmetric\Crypto;
use ParagonIE\Halite\KeyFactory;
class EncryptedField
{
private EncryptionKey $key;
public function __construct()
{
$this->key = KeyFactory::loadEncryptionKey('/secure/path/tenant.key');
}
public function encrypt(string $plaintext): string
{
return Crypto::encrypt(new HiddenString($plaintext), $this->key);
}
public function decrypt(string $ciphertext): string
{
return Crypto::decrypt($ciphertext, $this->key)->getString();
}
}Laravel (Eloquent Casting) :
// Model
protected $casts = [
'credit_card' => Encrypted::class,
];
// Automatiquement chiffré/déchiffré
$user->credit_card = '1234-5678-9012-3456'; // Chiffré en DB
echo $user->credit_card; // Déchiffré automatiquementRotation des clés : Prévoir une stratégie de rotation annuelle des clés de chiffrement.
---
Sécurité et validation
Checklist sécurité multitenant
- [ ] Résolution tenant : Valider le tenant à chaque requête (header, sous-domaine, JWT claim)
- [ ] Isolation SQL : Tous les SELECT incluent
tenant_idou schéma dédié - [ ] Tests d'isolation : Test unitaire vérifiant qu'un tenant ne peut pas accéder aux données d'un autre
- [ ] Audit logs : Logger tous les accès inter-tenants suspects
- [ ] Rate limiting : Par tenant (éviter qu'un tenant consomme toutes les ressources)
- [ ] RBAC/ABAC : Permissions fines au-delà de
tenant_id - [ ] Chiffrement : Field-level pour données sensibles
Tests d'isolation
Exemple :
// Test Symfony
public function testTenantIsolation(): void
{
$tenantA = $this->createTenant('acme');
$tenantB = $this->createTenant('beta');
$documentA = $this->createDocument($tenantA, 'Secret Acme');
$documentB = $this->createDocument($tenantB, 'Secret Beta');
// User de tenantA ne peut pas voir documentB
$this->loginAs($tenantA->getUser());
$response = $this->get("/documents/{$documentB->getId()}");
$this->assertResponseStatusCodeSame(403);
}---
Checklist
Nouveau projet multitenant
- [ ] Choisir le tier d'isolation par défaut (Tier 1 pour MVP)
- [ ] Configurer résolution tenant (header
X-Tenant-ID, sous-domaine, JWT) - [ ] Implémenter filtre global SQL (
tenant_id) - [ ] Tests d'isolation (un tenant ne voit pas les données d'un autre)
- [ ] RBAC/ABAC par tenant
- [ ] Field-level encryption pour données sensibles
- [ ] Migration script Tier 1 → Tier 2 → Tier 3
Migration vers Tier supérieur
- [ ] Script de migration des données
- [ ] Tests de non-régression
- [ ] Backup avant migration
- [ ] Rollback plan
---
Ressources
- Multi-Tenant Architecture Guide : gainhq.com/blog/multi-tenant-architecture
- OWASP Multitenant : owasp.org/www-project-web-security-testing-guide
- Symfony Multi-Tenancy : symfony.com/doc/current/doctrine.html#multiple-entity-managers
- Laravel Multi-Tenancy : tenancyforlaravel.com
---
Date de dernière mise à jour : 2026-04 Version : 1.0.0 Auteur : The Bearded CTO