Login to bookmark this video
Buy Access to Course
13.

Método personalizado authenticator authenticate()

|

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Actualmente estamos convirtiendo nuestro antiguo autenticador Guard al nuevo sistema de autenticadores. Y, muy bien, estos dos sistemas comparten algunos métodos, como supports(),onAuthenticationSuccess() y onAuthenticationFailure().

La gran diferencia está dentro del nuevo método authenticate(). En el antiguo sistema Guard, dividimos la autenticación en varios métodos. Teníamos getCredentials(), donde obteníamos cierta información, getUser(), donde encontrábamos el objeto User, ycheckCredentials(), donde comprobábamos la contraseña. Estas tres cosas se combinan ahora en el método authenticate()... con algunas bonificaciones. Por ejemplo, como verás en un segundo, ya no es responsabilidad nuestra comprobar la contraseña. Eso ahora ocurre automáticamente.

El objeto Pasaporte

Nuestro trabajo en authenticate() es sencillo: devolver un Passport. Sigue adelante y añade un tipo de retornoPassport. Esto es realmente necesario en Symfony 6. No se añadió automáticamente debido a una capa de desaprobación y al hecho de que el tipo de retorno cambió de PassportInterface a Passport en Symfony 5.4.

138 lines | src/Security/LoginFormAuthenticator.php
// ... lines 1 - 26
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
// ... lines 28 - 29
class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
// ... lines 32 - 39
public function authenticate(Request $request): Passport
{
// ... lines 42 - 66
}
// ... lines 68 - 136
}

En cualquier caso, este método devuelve un Passport... así que hazlo: return new Passport(). Por cierto, si eres nuevo en el sistema de autenticadores personalizados y quieres aprender más, echa un vistazo a nuestro Tutorial de seguridad de Symfony 5 donde hablamos de todo esto. Ahora repasaré lo básico, pero los detalles están ahí.

Antes de rellenar el Passport, coge toda la información del Request que necesitemos... pega... luego establece cada uno de estos como variables:$email =, $password =... y preocupémonos del token CSRF más tarde.

138 lines | src/Security/LoginFormAuthenticator.php
// ... lines 1 - 39
public function authenticate(Request $request): Passport
{
$email = $request->request->get('email');
$password = $request->request->get('password');
return new Passport(
// ... lines 46 - 65
);
}
// ... lines 68 - 138

El primer argumento del Passport es un new UserBadge(). Lo que pasas aquí es el identificador de usuario. ¡En nuestro sistema, nos identificamos a través del correo electrónico, así que pasa$email!

Y... si quieres, puedes detenerte aquí. Si sólo pasas un argumento aUserBadge, Symfony utilizará el "proveedor de usuario" de security.yaml para encontrar a ese usuario. Estamos utilizando un proveedor entity, que le dice a Symfony que intente consultar el objeto User en la base de datos a través de la propiedad email.

Consulta de usuario personalizada opcional

En el sistema antiguo, hacíamos todo esto manualmente consultando el UserRepository. Eso ya no es necesario. Pero a veces... si tienes una lógica personalizada, puede que aún necesites encontrar al usuario manualmente.

Si tienes este caso de uso, pasa un function() al segundo argumento que acepta un argumento $userIdentifier. Ahora, cuando el sistema de autenticación necesite el objeto Usuario, llamará a nuestra función y nos pasará el "identificador de usuario"... que será lo que hayamos pasado al primer argumento. Es decir, el correo electrónico.

Nuestro trabajo consiste en utilizarlo para devolver el usuario. Empieza con$user = $this->entityManager->getRepository(User::class)

Y sí, podría haber inyectado el UserRepository en lugar del gestor de entidades... eso sería mejor... pero esto está bien. Luego->findOneBy(['email' => $userIdentifier]).

Si no encontramos ningún usuario, necesitamos throw a new UserNotFoundException(). Luego, return $user.

138 lines | src/Security/LoginFormAuthenticator.php
// ... lines 1 - 39
public function authenticate(Request $request): Passport
{
// ... lines 42 - 44
return new Passport(
new UserBadge($email, function($userIdentifier) {
// optionally pass a callback to load the User manually
$user = $this->entityManager
->getRepository(User::class)
->findOneBy(['email' => $userIdentifier]);
if (!$user) {
throw new UserNotFoundException();
}
return $user;
}),
// ... lines 58 - 65
);
}
// ... lines 68 - 138

El primer argumento Passport ¡ya está!

ContraseñaCredenciales

Para el segundo argumento, aquí abajo, cambia mi mal punto y coma por una coma - entonces dinew PasswordCredentials() y pasa esto al enviado $password.

138 lines | src/Security/LoginFormAuthenticator.php
// ... lines 1 - 39
public function authenticate(Request $request): Passport
{
// ... lines 42 - 44
return new Passport(
new UserBadge($email, function($userIdentifier) {
// ... lines 47 - 56
}),
new PasswordCredentials($password),
// ... lines 59 - 65
);
}
// ... lines 68 - 138

¡Eso es todo lo que necesitamos! Así es: ¡no necesitamos comprobar la contraseña! Pasamos un PasswordCredentials()... ¡y luego otro sistema se encarga de comprobar la contraseña enviada con la contraseña hash de la base de datos! ¿No es genial?

Insignias adicionales

Por último, Passport acepta una matriz opcional de "insignias", que son "cosas" extra que quieres añadir... normalmente para activar otras funciones.

Nosotros sólo necesitamos pasar una: una new CsrfTokenBadge(). Esto se debe a que nuestro formulario de acceso está protegido por un token CSRF. Antes, lo comprobábamos manualmente. ¡Lamentable!

¡Pero ya no! Pasa la cadena authenticate al primer argumento... que sólo tiene que coincidir con la cadena utilizada cuando generamos el token en la plantilla:login.html.twig. Si busco csrf_token... ¡ahí está!

Para el segundo argumento, pasa el token CSRF enviado:$request->request->get('_csrf_token'), que también puedes ver en el formulario de acceso.

138 lines | src/Security/LoginFormAuthenticator.php
// ... lines 1 - 39
public function authenticate(Request $request): Passport
{
// ... lines 42 - 44
return new Passport(
// ... lines 46 - 57
new PasswordCredentials($password),
[
new CsrfTokenBadge(
'authenticate',
$request->request->get('_csrf_token')
),
// ... line 64
]
);
}
// ... lines 68 - 138

Y... ¡listo! Sólo con pasar la insignia, se validará el token CSRF.

Ah, y aunque no hace falta que lo hagamos, también voy a pasar unnew RememberMeBadge(). Si utilizas el sistema "Recuérdame", entonces necesitas pasar esta insignia. Indica al sistema que "aceptas" que se establezca una cookie "Recuérdame" si el usuario inicia sesión utilizando este autenticador. Pero aún necesitas tener una casilla de verificación "Recuérdame" aquí... para que funcione. O, para activarlo siempre, añade ->enable() en la insignia.

138 lines | src/Security/LoginFormAuthenticator.php
// ... lines 1 - 39
public function authenticate(Request $request): Passport
{
// ... lines 42 - 44
return new Passport(
// ... lines 46 - 57
new PasswordCredentials($password),
[
// ... lines 60 - 63
(new RememberMeBadge())->enable(),
]
);
}
// ... lines 68 - 138

Y, por supuesto, nada de esto funcionará a menos que actives el sistema remember_meen tu cortafuegos, cosa que aún no tengo. Seguirá siendo seguro añadir esa insignia... pero no habrá ningún sistema que la procese y añada la cookie. Por lo tanto, la insignia será ignorada.

¡Borrar métodos antiguos!

En fin, ¡ya hemos terminado! Si eso te ha parecido abrumador, retrocede y mira nuestro tutorial sobre Seguridad en Symfony para obtener más contexto.

Lo bueno es que ya no necesitamos getCredentials(), getUser(),checkCredentials(), ni getPassword(). Todo lo que necesitamos esauthenticate(), onAuthenticationSuccess(), onAuthenticationFailure(), ygetLoginUrl(). Incluso podemos celebrarlo aquí eliminando un montón de viejas declaraciones de uso. ¡Sí!

Ah, y mira el constructor. Ya no necesitamos CsrfTokenManagerInterfaceni UserPasswordHasherInterface: ambas comprobaciones se hacen ahora en otra parte. Y... eso nos da dos sentencias use más que eliminar.

87 lines | src/Security/LoginFormAuthenticator.php
// ... lines 1 - 28
public function __construct(private SessionInterface $session, private EntityManagerInterface $entityManager, private UrlGeneratorInterface $urlGenerator)
{
}
// ... lines 32 - 87

Activar el nuevo sistema de seguridad

Llegados a este punto, nuestro único autentificador personalizado se ha trasladado al nuevo sistema de autentificadores. Esto significa que, en security.yaml, ¡estamos listos para cambiar al nuevo sistema! Di enable_authenticator_manager: true.

64 lines | config/packages/security.yaml
security:
// ... lines 2 - 9
enable_authenticator_manager: true
// ... lines 11 - 64

Y estos autentificadores personalizados ya no están bajo una clave guard. En su lugar, añade custom_authenticator y añade esto directamente debajo.

63 lines | config/packages/security.yaml
security:
// ... lines 2 - 20
firewalls:
// ... lines 22 - 24
main:
// ... lines 26 - 27
custom_authenticator:
- App\Security\LoginFormAuthenticator
// ... lines 30 - 63

Vale, ¡llegó la hora de la verdad! Acabamos de cambiar completamente al nuevo sistema. ¿Funcionará? Vuelve a la página de inicio, recarga y... ¡funciona! ¡Y echa un vistazo a esas depreciaciones! Hemos pasado de unas 45 a 4. ¡Woh!

Algunas de ellas están relacionadas con otro cambio de seguridad. A continuación: actualicemos al nuevo password_hasher y comprobemos un nuevo comando para depurar cortafuegos de seguridad.