Symfony ya tiene integrada la gestión de usuarios.
El primer paso es crear la entidad User
usando el asistente de Symfony que se crea mediante
php bin/console make:user
The name of the security user class (e.g. User) [User]:
> User
Do you want to store user data in the database (via Doctrine)? (yes/no) [yes]:
> yes
Enter a property name that will be the unique "display" name for the user (e.g. email, username, uuid) [email]:
> email
Will this app need to hash/check user passwords? Choose No if passwords are not needed or will be checked/hashed by some other system (e.g. a single sign-on server).
Does this app need to hash/check user passwords? (yes/no) [yes]:
> yes
created: src/Entity/User.php
created: src/Repository/UserRepository.php
updated: src/Entity/User.php
updated: config/packages/security.yaml
Este es el código generado automáticamente
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
private ?string $email = null;
#[ORM\Column]
private array $roles = [];
/**
* @var string The hashed password
*/
#[ORM\Column]
private ?string $password = null;
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): static
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* @see UserInterface
*/
public function getUserIdentifier(): 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): static
{
$this->roles = $roles;
return $this;
}
/**
* @see PasswordAuthenticatedUserInterface
*/
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): static
{
$this->password = $password;
return $this;
}
/**
* @see UserInterface
*/
public function eraseCredentials(): void
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
}
El paso siguiente es realizar la migración:
php bin/console make:migration
php bin/console doctrine:migrations:migrate
Además de crear el usuario, también se encarga de modificar la configuración de seguridad
# config/packages/security.yaml
security:
# ...
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
Esta configuración le indica que la clase que gestiona el usuario es App\Entity\User
y que el campo que proporciona el login es el email
4.1 Crear el login
Ya sólo nos queda generar el formulario de login. Para ello ejecutamos
php bin/console make:controller Login
Que crea automáticamente un controlador para la ruta login
y una plantilla.
Ahora hay que activar el formulario de login
modificando la sección form_login
# config/packages/security.yaml
security:
# ...
firewalls:
main:
# ...
form_login:
# "login" is the name of the route created previously
login_path: login
check_path: login
Este es el controlador generado:
<?php
namespace App\Controller;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class LoginController extends AbstractController
{
#[Route('/login', name: 'login')]
public function index(AuthenticationUtils $authenticationUtils): Response
{
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('login/index.html.twig', [
'last_username' => $lastUsername,
'error' => $error,
]);
}
}
Y modificamos la plantilla templates/login/index.html.twig
:
{# templates/login/index.html.twig #}
{% extends 'base.html.twig' %}
{# ... #}
{% block body %}
{% if error %}
<div>{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
<form action="{{ path('login') }}" method="post">
<label for="username">Email:</label>
<input type="text" id="username" name="_username" value="{{ last_username }}"/>
<label for="password">Password:</label>
<input type="password" id="password" name="_password"/>
{# If you want to control the URL the user is redirected to on success
<input type="hidden" name="_target_path" value="/account"/> #}
<button type="submit">login</button>
</form>
{% endblock %}
4.2 Formulario de registro
Es tan sencillo como ejecutar el comando php bin/console make:registration-form
respondiendo a las preguntas que nos propone:
Do you want to add a @UniqueEntity validation annotation on your User class to make sure duplicate accounts aren't created? (yes/no) [yes]:
yes
Do you want to send an email to verify the user's email address after registration? (yes/no) [yes]:
no
Do you want to automatically authenticate the user after registration? (yes/no) [yes]:
yes
What route should the user be redirected to after registration?:
[0 ] _wdt
[1 ] _profiler_home
[2 ] _profiler_search
[3 ] _profiler_search_bar
[4 ] _profiler_phpinfo
[5 ] _profiler_search_results
[6 ] _profiler_open_file
[7 ] _profiler
[8 ] _profiler_router
[9 ] _profiler_exception
[10] _profiler_exception_css
[11] nuevo_contacto
[12] editar_contacto
[13] insertar_sin_provincia_contacto
[14] insertar_con_provincia_contacto
[15] insertar_contacto
[16] ficha_contacto
[17] buscar_contacto
[18] modificar_contacto
[19] eliminar_contacto
[20] login
[21] page
[22] inicio
[23] _preview_error
> 22
Importante. Especificar el número que identifique la ruta
inicio
que es a donde redirigirá automáticamente al registrarse
Se creará el controlador App\Controller\RegistrationController.php
y la plantilla registration/register.html.twig
Ahora, si visitamos http://localhost:8080/register mostrará el formulario de registro:
4.3 Logout
Para habilitar el logout, hay que activar el parámetro logout
en la configuración de seguridad:
firewalls:
...
main:
...
logout:
path: app_logout
Y comprobamos el controlador SecurityController.php
:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class SecurityController extends AbstractController
{
#[Route('/logout', name: 'app_logout', methods:["GET"])]
public function logout(): void
{
// controller can be blank: it will never be called!
throw new \Exception('Don\'t forget to activate logout in security.yaml');
}
}
4.4 Hacer login con más de un campo
En el caso que queramos que el usuario se logee con el username
o correo
o cualquier otro dato, debemos modificar UserRepository
para que implemente UserLoaderInterface
y luego implementar el método loadUserByIdentifier
Por ejemplo;
...
use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;
//Demás código
public function loadUserByIdentifier(string $usernameOrEmail): ?User
{
$entityManager = $this->getEntityManager();
return $entityManager->createQuery(
'SELECT u
FROM App\Entity\User u
WHERE u.email = :query
OR u.username = :query'
)
->setParameter('query', $usernameOrEmail)
->getOneOrNullResult();
Y modificar el archivo security.yaml
para que no use el campo por defecto:
app_user_provider:
entity:
class: App\Entity\User
# property: email # o cualquier otra que hubiera
4.5 Contenido del archivo security.yaml
Os dejo el contenido completo por si tenéis algún problema:
security:
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
App\Entity\User:
algorithm: auto
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
form_login:
# "login" is the name of the route created previously
login_path: login
check_path: login
logout:
path: app_logout
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
when@test:
security:
password_hashers:
# By default, password hashers are resource intensive and take time. This is
# important to generate secure password hashes. In tests however, secure hashes
# are not important, waste resources and increase test times. The following
# reduces the work factor to the lowest possible values.
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: auto
cost: 4 # Lowest possible value for bcrypt
time_cost: 3 # Lowest possible value for argon
memory_cost: 10 # Lowest possible value for argon
4.5 Restringir acceso a partes de la aplicación
https://symfony.com/doc/current/security.html#add-code-to-deny-access
4.6 Obtener el usuario actual
En un controlador
<?php
$this->getUser();
En twig
{{ app.user }}
Reto I
Añade un campo
name
a la entidadUser
. Modifica también el formulario de registro.
Reto II
- En la portada de la web crea una lista con todos los contactos. Cada elemento será un enlace a una página donde se muestran los detalles del mismo así como un botón para Editar y Modificar. Crea la lógica en el controlador para saber si el usuario ha pulsado en editar (guardar) o en borrar. En esta página explica cómo gestionar un formulario con varios botones.
- Crea un enlace para poder añadir un contacto.
- Donde se necesario se ha de comprobar que el usuario está logeado y enviarlo a
/index
en caso contrario