Chapitre 10.1 — Firewalls & authenticators
Partie 10 : Sécurité — Chapitre 1/6 Version de référence : Symfony 7.4 (composant Security, système authenticator).
⏱️ TL;DR
- Authentification = « qui es-tu ? » ; autorisation = « as-tu le droit ? ». Deux étapes distinctes.
- La config vit dans
config/packages/security.yaml: providers (où trouver les utilisateurs), firewalls (zones + stratégie d’auth), access_control (règles d’accès par chemin).- Un firewall protège une zone (matchée par un
patternd’URL). On en a plusieurs :main(back-office, session + form_login) etapi(stateless + JWT).- Les authenticators (système moderne, depuis Symfony 5.3) définissent comment on s’authentifie :
form_login,json_login,http_basic, ou un authenticator custom.- Le firewall agit tôt dans le pipeline (
kernel.request, Partie 4) : l’utilisateur est identifié avant le contrôleur.- Pour un dev Next.js : le firewall ≈ un middleware d’auth appliqué par zone d’URL, mais déclaratif et avec des stratégies interchangeables (session vs JWT).
🎯 Objectifs
- Distinguer authentification et autorisation.
- Lire et structurer
security.yaml(providers, firewalls, access_control). - Configurer plusieurs firewalls (session vs stateless).
- Comprendre le rôle des authenticators.
1. Authentification vs autorisation
Deux questions séquentielles :
- Authentification : établir l’identité (session, JWT, OAuth…). Résultat : un
User(ounull). - Autorisation : décider si cet utilisateur peut effectuer l’action (rôles, voters — chapitre 10.2).
Ce chapitre couvre surtout l’authentification et sa configuration.
2. La structure de security.yaml
# config/packages/security.yaml
security:
# (a) comment hacher les mots de passe
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# (b) où trouver les utilisateurs
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email # on identifie par email
# (c) les zones protégées et leur stratégie
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false # pas de sécurité sur les assets/profiler
api:
pattern: ^/api
stateless: true # pas de session → JWT (chapitre 10.4)
jwt: ~ # authenticator JWT
main:
lazy: true
provider: app_user_provider
form_login: # back-office : formulaire de connexion
login_path: app_login
check_path: app_login
logout:
path: app_logout
# (d) règles d'accès par chemin
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/api/bookings, roles: ROLE_USER }
- { path: ^/login, roles: PUBLIC_ACCESS }Quatre sections : hachage (a), providers (b), firewalls (c), access_control (d).
3. Le provider : où sont les utilisateurs
Le provider dit à Symfony où trouver les utilisateurs et par quelle propriété les identifier. Le plus courant : un entity provider sur votre entité User, avec property: email (on se connecte par email). Symfony chargera l’utilisateur correspondant lors de l’authentification.
4. Les firewalls : plusieurs zones, plusieurs stratégies
Un firewall protège une zone de l’application, matchée par un pattern d’URL. L’ordre compte : Symfony prend le premier firewall dont le pattern matche.
Dans BookLab :
dev: désactive la sécurité sur les assets et le profiler (sinon on ne pourrait pas les charger).api(^/api) :stateless: true→ pas de session, chaque requête porte son JWT (chapitre 10.4). C’est la porte du front Next.js.main(le reste) : le back-office, avecform_login(formulaire de connexion) et session (par défaut, stateful).
⚠️ Piège de l’ordre : les firewalls sont testés de haut en bas, et un seul s’applique par requête. Si
main(sans pattern, donc « tout ») était avantapi, il capterait aussi/apiet le JWT ne s’appliquerait jamais. Placez les firewalls spécifiques (api) avant le firewall « attrape-tout » (main). Idem, mettezdeven premier.
5. Les authenticators : comment on s’authentifie
Depuis Symfony 5.3, l’authentification repose sur des authenticators. Un firewall active un ou plusieurs mécanismes :
| Mécanisme | Usage |
|---|---|
form_login | formulaire HTML classique (back-office) |
json_login | login par JSON (renvoie souvent un JWT) |
http_basic | Basic Auth (API simples, outils internes) |
remember_me | cookie « se souvenir de moi » |
| authenticator custom | logique sur mesure (clé d’API, token maison…) |
Un authenticator custom étend AbstractAuthenticator : il décide s’il supporte la requête (supports()), extrait les identifiants, charge l’utilisateur et gère succès/échec. C’est ainsi qu’on implémente une auth par clé d’API, par exemple.
// squelette d'un authenticator custom
final class ApiKeyAuthenticator extends AbstractAuthenticator
{
public function supports(Request $request): ?bool
{
return $request->headers->has('X-API-KEY');
}
public function authenticate(Request $request): Passport
{
$apiKey = $request->headers->get('X-API-KEY');
// ... valider et charger l'utilisateur associé à cette clé
return new SelfValidatingPassport(new UserBadge($userIdentifier));
}
public function onAuthenticationFailure(Request $request, AuthenticationException $e): ?Response
{
return new JsonResponse(['error' => 'Clé invalide'], 401);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
return null; // laisse la requête continuer
}
}⚡ Depuis Symfony 5 : le système d’authentification a été refondu (« authenticator-based », stable depuis 5.3, seul système en 6/7). L’ancien système « Guard » et les vieux
security.yamlavecanonymous,guard, etc. sont supprimés. Si vos souvenirs de Symfony 5 incluent desGuard authenticatorsouanonymous: true, c’est périmé : place aux authenticators modernes et auPassport.
6. Récupérer l’utilisateur courant
Une fois authentifié, on accède à l’utilisateur :
- Contrôleur :
$this->getUser()(ou l’argument#[CurrentUser] User $user, chapitre 4.4). - Service : injecter
Symfony\Bundle\SecurityBundle\Securityet appeler$security->getUser(). - Twig :
app.user(chapitre 7.1).
use Symfony\Bundle\SecurityBundle\Security;
public function __construct(private readonly Security $security) {}
public function currentUserEmail(): ?string
{
return $this->security->getUser()?->getUserIdentifier();
}✏️ Exercices
Exercice 1. Expliquez pourquoi le firewall api doit être déclaré avant le firewall main.
✅ Solution
Les firewalls sont testés de haut en bas et un seul s’applique par requête. main (sans pattern) matche toutes les URLs, y compris /api. S’il était déclaré avant api, il capterait les requêtes d’API et appliquerait sa stratégie (session/form_login) au lieu du JWT stateless. En plaçant api (^/api) avant main, les requêtes d’API sont bien prises par le firewall JWT, et le reste par main.
Exercice 2. Quelle différence de configuration fondamentale entre le firewall du back-office et celui de l’API ?
✅ Solution
Le firewall back-office (main) est stateful : il utilise une session (cookie) et un form_login. Le firewall API est stateless: true : aucune session, chaque requête porte son JWT (Bearer). Conséquence : l’API est scalable sans affinité de session et sans CSRF classique, adaptée à un front découplé.
Exercice 3. Vous voulez authentifier un partenaire via un en-tête X-API-KEY. Quel mécanisme utilisez-vous ?
✅ Solution
Un authenticator custom (étendant AbstractAuthenticator) : supports() renvoie vrai si l’en-tête X-API-KEY est présent, authenticate() valide la clé et charge l’utilisateur/partenaire associé (via un Passport), et onAuthenticationFailure renvoie un 401. On l’active sur le firewall concerné. C’est le remplaçant moderne des anciens « Guard authenticators ».
🧠 Quiz de révision
1. Différence entre authentification et autorisation ?
Authentification = établir l’identité (« qui es-tu ? ») ; autorisation = décider des droits (« as-tu le droit ? »). Séquentielles.
2. Qu’est-ce qu’un firewall ?
Une zone de l’application (matchée par un pattern d’URL) avec sa stratégie d’authentification. Un seul firewall s’applique par requête (ordre important).
3. Quelle est la config clé qui rend un firewall stateless ?
stateless: true : pas de session, chaque requête porte son jeton (JWT) — utilisé pour l’API.
4. Qu’est-ce qu’un authenticator ?
Le composant qui définit comment on s’authentifie (form_login, json_login, http_basic, ou custom via AbstractAuthenticator + Passport).
5. Comment récupère-t-on l’utilisateur courant dans un service ?
En injectant Symfony\Bundle\SecurityBundle\Security et en appelant $security->getUser() (ou #[CurrentUser]/getUser() en contrôleur, app.user en Twig).
Prochain chapitre : 10.2 — Rôles & voters — de l’autorisation grossière (rôles) aux permissions fines (voters).