Skip to Content
SymfonyPartie 10 — Sécurité & authentification10.1 — Firewalls & authenticators

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 pattern d’URL). On en a plusieurs : main (back-office, session + form_login) et api (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 (ou null).
  • 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 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: truepas 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, avec form_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 avant api, il capterait aussi /api et le JWT ne s’appliquerait jamais. Placez les firewalls spécifiques (api) avant le firewall « attrape-tout » (main). Idem, mettez dev en 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écanismeUsage
form_loginformulaire HTML classique (back-office)
json_loginlogin par JSON (renvoie souvent un JWT)
http_basicBasic Auth (API simples, outils internes)
remember_mecookie « se souvenir de moi »
authenticator customlogique 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.yaml avec anonymous, guard, etc. sont supprimés. Si vos souvenirs de Symfony 5 incluent des Guard authenticators ou anonymous: true, c’est périmé : place aux authenticators modernes et au Passport.

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\Security et 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).