Chapters
-
Course Code
Subscribe to download the code!
Subscribe to download the code!
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeEl sistema de seguridad de Symfony viene repleto de cosas interesantes, como recordar mi nombre, suplantación de identidad y votantes. Incluso tiene soporte incorporado para un autentificador de "enlace de inicio de sesión", también conocido como "enlaces mágicos de inicio de sesión". En este caso, envías un enlace por correo electrónico a tu usuario y éste hace clic en él para iniciar la sesión.
Otra función muy interesante es el estrangulamiento del inicio de sesión: una forma de evitar que alguien de una única dirección IP pruebe las contraseñas una y otra vez en tu sitio... intentando iniciar sesión una y otra vez. Y es súper fácil de usar.
Activar login_throttling
En tu cortafuegos, habilítalo con login_throttling: true
:
security: | |
Show Lines
|
// ... lines 2 - 20 |
firewalls: | |
Show Lines
|
// ... lines 22 - 24 |
main: | |
Show Lines
|
// ... lines 26 - 28 |
login_throttling: true | |
Show Lines
|
// ... lines 30 - 62 |
Si te detienes ahí mismo... y actualizas cualquier página, obtendrás un error:
El estrangulamiento de inicio de sesión requiere el componente Rate Limiter.
¡Y luego un útil comando para instalarlo! ¡Muy bien! Cópialo, gira a tu terminal y ejecútalo:
composer require symfony/rate-limiter
Este paquete también instala un paquete llamado symfony/lock
, que tiene una receta. Ejecuta
git status
para ver lo que ha hecho. Es interesante. Creó un nuevo config/packages/lock.yaml
, y también modificó nuestro archivo .env
.
Para hacer un seguimiento de los intentos de acceso, el sistema de estrangulamiento necesita almacenar esos datos en algún lugar. Para ello utiliza el componente symfony/lock
. Dentro de nuestro archivo.env
, en la parte inferior, hay una nueva variable de entorno LOCK_DSN
que se establece en semaphore
:
Show Lines
|
// ... lines 1 - 28 |
###> symfony/lock ### | |
# Choose one of the stores below | |
# postgresql+advisory://db_user:db_password@localhost/db_name | |
LOCK_DSN=semaphore | |
###< symfony/lock ### |
Un semáforo... es básicamente una forma súper sencilla de almacenar estos datos si sólo tienes un único servidor. Si necesitas algo más avanzado, consulta la documentación desymfony/lock
: muestra todas las diferentes opciones de almacenamiento con sus pros y sus contras. Pero esto nos vendrá muy bien.
Así pues, el paso 1 fue añadir la configuración de login_throttling
. El paso 2 fue instalar el componente Rate Limiter. Y el paso 3 es... ¡disfrutar de la función! Sí, ¡hemos terminado!
Refrescar. No hay más errores. Por defecto, esto sólo permitirá 5 intentos de acceso consecutivos para el mismo correo electrónico y dirección IP por minuto. Vamos a probarlo. Uno, dos, tres, cuatro, cinco y... ¡el sexto es rechazado! Nos bloquea durante 1 minuto. Tanto el máximo de intentos como el intervalo se pueden configurar. De hecho, podemos verlo.
En tu terminal, ejecuta:
symfony console debug:config security
Y... busca login_throttling
. Ahí está. Sí, este max_attempts
está predeterminado a 5 y interval
a 1 minuto. Ah, y por cierto, esto también bloqueará que la misma dirección IP haga 5 veces el max_attempts
para cualquier correo electrónico. En otras palabras, si la misma dirección IP intentara rápidamente 25 correos electrónicos diferentes, los seguiría bloqueando. Y si quieres una primera línea de defensa impresionante, también te recomiendo encarecidamente que utilices algo como Cloudflare, que puede bloquear a los malos usuarios incluso antes de que lleguen a tu servidor... o activar las defensas si tu sitio es atacado desde muchas direcciones IP.
Profundizando en Cómo Funciona la Ralentización de inicio de sesión
Así que... creo que esta función es bastante genial. Pero lo más interesante para nosotros es cómo funciona entre bastidores. Funciona a través del sistema de oyentes de Symfony. Después de iniciar la sesión, ya sea con éxito o sin éxito, se envían una serie de eventos a lo largo de ese proceso. Podemos engancharnos a esos eventos para hacer todo tipo de cosas interesantes.
Por ejemplo, la clase que contiene la lógica de la aceleración del inicio de sesión se llamaLoginThrottlingListener
. Vamos a... ¡abrirla! Pulsa Shift
+Shift
y abreLoginThrottlingListener.php
.
Espectacular. Los detalles dentro de esto no son demasiado importantes. Puedes ver que utiliza algo llamado limitador de velocidad... que se encarga de comprobar si se ha alcanzado el límite. En última instancia, si se ha alcanzado el límite, lanza esta excepción, que provoca el mensaje que hemos visto. Para los que estén atentos, esa excepción se extiende a AuthenticationException
... y recuerda que puedes lanzar unAuthenticationException
en cualquier punto del proceso de autenticación para que falle.
En cualquier caso, este método está escuchando un evento llamado CheckPassportEvent
. Éste se envía después de que se llame al método authenticate()
desde cualquier autentificador. En este punto, la autentificación aún no ha tenido éxito... y el trabajo de la mayoría de los oyentes de CheckPassportEvent
es hacer alguna comprobación extra y fallar la autentificación si algo ha ido mal.
Esta clase también escucha otro evento llamado LoginSuccessEvent
... que... bueno, es bastante obvio: se envía después de cualquier autenticación con éxito. Esto restablece el limitador de velocidad en caso de éxito.
Así que esto está muy bien, y es nuestra primera visión de cómo funciona el sistema de eventos. A continuación, vamos a profundizar descubriendo que casi todas las partes de la autenticación las realiza un oyente. Entonces, crearemos el nuestro.
11 Comments
Hey Oliver W.!
Hmm. So the way it should work is this (assuming max_attempts = 5, which is the default):
A) Only allow 5 attempts per email (and we saw this in the screencast).
B) Only allow max_attempts * 5 (so 5x5=25) attempts (regardless of email) from the same IP address.
I admit that I've never tried pat (B) - I know it from reading the code iirc. But what it means that after you attempt 25 login attempts, it should not allow your IP address to make more attempts (until you reach the cool-down period). Have you tried 26 straight logins quickly (obviously you'll need to change email addresses every 5 attempts to avoid hitting the smaller, email-specific limit).
Let me know if you try that. If I'm wrong, I would definitely like to know!
Cheers!
Sorry Ryan, but I've made so many changes in the meantime that I don't think my tests would be of any value 8-(.
If I find any occasion to do it, I will.
I execute composer require symfony/rate-limiter
but it didn't generate any config/packages/lock.yaml
file, but I tested the throttling functionality and it still seems to work. it is normal ? Thanks
Hey @pasquale_pellicani!
Ah, awesome! I had to check, but it looks like since symfony/rate-limiter
6.2, the symfony/lock
package is an optional dependency. Apparently, for out-of-the-box functionality, symfony/lock
was never needed for the rate limiter. You CAN leverage a lock - https://symfony.com/doc/5.4/rate_limiter.html#using-locks-to-prevent-race-conditions - but it's not required.
I'll add a note to the tutorial so that others don't get tripped up by this.
Thanks!
So... if I get blocked for too many attempts, and then login successfully (say I created a fake user just for this), I'm not blocked anymore from attempting logins with a different e-mail than my fake user's? I gotta try this... (Rubs hands together) 😏
Hey @D-Marti
That's a great question, honestly, I don't know the inner details of how the login throttling system works, but I guess it blocks you by IP, not only by email. If you give this a try please let me know your findings :)
Cheers!
Indeed. Once you get blocked, you can only login if you provide valid credentials. Once you do that, and logout, even if you clear all your cookies, and try loging in with a different user and a bad password, your IP will still be blocked.
TL;DR The block timer does not get reset with a successful login. Blocking is IP based.
Which is great! 😲
Great research! Thank you for sharing it ✌
Hey Ryan. It would be nice to see how you can use something like cloudfare or fastly to block bad users or use it as a reverse proxy, caching, esi and so on. Can you do such a tutorial some time pleeeeasse?
Hey Michael,
Thank you for your interest in SymfonyCasts tutorials and sharing this idea with us! Unfortunately, no certain plans to do this in the near future, but we will consider releasing a course about it later... or probably cover it at least partially in future courses maybe. We'll add this to our idea pool for now, but I can't say when it might be released yet.
About ESI, we kinda covered it before in previous tutorials, I'd recommend you to take a look at it: https://symfonycasts.com/se... - the code is based on an older version of Symfony, but the concepts it covers are still valid for today. And also, here we were talking about reverse proxy: https://symfonycasts.com/sc... and caching: https://symfonycasts.com/sc... - probably it will be useful for you too.
Feel free to play with our search to find more related topics, it's really powerful and searches in video, code blocks, comments, etc :)
Cheers!
"Houston: no signs of life"
Start the conversation!
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"babdev/pagerfanta-bundle": "^3.3", // v3.3.0
"composer/package-versions-deprecated": "^1.11", // 1.11.99.4
"doctrine/annotations": "^1.0", // 1.13.2
"doctrine/doctrine-bundle": "^2.1", // 2.6.3
"doctrine/doctrine-migrations-bundle": "^3.0", // 3.1.1
"doctrine/orm": "^2.7", // 2.10.1
"knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
"knplabs/knp-time-bundle": "^1.11", // v1.16.1
"pagerfanta/doctrine-orm-adapter": "^3.3", // v3.3.0
"pagerfanta/twig": "^3.3", // v3.3.0
"phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
"scheb/2fa-bundle": "^5.12", // v5.12.1
"scheb/2fa-qr-code": "^5.12", // v5.12.1
"scheb/2fa-totp": "^5.12", // v5.12.1
"sensio/framework-extra-bundle": "^6.0", // v6.2.0
"stof/doctrine-extensions-bundle": "^1.4", // v1.6.0
"symfony/asset": "5.3.*", // v5.3.4
"symfony/console": "5.3.*", // v5.3.7
"symfony/dotenv": "5.3.*", // v5.3.8
"symfony/flex": "^1.3.1", // v1.21.6
"symfony/form": "5.3.*", // v5.3.8
"symfony/framework-bundle": "5.3.*", // v5.3.8
"symfony/monolog-bundle": "^3.0", // v3.7.0
"symfony/property-access": "5.3.*", // v5.3.8
"symfony/property-info": "5.3.*", // v5.3.8
"symfony/rate-limiter": "5.3.*", // v5.3.4
"symfony/runtime": "5.3.*", // v5.3.4
"symfony/security-bundle": "5.3.*", // v5.3.8
"symfony/serializer": "5.3.*", // v5.3.8
"symfony/stopwatch": "5.3.*", // v5.3.4
"symfony/twig-bundle": "5.3.*", // v5.3.4
"symfony/ux-chartjs": "^1.3", // v1.3.0
"symfony/validator": "5.3.*", // v5.3.8
"symfony/webpack-encore-bundle": "^1.7", // v1.12.0
"symfony/yaml": "5.3.*", // v5.3.6
"symfonycasts/verify-email-bundle": "^1.5", // v1.5.0
"twig/extra-bundle": "^2.12|^3.0", // v3.3.3
"twig/string-extra": "^3.3", // v3.3.3
"twig/twig": "^2.12|^3.0" // v3.3.3
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.0
"symfony/debug-bundle": "5.3.*", // v5.3.4
"symfony/maker-bundle": "^1.15", // v1.34.0
"symfony/var-dumper": "5.3.*", // v5.3.8
"symfony/web-profiler-bundle": "5.3.*", // v5.3.8
"zenstruck/foundry": "^1.1" // v1.13.3
}
}
Hi Ryan,
I am sorry, but I can NOT confirm this: "Oh, and by the way, this will also block the same IP address from making 5 times the max_attempts for any email."
My attempts with different email addresses and different passwords where not limited!? Different passwords for the same email, yes. But not if both are different.
I also tested five attempts for the same email with different passwords and then another email. And after six attempts and beeing blocked I can immediately restart with a new email.
Any idea why?
Thx for your good work - your means you AND the team ;-)
Oliver