{"id":320,"date":"2025-12-04T08:53:58","date_gmt":"2025-12-04T12:53:58","guid":{"rendered":"https:\/\/arielnavarrete.cl\/blog\/?p=320"},"modified":"2025-12-04T09:00:58","modified_gmt":"2025-12-04T13:00:58","slug":"custom-message-toomanyloginattemptsauthenticationexception","status":"publish","type":"post","link":"https:\/\/arielnavarrete.cl\/blog\/2025\/12\/04\/custom-message-toomanyloginattemptsauthenticationexception\/","title":{"rendered":"Custom message TooManyLoginAttemptsAuthenticationException"},"content":{"rendered":"\n<p>Desde Symfony 5.2, se puede agregar la opci\u00f3n de <a href=\"https:\/\/symfony.com\/blog\/new-in-symfony-5-2-login-throttling\" data-type=\"link\" data-id=\"https:\/\/symfony.com\/blog\/new-in-symfony-5-2-login-throttling\">login_throttling<\/a>, en resumen, es una protecci\u00f3n contra ataques de fuerza bruta para tu login, el problema es que si quieres personalizar el mensaje de error, es un poco engorroso&#8230;<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>Existe una opci\u00f3n bastante simple para poder hacer esto, antes, puedes revisar la configuraci\u00f3n b\u00e1sica de  login_throttling <a href=\"https:\/\/symfony.com\/doc\/7.4\/security.html#limiting-login-attempts\">aqui<\/a><\/p>\n\n\n\n<p>Un ejemplo seria:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>admin:\n    # ...\n    lazy: true\n    provider: admin\n    login_throttling:\n        max_attempts: 3 # per minute ...\n        interval: '3 minutes'\n    form_login:\n        # ...<\/code><\/pre>\n\n\n\n<p>Cabe destacar aqu\u00ed que:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>max_attempts<\/strong>: Indica cuantas peticiones por minuto permite antes de \u00abbloquear\u00bb, en este caso son 3<\/li>\n\n\n\n<li><strong>interval<\/strong>: Intervalo de tiempo que realiza entre peticiones<\/li>\n<\/ul>\n\n\n\n<p>No olvides revisar si tienes ya esta dependencia:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>composer require symfony\/rate-limiter<\/code><\/pre>\n\n\n\n<p>Ahora, a lo que nos convoca, para personalizar el mensaje que arroja puedes personalizar un evento de suscripci\u00f3n, el c\u00f3digo seria:<\/p>\n\n\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">&lt;?php\n\nnamespace App\\EventSubscriber;\n\nuse Symfony\\Component\\EventDispatcher\\EventSubscriberInterface;\nuse Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException;\nuse Symfony\\Component\\Security\\Core\\Exception\\TooManyLoginAttemptsAuthenticationException;\nuse Symfony\\Component\\Security\\Http\\Event\\LoginFailureEvent; \nuse Symfony\\Component\\Security\\Http\\SecurityRequestAttributes;\nuse Symfony\\Contracts\\Translation\\TranslatorInterface;\n\nclass LoginThrottlingSubscriber implements EventSubscriberInterface\n{\n    public function __construct(private TranslatorInterface $translator) {}\n\n    public static function getSubscribedEvents(): array\n    {\n        return &amp;#91;\n            LoginFailureEvent::class =&gt; &#039;onLoginFailure&#039;,\n        ];\n    }\n\n    public function onLoginFailure(LoginFailureEvent $event): void\n    {\n        $exception = $event-&gt;getException();\n\n        if ($exception instanceof TooManyLoginAttemptsAuthenticationException &amp;&amp; $event-&gt;getRequest()-&gt;hasSession()) {\n\n            $mensaje = $this-&gt;translator-&gt;trans(&quot;errores.tooManyFailed&quot;, $exception-&gt;getMessageData());\n\n            $event-&gt;getRequest()-&gt;getSession()-&gt;set(\n                SecurityRequestAttributes::AUTHENTICATION_ERROR,\n                new AuthenticationException($mensaje)\n            );\n        }\n    }\n}<\/pre>\n\n\n\n<p>Cosas a destacar:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>El mensaje puede ir en duro o como en este caso ocupando translator (opcional), si no ocupas este ultimo, puedes quitar el constructor y la referencia a este<\/li>\n\n\n\n<li>La variable SecurityRequestAttributes::AUTHENTICATION_ERROR, contiene el valor <strong>&#8216;_security.last_error&#8217;, <\/strong>seg\u00fan la versi\u00f3n de Symfony que ocupes puede variar la ruta o el nombre, en este caso es la 7.4<\/li>\n\n\n\n<li>No requiere registrarse en ning\u00fan archivo, symfony lo detecta autom\u00e1ticamente (obviamente debe estar en la ruta que corresponde src\/EventSubscriber)<\/li>\n\n\n\n<li>Solo estamos procesando las excepciones para TooManyLoginAttemptsAuthenticationException, todas las dem\u00e1s, contin\u00faan su flujo normal<\/li>\n<\/ul>\n\n\n\n<p>Y el mensaje que estoy ocupando en este caso es:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>errores.tooManyFailed : 'Demasiados intentos fallidos de inicio de sesi\u00f3n, int\u00e9ntelo de nuevo en %minutes% minutos'<\/code><\/pre>\n\n\n\n<p>La variable <strong>%minutes%<\/strong> viene en  $exception-&gt;getMessageData()<\/p>\n\n\n\n<p>Con esto ya tenemos listo nuestro mensaje personalizado y se mostrara en el login cuando ingreses mal la contrase\u00f1a 3 veces (para este caso)<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Desde Symfony 5.2, se puede agregar la opci\u00f3n de login_throttling, en resumen, es una protecci\u00f3n contra ataques de fuerza bruta para tu login, el problema es que si quieres personalizar el mensaje de error, es un poco engorroso&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5,8,46],"tags":[23,47],"class_list":["post-320","post","type-post","status-publish","format-standard","hentry","category-errores-2","category-php-2","category-symfony7","tag-php","tag-symfony"],"_links":{"self":[{"href":"https:\/\/arielnavarrete.cl\/blog\/wp-json\/wp\/v2\/posts\/320","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/arielnavarrete.cl\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/arielnavarrete.cl\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/arielnavarrete.cl\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/arielnavarrete.cl\/blog\/wp-json\/wp\/v2\/comments?post=320"}],"version-history":[{"count":6,"href":"https:\/\/arielnavarrete.cl\/blog\/wp-json\/wp\/v2\/posts\/320\/revisions"}],"predecessor-version":[{"id":326,"href":"https:\/\/arielnavarrete.cl\/blog\/wp-json\/wp\/v2\/posts\/320\/revisions\/326"}],"wp:attachment":[{"href":"https:\/\/arielnavarrete.cl\/blog\/wp-json\/wp\/v2\/media?parent=320"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/arielnavarrete.cl\/blog\/wp-json\/wp\/v2\/categories?post=320"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/arielnavarrete.cl\/blog\/wp-json\/wp\/v2\/tags?post=320"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}