Chapters
-
Course Code
Subscribe to download the code!
Subscribe to download the code!
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
Independientemente de cómo se autentifiquen tus usuarios -un formulario de inicio de sesión, una autenticación social o una clave de la API-, tu sistema de seguridad necesita algún concepto de usuario: alguna clase que describa la "cosa" que ha iniciado la sesión.
Sí, el paso 1 de la autenticación es crear una clase User
. ¡Y hay un comando que puede ayudarnos! Busca tu terminal y ejecuta:
symfony console make:user
Como recordatorio, symfony console
es sólo un atajo para bin/console
... pero como estoy usando la integración de Docker con el servidor web Symfony, llamar a symfony console
permite al binario symfony
inyectar algunas variables de entorno que apuntan a la base de datos de Docker. No importará para este comando, pero sí para cualquier comando que hable con la base de datos.
Bien, primera pregunta:
El nombre de la clase de usuario
Normalmente, será User
... aunque sería mejor utilizar algo comoHumanoidEntity
. Si la "cosa" que entra en tu sitio se llamaría mejor Company
o University
o Machine
, utiliza ese nombre aquí.
¿Quieres almacenar los datos de los usuarios en la base de datos a través de Doctrine?
Para nosotros: es un sí rotundo... pero no es un requisito. Tus datos de usuario pueden estar almacenados en algún otro servidor... aunque incluso en ese caso, a menudo es conveniente almacenar algunos datos adicionales en tu base de datos local... en cuyo caso también dirías que sí aquí.
Siguiente:
Introduce un nombre de propiedad que será el nombre de visualización único para el usuario.
Yo voy a utilizar email
. Esto no es tan importante, y explicaré cómo se utiliza en unos minutos. Por último:
¿Necesitará esta aplicación hacer un hash y comprobar las contraseñas de los usuarios?
Sólo tienes que decir que sí si será responsabilidad de tu aplicación comprobar la contraseña del usuario cuando se conecte. Vamos a hacer esto... pero voy a decir que no. Lo añadiremos manualmente un poco más tarde.
Pulsa enter y... ¡listo!
La clase y la entidad de usuario
Bien. ¿Qué ha hecho esto? En primer lugar, ha creado una entidad User
y una UserRepository
... exactamente lo mismo que se obtiene normalmente al ejecutar make:entity
. Vamos a ver esa nueva clase User
: src/Entity/User.php
:
Show Lines
|
// ... lines 1 - 2 |
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 | |
{ | |
/** | |
* @ORM\Id | |
* @ORM\GeneratedValue | |
* @ORM\Column(type="integer") | |
*/ | |
private $id; | |
/** | |
* @ORM\Column(type="string", length=180, unique=true) | |
*/ | |
private $email; | |
/** | |
* @ORM\Column(type="json") | |
*/ | |
private $roles = []; | |
Show Lines
|
// ... lines 31 - 113 |
} |
En primer lugar, se trata de una entidad normal y aburrida de Doctrine: tiene anotaciones -o quizás atributos de PHP 8 para ti- y un id. Es... sólo una entidad: no tiene nada de especial.
Interfaz de usuario y métodos obsoletos
Lo único que le importa a Symfony es que tu clase de usuario implementeUserInterface
. Mantén pulsado Command
o Ctrl
y haz clic para saltar al código del núcleo para ver esto.
Esta interfaz realmente sólo tiene 3 métodos: getUserIdentifier()
el que ves documentado encima de la interfaz, getRoles()
... y otro más abajo llamado eraseCredentials()
. Si estás confundido sobre por qué estoy omitiendo todos estos otros métodos, es porque están obsoletos. En Symfony 6, esta interfaz sólo tendrá esos 3: getUserIdentifier()
, getRoles()
yeraseCredentials()
.
En nuestra clase User
, si te desplazas hacia abajo, el comando make:user
implementó todo esto por nosotros. Gracias a cómo hemos respondido a una de sus preguntas,getUserIdentier()
devuelve el correo electrónico:
Show Lines
|
// ... lines 1 - 12 |
class User implements UserInterface | |
{ | |
Show Lines
|
// ... lines 15 - 48 |
/** | |
* A visual identifier that represents this user. | |
* | |
* @see UserInterface | |
*/ | |
public function getUserIdentifier(): string | |
{ | |
return (string) $this->email; | |
} | |
Show Lines
|
// ... lines 58 - 113 |
} |
Esto... no es demasiado importante: es sobre todo una representación visual de tu objeto Usuario... se utiliza en la barra de herramientas de depuración de la web... y en algunos sistemas opcionales, como el sistema "recuérdame".
Si estás usando Symfony 5 como yo, te darás cuenta de que los métodos obsoletos se siguen generando. Son necesarios sólo por compatibilidad con versiones anteriores, y puedes eliminarlos una vez que estés en Symfony 6.
El método getRoles()
se ocupa de los permisos:
Show Lines
|
// ... lines 1 - 12 |
class User implements UserInterface | |
{ | |
Show Lines
|
// ... lines 15 - 66 |
/** | |
* @see UserInterface | |
*/ | |
public function getRoles(): array | |
{ | |
$roles = $this->roles; | |
// guarantee every user at least has ROLE_USER | |
$roles[] = 'ROLE_USER'; | |
return array_unique($roles); | |
} | |
Show Lines
|
// ... lines 78 - 113 |
} |
más adelante se hablará de ello. Además, getPassword()
y getSalt()
están obsoletos:
Show Lines
|
// ... lines 1 - 12 |
class User implements UserInterface | |
{ | |
Show Lines
|
// ... lines 15 - 85 |
/** | |
* This method can be removed in Symfony 6.0 - is not needed for apps that do not check user passwords. | |
* | |
* @see PasswordAuthenticatedUserInterface | |
*/ | |
public function getPassword(): ?string | |
{ | |
return null; | |
} | |
/** | |
* This method can be removed in Symfony 6.0 - is not needed for apps that do not check user passwords. | |
* | |
* @see UserInterface | |
*/ | |
public function getSalt(): ?string | |
{ | |
return null; | |
} | |
Show Lines
|
// ... lines 105 - 113 |
} |
Seguirás necesitando el método getPassword()
si compruebas las contraseñas en tu sitio, pero ya lo veremos más adelante. Por último, eraseCredentials()
forma parte deUserInterface
:
Show Lines
|
// ... lines 1 - 12 |
class User implements UserInterface | |
{ | |
Show Lines
|
// ... lines 15 - 105 |
/** | |
* @see UserInterface | |
*/ | |
public function eraseCredentials() | |
{ | |
// If you store any temporary, sensitive data on the user, clear it here | |
// $this->plainPassword = null; | |
} | |
} |
pero no es muy importante y también hablaremos de ello más adelante.
Así que a alto nivel... si ignoras los métodos obsoletos... y el no tan importanteeraseCredentials()
, lo único que debe tener nuestra clase User
es un identificador y un método que devuelva la matriz de roles que debe tener este usuario. Sí... es sobre todo una entidad de User
.
"proveedores": El proveedor de usuarios
El comando make:user
también hizo un ajuste en nuestro archivo security.yaml
: puedes verlo aquí:
security: | |
Show Lines
|
// ... lines 2 - 7 |
providers: | |
# used to reload user from session & other features (e.g. switch_user) | |
app_user_provider: | |
entity: | |
class: App\Entity\User | |
property: email | |
Show Lines
|
// ... lines 14 - 33 |
Añadió lo que se llama un "proveedor de usuario", que es un objeto que sabe cómo cargar tus objetos de usuario... ya sea que cargues esos datos desde una API o desde una base de datos. Como estamos usando Doctrine, podemos usar el proveedor incorporado entity
: sabe cómo obtener nuestros usuarios de la base de datos usando la propiedad email
.
Quería que vieras este cambio... pero el proveedor de usuarios no es importante todavía. Te mostraré exactamente cómo y dónde se utiliza a medida que avancemos.
A continuación: tenemos un control total sobre el aspecto de nuestra clase User
. ¡El poder! Así que vamos a añadirle un campo personalizado y a cargar nuestra base de datos con un buen conjunto de usuarios ficticios.
35 Comments
Hey odds,
Yep, that's the reserved word in SQL, it may work though with ticks, i.e. SELECT * FROM
`user
`
, but better to avoid it, i.e. rename, though not the DB but the user
table, e.g. to something like app_user
, etc.
Cheers!
Hey @discipolat
Usually inheritance is not a good idea because it makes the code base more complex. I recommend using composition or PHP treats to share some common code among your user type classes. Also, applying inheritance to Doctrine is not always easy. So, unless you got a strong reason to use inheritance I suggest avoiding it
Cheers!
Are you going to use the same login form and route for everyone? You'll need to write a custom authenticator and perhaps an extra field to determine what type of user is logging in
https://symfony.com/doc/current/security/custom_authenticator.html
Hi Everyone
I am trying to make some demo apps close to the real-life...so I have question:
In my demo app, I have 3 types of users: Users, Companies and Freelancers, so I have those 3 entities.
Companies and Freelancers will have to register (email, password etc), while for the users there will be no registration form, they will be added using the User factory ( I will have one user only).
However, now if I run **bin/console make:registration-form**
, console is considering that I want to make registration form for the User entity, which I do have, but I actually want to make registration-form for the Companies and Freelancers.
How can I generate registration forms for the Companies and Freelancers?
Thanks in advance
Hey @t5810 ,
When you call that bin/console make:registration-form
- it will ask you "Enter the User class that you want to create during registration". So, I suppose you would need to specify the corrrect entity there instead of the default App\Entity\User
, i.e. specify there namespace of those Company/Freelancer entities and that should work I suppose :)
Cheers!
HI Victor
Thanks for the prompt replay. When I type symfony console make:registration-form
console reply with:
Creating a registration form for App\Entity\User
Do you want to add a #[UniqueEntity] validation attribute to your User class to make sure duplicate accounts aren't created? (yes/no) [yes]:
So, I have no way to change the entity for which the registration form will be made.
The only option that I have is: to commit all my changes, and then to proceed with making the changes for the user, and then manually to make the same changes for the Companies and Freelancers, and to revert the changes for the Users.
However, I am trying to learn best practices of using Symfony, and I am not sure if this approach may be considered as such.
Any suggestion will be deeply appreciated.
Regards
Hey @t5810 ,
Hm, probably you're not on the latest version of Maker bundle. Try to update Maker bundle to the latest version first and try again :)
Otherwise, you can generate it for the User entity but then manually tweak the generated code replacing User with other entity you need - also a valid way because the generated code is just a boilerplate that is suppose to be changed for you specific needs :)
Cheers!
Hi Victor
I upgrade the symfony/maker-bundle to the latest version 1.50.0, same command:
symfony console make:registration-form
Same reply:
Creating a registration form for App\Entity\User
Do you want to add a #[UniqueEntity] validation attribute to your User class to make sure duplicate accounts aren't created? (yes/no) [yes]:
By the way, I have send you an invitation to the repository, to avoid making this thread too long if that can help somehow to resolve the issue.
Regards
Hey @t5810 ,
Hm, I'm also on the latest v1.50.0, but in the very beginning the command asks me:
Enter the User class that you want to create during registration (e.g. App\Entity\User) [App\Entity\User]:
Probably try to go further, it might be the next (2nd) question for you... because I do have that UniqueEntity
and that's why probably it does not ask me :)
Unfortunately, we do not have the bandwidth to look at personal project issues. Anyway, as I said, you can generate a boilerplate code with this command and then change the entity to whatever you want in it.
Cheers!
Hi Victor
Thanks for your advice. I will create the code for the user and then manually create the code for the rest of the entities that I need.
Thanks.
Hey @t5810 ,
Yeah, sounds like a good strategy. Maker bundle is meant to be a helper that will generate some boilerplate code for you anyway, but you would need to tweak it eventually.
Cheers!
at the moment of migrating the migration via symfony console doctrine:migration:migrate
, I get the error
exception occurred while executing a query: SQLSTATE[23502]: Not null violation: 7 ERROR: column "email" of relation "user" contains null values"
In ExceptionConverter.php line 47:
An exception occurred while executing a query: SQLSTATE[23502]: Not null violation: 7 ERRO
R: column "email" of relation "user" contains null values
I there a manual change to be made on the generated code here?
Hey @reflex!
Hmmm. Is the failing migration fully creating the new user
table? Or does it somehow exist and you're altering it? If I'm reading this correctly, the error indicates that there is already a user
table... and this user
table already has rows in it. So, when the email
column is added (which does not allow null), it doesn't know what value to put for those.
If I'm correct, the fix for this is to either:
A) If you don't have anything deployed to production yet, this isn't a real problem as you won't have this situation on production where you already have rows in your user
table. Just drop your database entirely and restart:
symfony console doctrine:database:drop --force
symfony console doctrine:database:create
symfony console doctrine:migrations:migrate
B) If your user
table IS already on production, you'll need to be a little fancier:
1) Modify the migration to allow email
to be null
2) Add a new line after this in the migration that assigns every existing user some value for their email
column (no idea how you would do this - it depends on your business logic).
3) Run the migration
4) Ron make:migration
again: it will generate a final migration that will simply change the email
column to no allow null.
Let me know if this helps :)
Cheers!
thanks a lot,
I am not sure about the real root cause, but I tried this fix multiple times until it worked,
after executing
symfony console doctrine:database:drop --force
symfony console doctrine:database:create
symfony console doctrine:migrations:migrate
I had some different error messages like An exception occurred while executing a query: SQLSTATE[42704]: Undefined object: 7 ERROR: index "uniq_8d93d6495e23 <br /> 7e06" does not exist
and An exception occurred in the driver: SQLSTATE[08006] [7] FATAL: database "app" does not exist
similar to this one :
https://stackoverflow.com/questions/27915200/cannot-create-postgres-database-with-doctrine
so I just reverted the repo to the state before installing doctrine and security bundle, and retried going step by step again, things seem to work fine now, thanks for the suggestion and sorry for the late reply!!
Woohoo! Sometimes there are just weird gremlins in the code - good thinking to back up :).
Hello,
can someone explain me how to proceed to use an already existing (e.g. customer_main
) instead of the user table.
The columns here are email
and main_password
.
Thank you!
Hey Rufnex,
My apologies, but I didn't understand your question. Do you mean how to define a custom table/field name?
Cheers!
i still don't understand how the security logic works exactly. if you use it, a user table is created. But if I already have a table with user data that has a different name and the password field is different. how would I have to adjust the classes?
You can re-use your existing user's table, you only need to specify the name of it
#[ORM\Table(name: 'your_table_name')]
class User
{
#[ORM\Column(name: 'field_name')]
private string $password;
}
Does it answer your question?
Sounds easy ..
To rename the entire class to e.g. MyUserClass is not a good idea?
I will try it. Thank you!
you can name whatever you want to your user class as long as it implements the UserInterface
Cheers!
After i playe arround the whole day, no i'm clear with auth ;o) Thank you.
Hello, is there a guide to migrate from the old system to the new one?
I find weird deprecating the User::getUsername() method, for example I use it in twig templates to render the username, I don't see much sense in renaming it to getUserIdentifier, and rename the db column, but I can leave it and the deprecation notice should go away after 6.0, right?
Hey The_nuts,
Yeah, as soon as you implemented that new getUserIdentifier() - you definitely may keep that getUsername() method as well. The new system will use the getUserIdentifier(), but in your custom code you may continue using getUsername() of course. Actually, I think the deprecation should gone right after that method gone from the interface where it's deprecated.
Unfortunately, no the exact guide about how to migrate from old system to the new one, but we covered it partially in this course, so you basically should port your current system into one we shows in this course.
I hope this helps!
Cheers!
Hey Franck,
I think the first option should be better IMO, and it also will lead to less entities in your project, but really it may depends on your specific case, i.e. how different data should be for those different account types, how many data you will store there, etc. The best would be to implement both cases and profile with a special tool like Blackfire.io that will give you some numbers and you will see what fits better for you. We have a separate course about this tool btw: https://symfonycasts.com/sc...
Cheers!
Hi You!
Whats about Docker integration with the Symfony web server, I mean, whats is this?
I'm not php installled on my computer, I only use Docker, but I can't do this with your 'code along with me' because I don't know hot to create tthe JS enviroment.
Hey vespino_rojo!
The Docker + Symfony web server integration is kind of... "half Docker". It uses Docker to launch any services you have - e.g. database, Elasticsearch, etc - but you still have php installed locally and Node if you want to build the web assets. We're thinking of providing some Docker configuration to the code downloads to make life easier for users that want to use full Docker, but we don't have that yet.
Oh, but about the JS assets. You do not need to build the JS in this tutorial. Because this tutorial is all about security (and not JS stuff), we shipped the pre-built JS & CSS files you need in the public/build directory. So once you launch your web server, those files will simply be there: there is no need to run Webpack/Encore or anything else. We list this as an "optional" step in the README... and it really is optional - you an skip it entirely.
Let me know if that helps!
Cheers!
Hello, I'll you guys post more videos this week?
Hey Gustavo D. !
Yup - one more tomorrow - then we'll start again next week. We usually do one per day - we MAY go faster on this tutorial, but I'm not sure yet. I know, it sucks when the tutorial first comes out and things are slow - apologies!
Cheers!
Hi! Can you record a video that will show how to show a list of everyone I invited via a referral link on the site?
Hey @Работа На дому Интернет работа!
That, unfortunately, will not make it into this video :). But here is how I would implement that. A referral link is fairly simple. First, on your User class, you would probably have something like a referralCode property that stores some random, unique string. This is used to build the referral link. The route for that page would look something like /refer/{referralCode}. In the controller, I would store the referral code in the session and redirect to the registration page. Then, in the registration controller - right after success, but before redirecting - I would check to see if there is a referral code in the session. If there is, I would find which User that referral code belongs to and update something in the database to track this. For example, on the User class, you could add a referredBy ManyToOne to User. So, on the NEW User record, you would call $newUser->setReferredBy($referralUser)
where $referralUser is the User that the referralCode belongs to.
Good luck!
"Houston: no signs of life"
Start the conversation!
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": ">=8.1",
"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.21.6
"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
}
}
I have a small problem with naming the user database 'User' on PostgreSQL. When I want to get the users with
I do not get the users from my app. Supposedly 'user' is a reserved keyword in PostgreSQL. Should I rename my database or is there another solution?