Aprende a crear tu propio sistema de autenticación para Symfony 4 desde cero. En esta parte, aprenderás a crear el formulario de inicio de sesión y la ruta de cierre de sesión para tus usuarios desde cero.

Cómo implementar tu propio sistema de autenticación de usuario en Symfony 4.3: Parte 3 (Crear un formulario de inicio de sesión y ruta de cierre de sesión)

En nuestro artículo anterior, le mostramos cómo crear un formulario de registro para agregar nuevos usuarios en su aplicación. Obviamente, los usuarios deben iniciar sesión en la aplicación si ya tienen una cuenta en su aplicación, por lo que tendrá una sesión y credenciales mientras visita su sitio web.

Symfony 4 hace que esto sea realmente fácil de lograr y le explicaremos brevemente cómo crear las rutas de inicio y cierre de sesión:

1. Crear ruta de inicio de sesión

Inicialmente, necesitaremos crear una ruta donde el usuario accederá al formulario de inicio de sesión. Cree el SecurityController.phparchivo con el siguiente contenido en el directorio del controlador de su aplicación ( /src/Controller/):

<?php

// src/Controller/SecurityController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;

class SecurityController extends AbstractController
{
    /**
     * @Route("/login", name="app_login")
     */
    public function login(AuthenticationUtils $authenticationUtils): Response
    {
        // Obtener el error de inicio de sesión si hay uno
        $error = $authenticationUtils->getLastAuthenticationError();
        
        // Recupera el último correo electrónico ingresado por el usuario.
        $lastUsername = $authenticationUtils->getLastUsername();

        return $this->render('security/login.html.twig', [
            'last_username' => $lastUsername,
            'error' => $error
        ]);
    }
}

La acción de inicio de sesión recibirá como primer argumento a través de la inyección de dependencia una instancia AuthenticationUtils, un objeto del que puede obtener información del ejemplo, el último error de autenticación y el nombre de usuario proporcionado en el formulario.

2. Cree un autenticador de formulario de inicio de sesión

A continuación, deberá crear la clase de autenticador que amplíe la AbstractFormLoginAuthenticator clase base, lo que facilita la autenticación de inicio de sesión del formulario. Esta clase recibirá en el constructor 4 componentes clave requeridos en este módulo, a saber, el administrador de entidades (para crear consultas), la interfaz del enrutador (para crear rutas), el administrador de tokens CSRF (verificar si el formulario era válido) y el codificador de contraseña. (para comprobar si la autenticación es válida).

Este autenticador se ejecuta (después de registrarse en el paso 3), cuando el método de soportes devuelve verdadero. En este caso, se activará cuando la ruta actual sea app_login y el método sea POST (cuando se haya enviado el formulario):

<?php

// src/Security/LoginFormAuthenticator.php
namespace App\Security;

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Http\Util\TargetPathTrait;

class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
    use TargetPathTrait;

    private $entityManager;
    private $router;
    private $csrfTokenManager;
    private $passwordEncoder;

    public function __construct(EntityManagerInterface $entityManager, RouterInterface $router, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
    {
        $this->entityManager = $entityManager;
        $this->router = $router;
        $this->csrfTokenManager = $csrfTokenManager;
        $this->passwordEncoder = $passwordEncoder;
    }

    public function supports(Request $request)
    {
        return 'app_login' === $request->attributes->get('_route')
            && $request->isMethod('POST');
    }

    public function getCredentials(Request $request)
    {
        $credentials = [
            'email' => $request->request->get('email'),
            'password' => $request->request->get('password'),
            'csrf_token' => $request->request->get('_csrf_token'),
        ];
        $request->getSession()->set(
            Security::LAST_USERNAME,
            $credentials['email']
        );

        return $credentials;
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        $token = new CsrfToken('authenticate', $credentials['csrf_token']);
        if (!$this->csrfTokenManager->isTokenValid($token)) {
            throw new InvalidCsrfTokenException();
        }

        $user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $credentials['email']]);

        if (!$user) {
            // falla la autenticación con un error personalizado
            throw new CustomUserMessageAuthenticationException('Email could not be found.');
        }

        return $user;
    }

    public function checkCredentials($credentials, UserInterface $user)
    {
        return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
            return new RedirectResponse($targetPath);
        }

        // Por ejemplo : return new RedirectResponse($this->router->generate('some_route'));
        throw new \Exception('TODO: provide a valid redirect inside '.__FILE__);
    }

    protected function getLoginUrl()
    {
        return $this->router->generate('app_login');
    }
}

Tenga en cuenta que debe manejar en esta clase, específicamente en la devolución de llamada onAuthenticationSuccess, lo que sucede con el usuario ahora. Por lo general, solo debe redirigirlo a la página de índice de su aplicación o algo así, así que asegúrese de modificar el código de la devolución de llamada de acuerdo con sus necesidades, por ejemplo, en lugar de lanzar la excepción, simplemente redirija a nuestra ruta de índice:

public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
    if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
        return new RedirectResponse($targetPath);
    }

    //  Redirigir al usuario a la página de inicio
    return new RedirectResponse($this->router->generate('app_index'));
}

3. Registrar Guard Authenticator

El autenticador ya existe, sin embargo, debe registrarlo en el firewall principal:

# app/config/packages/security.yaml
security:
    firewalls:
        main:
            anonymous: true
            guard:
                authenticators:
                    - App\Security\LoginFormAuthenticator

    # Una forma sencilla de controlar el acceso a grandes secciones de su sitio
    # Nota: Solo se utilizará el * primer * control de acceso que coincida
    access_control:
        - { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }

Al hacer esto, la clase se registrará y reaccionará cuando el método de soportes de la clase devuelva verdadero.

4. Crear vista de inicio de sesión

Por último, pero no menos importante, en la ruta de inicio de sesión representamos una vista de Twig, es decir login.html.twig, que aún no se ha creado y contendrá el siguiente marcado (almacenado en /app/templates/security/):

{# /app/templates/security/login.html.twig #}
{% extends 'base.html.twig' %}

{% block title %}Log in!{% endblock %}

{% block body %}
<form method="post">
    {# If there's any error, display it to the user #}
    {% if error %}
        <div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
    {% endif %}

    <h1>Please sign in</h1>
    
    {# Email Input #}
    <label for="inputEmail" class="sr-only">Email</label>
    <input type="email" value="{{ last_username }}" name="email" id="inputEmail" class="form-control" placeholder="Email" required autofocus>
    
    {# Password Input #}
    <label for="inputPassword" class="sr-only">Password</label>
    <input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required>
    
    {# CSRF Token Input #}
    <input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}" />

    <button class="btn btn-lg btn-primary" type="submit">
        Sign in
    </button>
</form>
{% endblock %}

5. Acceso e inicio de sesión

Una vez creada la vista, si intenta acceder a la ruta  mywebsite.com/login verá el formulario de inicio de sesión:

Sign In Form

Proporcione algunas credenciales de un usuario registrado a través del formulario de registro creado en la parte 2 de este artículo. Si especificó allí una ruta de redireccionamiento, todo funcionará correctamente y será redirigido a esa ruta y verá en las herramientas de desarrollo que está autenticado:

User Authenticated Form

¡Y eso es todo sobre la parte de inicio de sesión!

6. Crear ruta de cierre de sesión

Ahora, su usuario también debería poder cerrar la sesión si inició sesión, por lo que debe exponer una ruta de cierre de sesión. Puede crearlo en el mismo SecurityController donde también se creó la ruta de inicio de sesión, para que pueda agregarlo, una simple acción vacía llamada cerrar sesión:

<?php

// src/Controller/SecurityController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;

class SecurityController extends AbstractController
{
    /**
     * @Route("/logout", name="app_logout")
     */
    public function logout(): Response
    {
        // El controlador puede estar en blanco: ¡nunca se ejecutará!
    }
}

Tenga en cuenta que la ruta tendrá un identificador utilizado en el siguiente paso, para habilitar la función de cierre de sesión del sistema de autenticación.

7. Habilitar Cerrar sesión

Ahora que existe la ruta de cierre de sesión, debe especificarla en el firewall principal del archivo security.yaml:

# app/config/packages/security.yaml
security:
    firewalls:
        main:
            logout:
                path: app_logout
                # ¿Dónde redireccionar después de cerrar sesión? Puede especificar la propiedad de destino
                # target: app_any_route 

También puede especificar en este bloque a dónde redirigir al usuario después de cerrar la sesión. Una vez que registre esta ruta, accediendo a la ruta mywebsite.com/logout después de iniciar sesión, la sesión se eliminará y el usuario deberá iniciar sesión en la aplicación una vez más.

LEA TODAS LAS PARTES DEL TUTORIAL "CÓMO IMPLEMENTAR SU PROPIO SISTEMA DE AUTENTICACIÓN DE USUARIO EN SYMFONY 4.3"

ENLACES DE INTERÉS PARA ESTE TUTORIAL

Que te diviertas ❤️!


Ingeniero de Software Senior en EPAM Anywhere. Interesado en la programación desde los 14 años, Carlos es un programador autodidacta, fundador y autor de la mayoría de los artículos de Our Code World.

Conviertete en un programador más sociable

Patrocinadores