gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
No matter how your users authenticate - a login form, social authentication, or an API key - your security system needs some concept of a user: some class that describes the "thing" that is logged in.
Yup, step 1 of authentication is to create a User
class. And there's a command that can help us! Find your terminal and run:
symfony console make:user
As a reminder, symfony console
is just a shortcut for bin/console
... but because I'm using the Docker integration with the Symfony web server, calling symfony console
allows the symfony
binary to inject some environment variables that point to the Docker database. It won't matter for this command, but it will matter for any command that talks to the database.
Ok, question one:
The name of the user class
Typically, this will be User
... though it would be cooler to use something like HumanoidEntity
. If the "thing" that logs into your site would be better called a Company
or University
or Machine
, use that name here.
Do you want to store user data in the database via Doctrine?
For us: that's a definite yes... but it's not a requirement. Your user data might be stored on some other server... though even in that case, it's often convenient to store some extra data in your local database... in which case you would also say yes here.
Next:
Enter a property name that will be the unique display name for the user.
I'm going to use email
. This is not that important, and I'll explain how it's used in a few minutes. Finally:
Will this app need to hash and check user passwords?
You only need to say yes if it will be your application's responsibility to check the user's password when they log in. We are going to do this... but I'm going to say "no". We'll add it manually a bit later.
Hit enter and... done!
Okay. What did this do? First, it created a User
entity and a UserRepository
... the exact same stuff you normally get from running make:entity
. Let's go check out that new User
class: src/Entity/User.php
:
... 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 = []; | |
... lines 31 - 113 | |
} |
First and foremost, this is a normal boring Doctrine entity: it has annotations - or maybe PHP 8 attributes for you - and an id. It is... just an entity: there is nothing special about it.
The only thing that Symfony cares about is that your user class implements UserInterface
. Hold Command
or Ctrl
and click to jump way into the core code to see this.
This interface really has just 3 methods: getUserIdentifier()
, which you see documented above the interface, getRoles()
... and another one way down here called eraseCredentials()
. If you're confused about why I'm skipping all of these other methods, it's because they're deprecated. In Symfony 6, this interface will only have those 3: getUserIdentifier()
, getRoles()
and eraseCredentials()
.
In our User
, class, if you scroll down, the make:user
command implemented all of this for us. Thanks to how we answered one of its questions, getUserIdentier()
returns the email:
... lines 1 - 12 | |
class User implements UserInterface | |
{ | |
... lines 15 - 48 | |
/** | |
* A visual identifier that represents this user. | |
* | |
* @see UserInterface | |
*/ | |
public function getUserIdentifier(): string | |
{ | |
return (string) $this->email; | |
} | |
... lines 58 - 113 | |
} |
This... isn't too important: it's mostly just a visual representation of your User object... it's used in the web debug toolbar... and in a few optional systems, like the "remember me" system.
If you're using Symfony 5 like I am, you'll notice that the deprecated methods are still generated. They're needed just for backwards compatibility, and you can delete them once you're on Symfony 6.
The getRoles()
method deals with permissions:
... lines 1 - 12 | |
class User implements UserInterface | |
{ | |
... 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); | |
} | |
... lines 78 - 113 | |
} |
more on that later. And then getPassword()
and getSalt()
are both deprecated:
... lines 1 - 12 | |
class User implements UserInterface | |
{ | |
... 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; | |
} | |
... lines 105 - 113 | |
} |
You will still need a getPassword()
method if you check passwords on your site - but we'll learn about that later. Finally, eraseCredentials()
is part of UserInterface
:
... lines 1 - 12 | |
class User implements UserInterface | |
{ | |
... lines 15 - 105 | |
/** | |
* @see UserInterface | |
*/ | |
public function eraseCredentials() | |
{ | |
// If you store any temporary, sensitive data on the user, clear it here | |
// $this->plainPassword = null; | |
} | |
} |
but it's not very important and we'll also talk about it later.
So at a high level... if you ignore the deprecated methods... and the not-so-important eraseCredentials()
, the only thing that our User
class needs to have is an identifier and a method that returns the array of roles that this user should have. Yup... it's mostly just a User
entity.
The make:user
command also made one tweak to our security.yaml
file: you can see it right here:
security: | |
... 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 | |
... lines 14 - 33 |
It added what's called a "user provider", which is an object that knows how to load your user objects... whether you're loading that data from an API or from a database. Because we're using Doctrine, we get to use the built-in entity
provider: it knows how to fetch our users from the database using the email
property.
I wanted you to see this change... but the user provider isn't important yet. I'll show you exactly how and where it's used as we go along.
Next: we have total control over how our User
class looks. The power! So let's add a custom field to it and then load up our database with a nice set of dummy users.
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!
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!
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!
Hi there . What would you suggest for performance ?
In my database, there are two kind of account : personnel and company. And each of them have specific properties.
1. Two entities : User and Details. the first to store common properties require for registration, and the last one to store details about users;
2.Three entities: User, Personnel_User and Company_User.
Thank's.
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!
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!
// composer.json
{
"require": {
"php": "^7.4.1 || >=8.0",
"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.17.5
"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
}
}
at the moment of migrating the migration via
symfony console doctrine:migration:migrate
, I get the errorI there a manual change to be made on the generated code here?