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.