Le sudo mode est un mécanisme qui redemande ses identifiants à l'utilisateur avant une action sensible, même s'il est déjà connecté. Symfony 8.2 l'intègre nativement via deux attributs, IS_AUTHENTICATED_RECENTLY et IS_AUTHENTICATED_VERY_RECENTLY. Présenté le 30 septembre 2026 sur le blog officiel, contribution de Nicolas Grekas (PR #66014 et suivantes), il arrive avec Symfony 8.2, attendu en novembre 2026. La PR répond à une issue ouverte depuis sept ans (#33955).
IS_AUTHENTICATED_FULLY indique comment la session a démarré, pas quand. Une session ouverte avec un mot de passe à 9 h reste « fully authenticated » à 17 h, et reste valide pour quiconque se retrouve devant l'écran. Poste laissé déverrouillé dans un open space, cookie de session récupéré par une extension malveillante : dans ces cas, l'attaquant n'a aucun mot de passe à trouver. Il lui suffit d'appeler le bon endpoint pour changer l'adresse e-mail, valider un paiement ou supprimer le compte.
Pourquoi IS_AUTHENTICATED_FULLY ne suffit pas pour les actions sensibles
Jusqu'ici, la parade se bricolait à la main : un timestamp de confirmation stocké en session, comparé dans chaque contrôleur sensible, ou un voter maison. Chaque équipe réinventait le mécanisme, avec ses variantes et ses oublis. Et raccourcir la durée de session ne remplace pas ce contrôle : comme le note la PR, cela change le moment où la session meurt, pas la date à laquelle l'utilisateur a prouvé qu'il détient ses identifiants. GitHub résout ce problème avec un « sudo mode » ; Symfony 8.2 en fait une primitive du framework.
IS_AUTHENTICATED_RECENTLY et IS_AUTHENTICATED_VERY_RECENTLY : quelle différence ?
IS_AUTHENTICATED_FULLY: la session a démarré par un vrai login, à n’importe quel moment.IS_AUTHENTICATED_RECENTLY: l’utilisateur a saisi ses identifiants il y a moins de deux heures.IS_AUTHENTICATED_VERY_RECENTLY: il les a saisis il y a moins de cinq minutes.
L'échelle est stricte : être « fully authenticated » n'accorde pas RECENTLY. Le découpage suit le risque de l'action. Exemple du billet : changer d'adresse e-mail tolère une authentification récente, supprimer un compte exige une authentification très récente.
// src/Controller/AccountController.php
use Symfony\Component\Security\Http\Attribute\IsGranted;
// ...
#[IsGranted('IS_AUTHENTICATED_RECENTLY')]
public function changeEmail(): Response
{
// ...
}
#[IsGranted('IS_AUTHENTICATED_VERY_RECENTLY')]
public function deleteAccount(): Response
{
// ...
}Ces attributs s'utilisent partout où tu utilises déjà les autres IS_AUTHENTICATED_*, y compris dans access_control et dans les templates Twig. Les expressions de sécurité gagnent deux fonctions : is_recently_authenticated() et is_very_recently_authenticated(). Le défaut de deux heures est celui de GitHub, d'après la PR ; celui de cinq minutes doit couvrir une page de confirmation et l'action qui suit, sans être confondu avec une session.
Comment régler les durées avec recent_authentication_lifetime
Les deux fenêtres se règlent en secondes ; les défauts sont 7 200 secondes pour recent_authentication_lifetime et 300 secondes pour very_recent_authentication_lifetime :
# config/packages/security.yaml
security:
recent_authentication_lifetime: 3_600 # default: 7_200
very_recent_authentication_lifetime: 60 # default: 300Les défauts sont raisonnables pour un SaaS standard. Pour un back-office financier, descends la première valeur. Dans tous les cas, choisis les durées en fonction de l'action protégée, pas de la gêne ressentie par l'utilisateur.
Le remember-me passe-t-il le sudo mode ?
Non, jamais. Un utilisateur connecté uniquement par cookie remember-me ne passe aucun de ces contrôles : il doit se reconnecter, car ce cookie peut servir à toute personne ayant accès au navigateur. La PR détaille le mécanisme : un listener enregistre le moment de chaque authentification interactive sur le token, et le voter refuse tout token sans tampon ou issu d'un remember-me. C'est un échec fermé, le seul comportement qui ait du sens ici. Pour le reste du durcissement autour du remember-me, du logout et du CSRF, voir notre article sur la sécurité de l'authentification Symfony en 2026.
Deux bonus pratiques. Le voter renvoie désormais la raison précise du refus, visible dans le profiler, au lieu du message générique « not appropriately authenticated ». Et le calcul passe par le service clock : tu peux figer le temps avec MockClock dans tes tests au lieu d'écrire des sleep().
Rediriger vers une page de confirmation avec ReAuthenticationEntryPointInterface
Par défaut, un attribut refusé renvoie une 403. Pour l'utilisateur légitime dont la session a simplement vieilli, c'est une impasse. Le bon comportement : lui redemander son mot de passe, puis le ramener là où il en était. Symfony 8.2 introduit pour cela ReAuthenticationEntryPointInterface :
Votre dette technique s'accumule ?
Audit complet en 10 jours. Recommandations priorisées et actionnables.
// src/Security/ConfirmPasswordEntryPoint.php
namespace App\Security;
use Symfony\Component\Security\Http\EntryPoint\ReAuthenticationEntryPointInterface;
// ...
final class ConfirmPasswordEntryPoint implements ReAuthenticationEntryPointInterface
{
public function __construct(
private UrlGeneratorInterface $urlGenerator,
) {
}
public function startReAuthentication(Request $request, TokenInterface $token): Response
{
// this page displays a password form that submits to the form_login check_path
return new RedirectResponse($this->urlGenerator->generate('app_confirm_password'));
}
}Tu l’enregistres ensuite dans le firewall avec la nouvelle option re_authentication_entry_point :
# config/packages/security.yaml
security:
firewalls:
main:
form_login:
# ...
re_authentication_entry_point: App\Security\ConfirmPasswordEntryPointLa PR #66016 explique pourquoi une interface dédiée plutôt qu'un entry point classique. Un entry point répond à « qui es-tu ? », alors qu'ici l'utilisateur est connu et il ne manque qu'une preuve fraîche. Pointer l'option vers login_path créerait une boucle : la page de login renvoie un utilisateur déjà connecté vers l'accueil. Le typage sur l'interface rend ces configurations impossibles. Autre détail : la ré-authentification ne se déclenche que si l'exception porte exactement l'attribut sudo. Un refus sur [ROLE_ADMIN, IS_AUTHENTICATED_RECENTLY] n'envoie pas l'utilisateur se reconnecter, puisque cela ne l'aiderait pas s'il lui manque le rôle.
Sudo mode avec OpenID Connect : prompt=login et auth_time
Si ton firewall utilise le login OpenID Connect natif de Symfony 8.2, l'option est inutile : son entry point implémente déjà l'interface et renvoie l'utilisateur chez son fournisseur avec prompt=login. Symfony lit en plus le claim auth_time de l'ID token pour dater l'authentification réelle : une reconnexion silencieuse via une vieille session du fournisseur ne compte pas comme une authentification fraîche. Sans cette vérification, le sudo mode serait contournable en un clic. La PR #66015 conseille de configurer max_age sur la même durée que recent_authentication_lifetime, pour que le fournisseur ré-authentifie exactement quand le tampon devient trop vieux.
Personnaliser la règle : clé matérielle et méthodes d’authentification
Parfois, « s'est connecté il y a moins de cinq minutes » ne suffit pas. Une application bancaire peut exiger en plus qu'une clé matérielle ait été utilisée pendant la session. C'est le rôle du trust resolver : étends-le et surcharge isAuthenticatedRecently() ou isAuthenticatedVeryRecently(). Exemple du billet :
// src/Security/BankTrustResolver.php
namespace App\Security;
use Symfony\Component\Security\Core\Authentication\AuthenticationMethod;
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
final class BankTrustResolver extends AuthenticationTrustResolver
{
public function isAuthenticatedVeryRecently(?TokenInterface $token = null): bool
{
if (!$this->isFullFledged($token)) {
return false;
}
// e.g. ['pwd' => 1789140200, 'hwk' => 1789133290]
$proofs = $token->getAuthenticationProofs();
return isset($proofs[AuthenticationMethod::HARDWARE_KEY])
&& time() - max($proofs) <= 300;
}
}Cela repose sur une autre nouveauté de 8.2 : le token garde la trace des méthodes d'authentification utilisées et de la date de chacune, avec les noms de la RFC 8176. C'est le sujet de l'article de demain. Ne personnalise pas par réflexe : pour la plupart des applications, les deux attributs et un entry point suffisent.
Quand l’utiliser, et ce que le sudo mode ne couvre pas
- À protéger : changement d’e-mail ou de mot de passe, suppression de compte, export de données, paiement ou moyen de paiement, gestion des clés d’API.
- Identifiant compromis : un attaquant qui détient déjà le mot de passe passe le contrôle. Le sudo mode limite l’exploitation d’une session volée, pas d’un mot de passe volé ; il ne remplace pas un second facteur.
- API stateless : sans authentification interactive tamponnée, ces attributs ne sont jamais accordés. Il te faudra un équivalent, comme un token à courte durée pour les opérations sensibles.
- Sessions existantes : une session ouverte avant le déploiement n’a pas de tampon et sera refusée jusqu’à une nouvelle connexion (déduit du fonctionnement en échec fermé).
- Resolver maison : les nouvelles méthodes sont annoncées en
@methoden 8.2 et rejoignent l’interface en 9.0 ; un resolver custom qui ne les implémente pas refuse l’accès.
Le sudo mode est une couche parmi d'autres. Pour le reste de la surface d'attaque, notre checklist OWASP pour sécuriser une application Symfony en 30 minutes reste un bon point de départ.
Ma lecture
Toute action destructive ou touchant aux identifiants devrait exiger une authentification récente, et une 403 sur une session qui a simplement vieilli n'est pas une réponse acceptable. Une fois en 8.2 : liste tes actions sensibles, pose IS_AUTHENTICATED_RECENTLY sur les modifications d'identité et IS_AUTHENTICATED_VERY_RECENTLY sur les actions irréversibles, écris l'entry point qui ramène l'utilisateur à sa page, ajuste les durées, et teste avec MockClock. Le coût est faible ; le gain est de fermer la fenêtre où un poste déverrouillé permet de tout faire.
Avant même la 8.2, la question à se poser est simple : quelles actions de mon application restent possibles avec une session ouverte depuis huit heures, ou avec un simple remember-me ? Dans un Bear Scan, on relit précisément ce genre de chemin sensible (contrôles d'accès, réauthentification, périmètre des rôles) et on te le rend sous forme de recommandations priorisées, en 10 jours.

Écrit par
Flavien Métivier
Expert PHP/Symfony indépendant depuis 2014, ancien CTO de Kartable et d'Extrabat. Plus de 80 projets menés, de l'audit à la migration.
Mon parcours