Aprende a crear tu propio sistema de autenticación para Symfony 4 desde cero. En esta parte, aprenderás a implementar tu propia clase de usuario personalizada.

Cómo implementar tu propio sistema de autenticación de usuario en Symfony 4.3: Parte 1 (Crear una clase de usuario personalizada)

Desde Symfony 2, FOSUserBundle ha sido sin duda el paquete más utilizado para implementar un sistema de usuario respaldado por una base de datos. Su instalación fue bastante fácil y comprensible, sin embargo, como desarrollador al que le encanta implementar muchas cosas desde cero (para que pueda personalizar algunos comportamientos más adelante), algunas cosas del paquete no eran las adecuadas para mí. Con la introducción de Symfony 4, FOSUserBundle no proporciona una gran compatibilidad, esto significa que puede encontrar muchas advertencias de obsolescencia cuando simplemente habilita el paquete en su aplicación.

La mayoría de los desarrolladores, solo necesitan implementar un sistema básico de autenticación de usuarios basado en una base de datos, esto incluye la creación de una tabla de usuarios, un formulario de registro e inicio de sesión. En esta serie, le explicaremos cómo crear este sistema de usuario usted mismo en Symfony 4.3 paso a paso.

1. Crear entidad de usuario

Como en todas las aplicaciones que requieren autenticación, necesitará una entidad de usuario que se describirá con al menos 4 campos en su base de datos en una tabla, a saber, usuario:

  • id (autoincrementable)
  • correo electrónico
  • roles
  • contraseña

Symfony 4 por defecto ofrece una forma bastante fácil de generar esta entidad de Usuario en su aplicación a través del php bin/console make:usercomando. Sin embargo, nos gusta hacer algunas cosas manualmente para que podamos entender lo que estamos haciendo, cómo funciona y por qué es necesario. El primer paso a seguir para crear la entidad de usuario es crear la User.phpclase en el src/Entitydirectorio de su proyecto. Básicamente contendrá la estructura de la tabla de su tabla de usuario en la base de datos. En nuestro caso, tendremos una tabla con los 6 siguientes campos:

  • id (autoincrementable)
  • correo electrónico
  • roles
  • contraseña
  • nombre de pila
  • apellido

La principal ventaja de este enfoque es la facilidad con la que puede agregar nuevos campos a la tabla de usuarios sin problemas:

<?php

// src/Entity/User.php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 */
class User implements UserInterface
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;
    
    /**
     * @var string|null
     *
     * @ORM\Column(name="first_name", type="string", length=255, nullable=true)
     */
    private $firstName;
    
    /**
     * @var string|null
     *
     * @ORM\Column(name="last_name", type="string", length=255, nullable=true)
     */
    private $lastName;

    /**
     * @ORM\Column(type="string", length=180, unique=true)
     */
    private $email;

    /**
     * @ORM\Column(type="json")
     */
    private $roles = [];

    /**
     * @var string The hashed password
     * @ORM\Column(type="string")
     */
    private $password;

    public function getId(): ?int
    {
        return $this->id;
    }
    
    public function getFirstName(): ?string
    {
        return $this->firstName;
    }

    public function setFirstName(?string $name): self
    {
        $this->firstName = $name;

        return $this;
    }
    
    public function getLastName(): ?string
    {
        return $this->lastName;
    }

    public function setLastName(?string $name): self
    {
        $this->lastName = $name;

        return $this;
    }

    public function getEmail(): ?string
    {
        return $this->email;
    }

    public function setEmail(string $email): self
    {
        $this->email = $email;

        return $this;
    }

    /**
     * A visual identifier that represents this user.
     *
     * @see UserInterface
     */
    public function getUsername(): string
    {
        return (string) $this->email;
    }

    /**
     * @see UserInterface
     */
    public function getRoles(): array
    {
        $roles = $this->roles;
        // guarantee every user at least has ROLE_USER
        $roles[] = 'ROLE_USER';

        return array_unique($roles);
    }

    public function setRoles(array $roles): self
    {
        $this->roles = $roles;

        return $this;
    }

    /**
     * @see UserInterface
     */
    public function getPassword(): string
    {
        return (string) $this->password;
    }

    public function setPassword(string $password): self
    {
        $this->password = $password;

        return $this;
    }

    /**
     * @see UserInterface
     */
    public function getSalt()
    {
        // no es necesario cuando se usa el algoritmo "auto" en security.yaml
    }

    /**
     * @see UserInterface
     */
    public function eraseCredentials()
    {
        // Si almacena datos confidenciales temporales sobre el usuario, bórrelos aquí
        // $this->plainPassword = null;
    }
}

Después de crear la entidad Usuario, observará también que tenemos en las anotaciones de la clase una Clase de repositorio definida, por lo que también debe crearla (o puede eliminar la  @ORM\Entity(repositoryClass="App\Repository\UserRepository")anotación). La UserRepository.phpclase contiene el siguiente código y estará ubicada dentro del src/Repositorydirectorio:

<?php

// src/Repository/UserRepository.php
namespace App\Repository;

use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;

/**
 * @method User|null find($id, $lockMode = null, $lockVersion = null)
 * @method User|null findOneBy(array $criteria, array $orderBy = null)
 * @method User[]    findAll()
 * @method User[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 */
class UserRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, User::class);
    }

    // /**
    //  * @return User[] Returns an array of User objects
    //  */
    /*
    public function findByExampleField($value)
    {
        return $this->createQueryBuilder('u')
            ->andWhere('u.exampleField = :val')
            ->setParameter('val', $value)
            ->orderBy('u.id', 'ASC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult()
        ;
    }
    */

    /*
    public function findOneBySomeField($value): ?User
    {
        return $this->createQueryBuilder('u')
            ->andWhere('u.exampleField = :val')
            ->setParameter('val', $value)
            ->getQuery()
            ->getOneOrNullResult()
        ;
    }
    */
}

En esta clase, puede definir métodos personalizados para el repositorio como findByEmailProvider, findByAge o algo personalizado. Después de crear la entidad de usuario, asegúrese de tener una conexión de base de datos definida en su archivo .env:

# app/.env
DATABASE_URL=mysql://user:[email protected]:3306/database_name

y ejecute el siguiente comando para actualizar la estructura de la base de datos:

php bin/console doctrine:schema:update --force

Esto creará la tabla de usuarios en nuestra base de datos:

Symfony 4.3 User Table on Database MySQL

Ahora que tenemos una entidad de usuario y la tabla en la base de datos, podemos continuar con la configuración del security.yamlarchivo.

2. Configure security.yaml

La entidad de usuario ha sido definida y configurada con los campos más básicos, ahora necesitamos especificarle al framework que usaremos dicha entidad como el proveedor de usuarios de nuestra aplicación:

# app/config/packages/security.yaml
security:
    encoders:
        App\Entity\User:
            algorithm: auto

    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        # utilizado para recargar al usuario desde la sesión y otras funciones (por ejemplo, switch_user)
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email

Como se especifica en los documentos oficiales de Symfony, en esta versión de Symfony, deberá configurar el algoritmo hash para las contraseñas en automático. Si define manualmente bcrypt, argon2i o sodium, verá una advertencia de desaprobación en el proyecto, que es exactamente lo que queremos evitar al implementar nuestro propio sistema de autenticación. Esto sucede debido a la naturaleza de evolución acelerada de los hash, por lo que es cada vez menos recomendable seleccionar un algoritmo de hash específico y ahí es cuando  NativePasswordEncoderentra en juego.

3. Crear usuarios de prueba

Ahora, para terminar esta parte del tutorial, necesitaremos crear al menos un solo usuario en nuestra base de datos para que podamos jugar con el resto del tutorial más adelante. Hasta ahora, no hemos implementado ningún formulario para el registro o el inicio de sesión, pero implementamos la lógica de backend para que el sistema funcione con una entidad de Usuario. De acuerdo con nuestra base de datos, simplemente podríamos registrar un usuario con un correo electrónico, el nombre y apellido y la contraseña. El único problema para nosotros en este paso es cómo registrar un usuario con una contraseña válida, porque como puede comprender ahora, no almacenamos contraseñas de texto sin formato por razones obvias en la base de datos.

Como no estamos implementando los formularios en esta parte del tutorial, podríamos registrar un usuario manualmente en nuestra base de datos con una consulta SQL fácilmente. Pero, ¿de dónde deberíamos obtener la versión hash de la contraseña ahora mismo? Podemos hacer esto a través del  UserPasswordEncoderInterfacecódigo with o usando el comando security: encode-password en el cli. Te explicaremos cómo crear un usuario de 2 formas:

A. Crear usuario manualmente con una consulta SQL

La primera opción es simplemente registrar a un usuario con una consulta SQL en herramientas como PHPMyAdmin o algo relacionado. Hacemos esto para que pueda comprender fácilmente lo que está sucediendo en este momento. Necesitaremos hash la contraseña del usuario con el  NativePasswordEncoder, por lo que si no desea escribir código PHP para registrar este usuario, simplemente puede usar el hash de la contraseña del cli con el siguiente comando, donde simplemente puede proporcionar como argumento posicional el contraseña de texto plano que se le asignará al usuario, en este caso será "MyTestPassword":

php bin/console security:encode-password MyTestPassword

Ejecutar el comando anterior en la terminal generará la siguiente salida:

Como puede ver, la contraseña codificada "MyTestPassword" con el codificador de contraseña nativo de Symfony es  $argon2id$v=19$m=65536,t=4,p=1$cXczUUhnTWJwRC4uQkNDSA$PswUIjNtoOwGVwGj/e8DamrwQZKMqhzr4N39jq8eKG0. Ahora que tenemos la contraseña, podemos crear fácilmente un usuario en nuestra base de datos con la siguiente consulta (tenga en cuenta que la contraseña debe cambiarse con la contraseña codificada generada por el comando en su propia máquina):

INSERT INTO `user` (
    `id`, 
    `email`, 
    `roles`, 
    `password`, 
    `first_name`,
    `last_name`
) VALUES (
    NULL, 
    '[email protected]', 
    '[]', 
    '$argon2id$v=19$m=65536,t=4,p=1$aU1QY2pLSjRjdzUwM2QyLw$8rwHEb9OxHusAt5yfSjHf3CtIkeWTFJxBy8IFDYa2jQ',
    'Carlos', 
    'Delgado'
);

B. Creando usuario manualmente con Doctrine dentro de un controlador

Alternativamente, si desea crear el usuario de prueba con código PHP, puede escribir rápidamente una acción personalizada en un controlador de prueba con el siguiente código, que simplemente conservaría una nueva entidad de usuario en la base de datos:

<?php

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

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

// Importar la entidad de usuario
use App\Entity\User;
// Importar la interfaz del codificador de contraseña
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

class TestController extends AbstractController
{   
    /**
     * @Route("/register-test-user", name="app_register_testuser")
     */
    public function register(UserPasswordEncoderInterface $passwordEncoder)
    {
        // Recuperar al administrador de la entidad
        $entityManager = $this->getDoctrine()->getManager();
        
        // Crear un nuevo usuario con datos aleatorios
        $user = new User();
        $user
            ->setEmail("[email protected]")
            ->setPassword($passwordEncoder->encodePassword(
                $user,
                'MyTestPassword'
            ))
            ->setFirstName("Carlos")
            ->setLastName("Delgado");
        
        $entityManager->persist($user);
        $entityManager->flush();

        return new Response(
            '<html><body>Test User Registered Succesfully</body></html>'
        );
    }
}

Siguiendo cualquiera de los pasos anteriores de la creación de un usuario de prueba, almacenará un solo registro en la base de datos con datos como:

User Entries Symfony 4.3

Y eso será todo por esta parte. Como puede ver, implementamos el aspecto más básico de nuestro sistema de autenticación, que es básicamente la implementación de una entidad de usuario.

Lea todas las partes del tutorial "Cómo implementar su propio sistema de autenticación de usuarios en Symfony 4.3"

Enlaces de interés para este tutorial

Que te diviertas ❤️!


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