Aller au contenu

Configuration security.yaml

Référence de la configuration de sécurité de Kirexo, telle que déclarée dans config/packages/security.yaml et config/routes.yaml. Pour le pourquoi de cette architecture (deux firewalls, masquage 404 de l'admin), voir Architecture des firewalls Kirexo.

Provider d'utilisateurs

Un unique provider Doctrine, partagé par les deux firewalls.

Provider Type Classe Propriété d'identification
app_user_provider entity App\Entity\User email

Firewalls

Déclarés dans l'ordre ci-dessous. Symfony retient le premier firewall dont le pattern correspond à l'URL ; un firewall sans pattern agit comme fallback.

Firewall pattern lazy provider Sécurisé
dev ^/(_(profiler\|wdt)\|css\|images\|js)/ non (security: false)
admin ^/admin oui app_user_provider oui
user (aucun — fallback) oui app_user_provider oui

Options form_login

Les firewalls admin et user utilisent l'authentification par formulaire native de Symfony (form_login). Chaque firewall déclare en plus un authenticator custom SwitchSpaceAuthenticator (voir Authenticators custom ci-dessous). La soumission du formulaire (Live Component email-first du lot 4) cible le check_path de chaque firewall.

Option Firewall admin Firewall user
login_path app_admin_login app_login
check_path app_admin_login_check app_login_check
default_target_path app_admin_dashboard app_user_home
username_parameter email email
password_parameter password password
enable_csrf true true

Après une connexion réussie, chaque firewall redirige vers l'accueil de son espace : /admin (app_admin_dashboard) pour l'admin, /u (app_user_home) pour l'utilisateur. Ces deux pages sont décrites dans la Référence des espaces utilisateur et admin.

username_parameter est fixé à email (et non _username) car l'identification se fait sur l'email, cohérent avec la propriété du provider Doctrine.

Options logout

Option Firewall admin Firewall user Rôle
path app_admin_logout app_logout Route interceptée par le firewall pour déclencher la déconnexion.
target app_home app_home Route de redirection après déconnexion.
clear_site_data ['cookies'] ['cookies'] Émet l'en-tête HTTP Clear-Site-Data: "cookies" à la déconnexion, demandant au navigateur de purger les cookies du site.

clear_site_data : défense en profondeur de la déconnexion globale

clear_site_data: ['cookies'] complète l'effacement ciblé des cookies remember-me réalisé côté serveur par src/Security/EventSubscriber/ClearRememberMeCookiesSubscriber.php. Ce subscriber, branché sur les dispatchers des deux firewalls, efface REMEMBERME_USER et REMEMBERME_ADMIN à chaque LogoutEvent, pour qu'une déconnexion depuis un espace déconnecte aussi l'autre. Le raisonnement complet (bug #69, contextes de sécurité désynchronisés) est détaillé dans Déconnexion cohérente. En HTTP local/test (cookies non secure), Clear-Site-Data reste inoffensif ; la garantie testable côté serveur est l'effacement du subscriber.

Authenticators custom

Les deux firewalls déclarent un custom_authenticator App\Security\Authenticator\SwitchSpaceAuthenticator qui authentifie les arrivées depuis l'autre espace via un jeton de bascule signé à usage unique (?_switch_token). Voir Mécanisme de bascule entre espaces.

Remember-me (« Se souvenir de moi »)

Chaque firewall (admin et user) active le remember_me natif de Symfony. Quand la case « Se souvenir de moi » du formulaire est cochée (paramètre _remember_me), un cookie persistant maintient la session après la fermeture du navigateur. Pour le parcours utilisateur, voir Rester connecté avec « Se souvenir de moi ».

Option Firewall admin Firewall user Rôle
secret %kernel.secret% %kernel.secret% Clé de signature du cookie (dérivée d'APP_SECRET).
lifetime 2592000 (30 j) 2592000 (30 j) Durée de vie du cookie, en secondes.
name REMEMBERME_ADMIN REMEMBERME_USER Nom du cookie — distinct par espace.
path / / Portée du cookie.
secure %env(bool:REMEMBER_ME_SECURE)% %env(bool:REMEMBER_ME_SECURE)% Cookie transmis sur HTTPS uniquement quand true (cf. REMEMBER_ME_SECURE).
httponly true true Cookie inaccessible au JavaScript.
samesite lax lax Non envoyé sur les requêtes cross-site, hors navigation top-level.
always_remember_me false false Opt-in : le cookie n'est posé que si la case est cochée.
signature_properties ['password'] ['password'] Le hash du mot de passe entre dans la signature du cookie.

signature_properties: ['password'] — invalidation au reset

Inclure password dans signature_properties lie la validité du cookie au hash courant du mot de passe. Toute modification du mot de passe (réinitialisation, cf. Réinitialiser son mot de passe) change ce hash et invalide automatiquement tous les cookies « se souvenir de moi » émis avant — sur tous les appareils. Aucune liste de cookies à révoquer côté serveur n'est nécessaire.

Rate limiter (login throttling)

Chaque firewall active login_throttling : après trop de tentatives de connexion échouées sur un même couple (IP + identifiant), l'accès est temporairement bloqué. Une connexion réussie remet le compteur à zéro. Pour le pourquoi (brute-force, credential stuffing, admin plus strict), voir Pourquoi limiter les tentatives de connexion.

Option Firewall admin Firewall user
max_attempts 3 5
interval 15 minutes 15 minutes

Symfony génère à partir de cette configuration le rate limiter interne security.login_throttling, indexé par IP + identifiant.

Limiteurs nommés exposés en parallèle

config/packages/rate_limiter.yaml déclare en plus deux limiteurs nommés login_user_throttle (limit 5) et login_admin_throttle (limit 3), en policy sliding_window sur le pool cache.app. Le rate limiting effectif des logins reste celui généré par login_throttling ci-dessus ; ces définitions nommées existent pour qu'un service ou un composant UX puisse consulter le compteur (ex. afficher les essais restants) sans réimplémenter le décompte.

Routes de sécurité

Déclarées dans config/routes.yaml, sauf les deux routes de login GET qui sont déclarées en attribut #[Route] sur leur controller (chargées via le resource ../src/Controller/).

Route Chemin Controller Méthode
app_login /login src/Controller/AppLoginController.php GET
app_admin_login /admin/login src/Controller/AppAdminLoginController.php GET
app_login_check /login_check (aucun)
app_admin_login_check /admin/login_check (aucun)
app_logout /logout (aucun)
app_admin_logout /admin/logout (aucun)

Routes sans controller

app_login_check, app_admin_login_check, app_logout et app_admin_logout n'ont aucune action de controller. Elles existent uniquement pour être référencées par form_login (check_path) et logout (path) ; le firewall les intercepte avant qu'un controller ne soit atteint. Déclarer ces routes en YAML sans controller est la convention Symfony pour ce cas.

access_control

Règles évaluées dans l'ordre ; Symfony applique la première dont le path correspond.

Ordre path roles
1 ^/admin/login PUBLIC_ACCESS
2 ^/admin ROLE_ADMIN
3 ^/u ROLE_USER
4 ^/login PUBLIC_ACCESS

L'ordre des deux premières règles est critique : ^/admin/login (public) doit précéder ^/admin (ROLE_ADMIN), sinon la page de login admin exigerait d'être déjà admin pour s'afficher. Détails dans l'explication associée.

access_control ne masque pas l'admin

access_control gère l'autorisation (un rôle a-t-il le droit ?), pas le masquage (l'admin avoue-t-il son existence ?). Le 404 servi aux visiteurs de l'espace user sur ^/admin est porté par src/EventSubscriber/AdminAreaAccessSubscriber.php, indépendamment de access_control. Voir Masquer l'admin : 404 plutôt que 403.

Surcharge when@test

En environnement de test, security.yaml affaiblit volontairement le hashage des mots de passe pour accélérer la suite de tests :

Paramètre Valeur test
cost 4
time_cost 3
memory_cost 10

Ces valeurs n'ont aucun effet en dev ni en prod, où le hashage reste sur auto.