La sécurité n'est pas un luxe réservé aux grandes équipes. Avec Symfony, 80 % de la protection est déjà intégrée au framework. Encore faut-il l'activer correctement. Voici une checklist rapide basée sur l'OWASP Top 10 — chaque point est vérifiable en quelques minutes.
A01 — Broken Access Control : les Voters
Symfony protège les routes avec access_control dans security.yaml. Mais le contrôle d'accès granulaire — « cet utilisateur peut-il modifier cette ressource ? » — nécessite des Voters.
<?php
declare(strict_types=1);
// Dans le controller : une seule ligne
#[Route('/{id}', methods: ['DELETE'])]
public function delete(User $user): Response
{
$this->denyAccessUnlessGranted('USER_DELETE', $user);
// Si le Voter refuse, Symfony renvoie automatiquement un 403
}
// Le Voter centralise la logique d'accès
final class UserVoter extends Voter
{
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$currentUser = $token->getUser();
return match ($attribute) {
'USER_DELETE' => $this->security->isGranted('ROLE_ADMIN')
&& !$subject->id()->equals($currentUser->id()),
default => false,
};
}
}A02 — Cryptographic Failures : password hashing
Symfony gère le hashing automatiquement via le composant Security. Il suffit de configurer l'algorithme :
# config/packages/security.yaml
security:
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: auto # Utilise bcrypt ou argon2 selon la plateforme
cost: 15 # Minimum recommandé (bcrypt)
# Forcer HTTPS partout
access_control:
- { path: ^/, roles: PUBLIC_ACCESS, requires_channel: https }A03 — Injection SQL : Doctrine vous protège (si vous l'utilisez correctement)
Doctrine paramètre automatiquement les requêtes via le QueryBuilder. La seule erreur à éviter : la concaténation de chaînes dans les requêtes DQL ou SQL brutes.
<?php
// INTERDIT — injection SQL possible
$dql = "SELECT u FROM User u WHERE u.email = '$email'";
// OBLIGATOIRE — paramètre sécurisé
$qb = $this->createQueryBuilder('u')
->where('u.email = :email')
->setParameter('email', $email->toString())
->getQuery()
->getOneOrNullResult();A04 — Insecure Design : validation des inputs
Chaque donnée entrante doit être validée avec le composant Validator de Symfony. Utilisez des DTOs avec des contraintes PHP 8 Attributes :
Besoin d'un expert Symfony ?
Réserver un appel →<?php
declare(strict_types=1);
use Symfony\Component\Validator\Constraints as Assert;
final readonly class CreateUserDTO
{
public function __construct(
#[Assert\NotBlank]
#[Assert\Email(mode: 'strict')]
#[Assert\Length(max: 180)]
public string $email,
#[Assert\NotBlank]
#[Assert\Length(min: 12, max: 4096)]
#[Assert\NotCompromisedPassword]
#[Assert\PasswordStrength(minScore: Assert\PasswordStrength::STRENGTH_STRONG)]
public string $password,
) {}
}A05 — Security Misconfiguration : headers et sessions
Ajoutez un event listener qui injecte les headers de sécurité sur chaque réponse :
<?php
declare(strict_types=1);
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
#[AsEventListener(event: 'kernel.response')]
final readonly class SecurityHeadersListener
{
public function __invoke(ResponseEvent $event): void
{
if (!$event->isMainRequest()) { return; }
$headers = $event->getResponse()->headers;
$headers->set('X-Frame-Options', 'DENY');
$headers->set('X-Content-Type-Options', 'nosniff');
$headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
$headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
$headers->set('Content-Security-Policy', "default-src 'self'");
}
}A07 — Authentication Failures : rate limiting
Symfony intègre un composant RateLimiter depuis la version 5.2. Configurez-le pour protéger vos endpoints sensibles :
# config/packages/framework.yaml
framework:
rate_limiter:
login:
policy: 'sliding_window'
limit: 5
interval: '15 minutes'
api:
policy: 'token_bucket'
limit: 1000
rate: { interval: '1 hour' }La checklist complète en 30 minutes
- Vérifier que tous les endpoints sensibles ont un Voter (5 min)
- Vérifier security.yaml : password_hashers, HTTPS, CSRF (3 min)
- Rechercher les requêtes SQL/DQL sans setParameter (5 min)
- Vérifier que chaque DTO entrant a des contraintes Assert (5 min)
- Ajouter le SecurityHeadersListener si absent (3 min)
- Configurer le rate limiting sur login et API (3 min)
- Lancer
composer auditpour les CVE (1 min) - Vérifier les sessions : cookie_secure, cookie_httponly, cookie_samesite (2 min)
- Vérifier le logging sécurité dans Monolog (3 min)
Cette checklist couvre les fondamentaux. Pour un audit de sécurité complet avec scoring et roadmap chiffrée, notre service Bear Scan analyse votre codebase sur les 10 risques OWASP avec des recommandations actionnables. En savoir plus.
Besoin d'un expert Symfony ?
20 ans d'expérience sur l'écosystème PHP/Symfony.