Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Cortafuegos y autenticadores

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Construimos este formulario de inicio de sesión haciendo una ruta, un controlador y renderizando una plantilla:

... lines 1 - 8
class SecurityController extends AbstractController
{
/**
* @Route("/login", name="app_login")
*/
public function login(): Response
{
return $this->render('security/login.html.twig');
}
}

Muy sencillo. Cuando enviamos el formulario, se hace un POST de vuelta a /login. Así que, para autenticar al usuario, es de esperar que pongamos algo de lógica aquí: como si se tratara de una petición POST, leer el correo electrónico y la contraseña POSTados, consultar el objeto User... y, finalmente, comprobar la contraseña. ¡Eso tiene mucho sentido! Y eso no es en absoluto lo que vamos a hacer.

Hola cortafuegos

El sistema de autenticación de Symfony funciona de una manera... un poco mágica, que supongo que es adecuada para nuestro sitio. Al inicio de cada petición, antes de que Symfony llame al controlador, el sistema de seguridad ejecuta un conjunto de "autenticadores". El trabajo de cada autentificador es mirar la petición, ver si hay alguna información de autentificación que entienda -como un correo electrónico y una contraseña enviados, o una clave de la API que esté almacenada en una cabecera- y, si la hay, utilizarla para consultar al usuario y comprobar la contraseña. Si todo eso ocurre con éxito, entonces... ¡boom! Autenticación completa.

Nuestro trabajo es escribir y activar estos autentificadores. Abreconfig/packages/security.yaml. Recuerda las dos partes de la seguridad: la autenticación (quién eres) y la autorización (qué puedes hacer).

La parte más importante de este archivo es firewalls:

security:
... lines 2 - 13
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#firewalls-authentication
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
... lines 27 - 33

Un cortafuegos tiene que ver con la autenticación: su trabajo es averiguar quién eres. Y, por lo general, tiene sentido tener sólo un cortafuegos en tu aplicación... incluso si hay varias formas diferentes de autenticarse, como un formulario de inicio de sesión y una clave de API y OAuth.

El cortafuegos "dev"

Pero... woh woh woh. Si casi siempre queremos un solo cortafuegos... ¿por qué hay ya dos? Así es como funciona: al inicio de cada petición, Symfony recorre la lista de cortafuegos, lee la clave pattern -que es una expresión regular- y encuentra el primer cortafuegos cuyo patrón coincida con la URL actual. Así que sólo hay un cortafuegos activo por petición.

Si te fijas bien, ¡este primer cortafuegos es falso! Básicamente, busca si la URL empieza por /_profiler o /_wdt... y luego establece la seguridad en false:

security:
... lines 2 - 13
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
... lines 18 - 33

En otras palabras, básicamente se está asegurando de que no se crea un sistema de seguridad tan épico que... bloquea la barra de herramientas de depuración web y el perfilador.

Así que... en realidad, sólo tenemos un cortafuegos real llamado main. No tiene la clave pattern, lo que significa que coincidirá con todas las peticiones que no coincidan con el cortafuegos dev. Ah, ¿y los nombres de estos cortafuegos - main y dev? No tienen ningún sentido.

Activar los autentificadores

La mayor parte de la configuración que vamos a poner debajo del cortafuegos está relacionada con la activación de los autentificadores: esas cosas que se ejecutan al principio de cada petición y que intentan autentificar al usuario. Pronto añadiremos parte de esa configuración. Pero estas dos claves superiores hacen algo diferente. lazy permite que el sistema de autenticación no autentique al usuario hasta que lo necesite y provider vincula este cortafuegos al proveedor de usuarios del que hemos hablado antes. Deberías tener estas dos líneas... pero ninguna es terriblemente importante:

security:
... lines 2 - 13
firewalls:
... lines 15 - 17
main:
lazy: true
provider: app_user_provider
... lines 21 - 33

Crear una clase de autenticador personalizada

De todos modos, cada vez que queramos autentificar al usuario -como cuando enviamos un formulario de acceso- necesitamos un autentificador. Hay algunas clases de autentificadores principales que podemos utilizar, incluida una para los formularios de inicio de sesión.... y te mostraré algunas de ellas más adelante. Pero para empezar, vamos a construir nuestra propia clase de autentificador desde cero.

Para ello, ve al terminal y ejecuta:

symfony console make:auth

Como puedes ver, puedes seleccionar "Autenticador de formularios de inicio de sesión" para engañar y generar un montón de código para un formulario de inicio de sesión. Pero como estamos construyendo cosas desde cero, selecciona "Autentificador vacío" y llámalo LoginFormAuthenticator.

Espectacular. Esto hizo dos cosas: creó una nueva clase de autentificador y también actualizósecurity.yaml. Abre primero la clase: src/Security/LoginFormAuthenticator.php:

... lines 1 - 11
class LoginFormAuthenticator extends AbstractAuthenticator
{
public function supports(Request $request): ?bool
{
// TODO: Implement supports() method.
}
public function authenticate(Request $request): PassportInterface
{
// TODO: Implement authenticate() method.
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
// TODO: Implement onAuthenticationSuccess() method.
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
// TODO: Implement onAuthenticationFailure() method.
}
... lines 33 - 43
}

La única regla sobre un autentificador es que necesita implementarAuthenticatorInterface... aunque normalmente extenderás AbstractAuthenticator... que implementa AuthenticatorInterface por ti:

... lines 1 - 8
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
... lines 10 - 11
class LoginFormAuthenticator extends AbstractAuthenticator
{
... lines 14 - 43
}

Hablaremos de lo que hacen estos métodos uno por uno. En cualquier caso, AbstractAuthenticator es agradable porque implementa un método súper aburrido para ti.

Una vez que activemos esta nueva clase en el sistema de seguridad, al principio de cada petición, Symfony llamará a este método supports() y básicamente preguntará

¿Ves información de autenticación en esta petición que entiendas?

Para demostrar que Symfony llamará a esto, vamos a dd('supports'):

... lines 1 - 11
class LoginFormAuthenticator extends AbstractAuthenticator
{
public function supports(Request $request): ?bool
{
dd('supports!');
}
... lines 18 - 43
}

Activar los autenticadores con custom_authenticators

Bien, entonces, ¿cómo activamos este autentificador? ¿Cómo le decimos a nuestro cortafuegos que debe utilizar nuestra nueva clase? En security.yaml, ¡ya tenemos el código que lo hace! Esta línea custom_authenticator fue añadida por el comando make:auth:

security:
... lines 2 - 13
firewalls:
... lines 15 - 17
main:
... lines 19 - 20
custom_authenticator: App\Security\LoginFormAuthenticator
... lines 22 - 34

Así que si tienes una clase de autentificador personalizada, así es como la activas. Más adelante, veremos que puedes tener varios autentificadores personalizados si quieres.

En cualquier caso, ¡esto significa que nuestro autentificador ya está activo! Así que vamos a probarlo. Actualiza la página de inicio de sesión. ¡Accede al método supports()! De hecho, si vas a cualquier URL, se encontrará con nuestro dd(). En cada petición, antes del controlador, Symfony pregunta ahora a nuestro autentificador si soporta la autentificación en esta petición.

A continuación, vamos a rellenar la lógica del autentificador y conseguir que nuestro usuario inicie la sesión

Leave a comment!

¡Este tutorial también funciona muy bien para Symfony 6!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.4.1 || ^8.0.0",
        "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.17.5
        "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
    }
}