| name | multitenant |
| description | Architecture multitenant avec approche tiered (Shared/Dedicated Schema/DB), RBAC/ABAC, field-level encryption. Use when working with multitenant applications, tenant isolation, data segregation. |
| triggers | {"files":["**/TenantFilter.php","**/TenantScope.php","**/middleware/Tenant*"],"keywords":["multitenant","multi-tenant","tenant","isolation","tenant_id","schema","RBAC","ABAC","field-level encryption"]} |
| auto_suggest | true |
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
- Approche Tiered
- Tier 1 : Shared Schema
- Tier 2 : Dedicated Schema
- Tier 3 : Dedicated Database
- RBAC/ABAC par tenant
- Field-Level Encryption
- Sécurité et validation
- 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) :
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 :
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) :
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 :
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_prod
Avantages
- ✅ 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) :
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 :
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 :
class DocumentVoter extends Voter
{
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if ($subject->getTenantId() !== $user->getTenantId()) {
return false;
}
return match ($attribute) {
'DOCUMENT_EDIT' => in_array('ROLE_EDITOR', $user->getRoles()),
'DOCUMENT_DELETE' => in_array('ROLE_ADMIN', $user->getRoles()),
default => false,
};
}
}
Laravel (Spatie Permission) :
$user->givePermissionTo('edit-documents', 'tenant', $tenantId);
if ($user->hasPermissionTo('edit-documents', 'tenant', $tenantId)) {
}
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) :
protected $casts = [
'credit_card' => Encrypted::class,
];
$user->credit_card = '1234-5678-9012-3456';
echo $user->credit_card;
Rotation des clés : Prévoir une stratégie de rotation annuelle des clés de chiffrement.
Sécurité et validation
Checklist sécurité multitenant
Tests d'isolation
Exemple :
public function testTenantIsolation(): void
{
$tenantA = $this->createTenant('acme');
$tenantB = $this->createTenant('beta');
$documentA = $this->createDocument($tenantA, 'Secret Acme');
$documentB = $this->createDocument($tenantB, 'Secret Beta');
$this->loginAs($tenantA->getUser());
$response = $this->get("/documents/{$documentB->getId()}");
$this->assertResponseStatusCodeSame(403);
}
Checklist
Nouveau projet multitenant
Migration vers Tier supérieur
Ressources
Date de dernière mise à jour : 2026-04
Version : 1.0.0
Auteur : The Bearded CTO