Buy

I just found out that giving everyone the same password - iliketurtles - is apparently not a great security system. Let's give each user their own password. Again, in your security setup, you might not be responsible for storing and checking passwords. Skip this if it doesn't apply to you.

In User, add a private $password that will eventually store the encoded password. Give it the @ORM\Column annotation:

75 lines src/AppBundle/Entity/User.php
... lines 1 - 12
class User implements UserInterface
{
... lines 15 - 26
/**
* The encoded password
*
* @ORM\Column(type="string")
*/
private $password;
... lines 33 - 73
}

Now, remember the three methods from UserInterface that we left blank?

63 lines src/AppBundle/Entity/User.php
... lines 1 - 12
class User implements UserInterface
{
... lines 15 - 37
public function getPassword()
{
// leaving blank - I don't need/have a password!
}
public function getSalt()
{
// leaving blank - I don't need/have a password!
}
public function eraseCredentials()
{
// leaving blank - I don't need/have a password!
}
... lines 52 - 61
}

It's finally their time to shine. In getPassword(), return $this->password:

75 lines src/AppBundle/Entity/User.php
... lines 1 - 12
class User implements UserInterface
{
... lines 15 - 44
public function getPassword()
{
return $this->password;
}
... lines 49 - 73
}

But keep getSalt() blank: we're going to use the bcrypt algorithm, which has a built-in mechanism to salt passwords.

Use the "Code"->"Generate" menu to generate the setter for password:

75 lines src/AppBundle/Entity/User.php
... lines 1 - 12
class User implements UserInterface
{
... lines 15 - 69
public function setPassword($password)
{
$this->password = $password;
}
}

And next, go make that migration:

./bin/console doctrine:migrations:diff

I should check that file, but let's go for it:

./bin/console doctrine:migrations:migrate

Perfect.

Handling the Plain Password

Here's the plan: we'll start with a plain text password, encrypt it through the bcrypt algorithm and store that on the password property.

How? The best way is to set the plain-text password on the User and encode it automatically via a Doctrine listener when it saves.

To do that, add a new property on User called plainPassword. But wait! Don't persist this with Doctrine: we will of course never store plain-text passwords:

92 lines src/AppBundle/Entity/User.php
... lines 1 - 12
class User implements UserInterface
{
... lines 15 - 33
/**
* A non-persisted field that's used to create the encoded password.
*
* @var string
*/
private $plainPassword;
... lines 40 - 90
}

This is just a temporary-storage place during a single request.

Next, at the bottom, use Command+N or the "Code"->"Generate" menu to generate the getters and setters for plainPassword:

92 lines src/AppBundle/Entity/User.php
... lines 1 - 12
class User implements UserInterface
{
... lines 15 - 81
public function getPlainPassword()
{
return $this->plainPassword;
}
public function setPlainPassword($plainPassword)
{
$this->plainPassword = $plainPassword;
}
}

Forcing User to look Dirty?

Inside setPlainPassword(), do one more thing: $this->password = null:

95 lines src/AppBundle/Entity/User.php
... lines 1 - 12
class User implements UserInterface
{
... lines 15 - 86
public function setPlainPassword($plainPassword)
{
$this->plainPassword = $plainPassword;
// forces the object to look "dirty" to Doctrine. Avoids
// Doctrine *not* saving this entity, if only plainPassword changes
$this->password = null;
}
}

What?! Yep, this is important. Soon, we'll use a Doctrine listener to read the plainPassword property, encode it, and update password. That means that password will be set to a value before it actually saves: it won't remain null.

So why add this weird line if it basically does nothing? Because Doctrine listeners are not called if Doctrine thinks that an object has not been updated. If you eventually create a "change password" form, then the only property that will be updated is plainPassword. Since this is not persisted, Doctrine will think the object is "un-changed", or "clean". In that case, the listeners will not be called, and the password will not be changed.

But by adding this line, the object will always look like it has been changed, and life will go on like normal.

Anyways, it's a necessary little evil.

Finally, in eraseCredentials(), add $this->plainPassword = null:

95 lines src/AppBundle/Entity/User.php
... lines 1 - 12
class User implements UserInterface
{
... lines 15 - 61
public function eraseCredentials()
{
$this->plainPassword = null;
}
... lines 66 - 93
}

Symfony calls this after logging in, and it's just a minor security measure to prevent the plain-text password from being accidentally saved anywhere.

The User object is perfect. Let's add the listener.

Leave a comment!

  • 2017-11-06 weaverryan

    Hi Ahmed Bhs!

    Ah, very good question! I'm not using the salt for one very important reason: in security.yml, we configure out encoder to us the bcrypt algorithm. This algorithm does salting automatically: it includes the a salt automatically in the encoded password. So, we're returning null from getSalt() only because this encode does not require us to pass *it* a salt, it computes the salt automatically (which I believe is more secure anyways).

    So, it's good news! You *are* using a salt... but you didn't need to do any work for it ;).

    Cheers!

  • 2017-11-05 Ahmed Bhs

    Thank you for this amazing tuto <3
    I've noticed that you didn't use the salt mécanisme, I don't know why? all what I know is the primary function of salts is to defend against dictionary attacks or against its hashed equivalent, a pre-computed rainbow table attack.
    So we just use salts to encode our password for more sécurity.

    I'm asking how we can use salts ? maybe, some explanation about the mécanisme ...

  • 2017-08-08 Diego Aguiar

    Cool, you are not missing it, but, in order to work, it must be the name of an unique property (e.g. id, slug). So just change "usrid" to "id" or to whatever you named your id property on User class

    Cheers!

  • 2017-08-07 Moises Cano

    /**
    * @Route("/edit/{usrid}", name="user_edit")
    */

  • 2017-08-07 Diego Aguiar

    Hey Moises Cano

    Can you show me how you defined your Route ? Look's like you are missing the User's wildcard

    Cheers!

  • 2017-08-07 Moises Cano

    Oh yea. I see that now. Is there another video showing how to edit a user information? I tried copying the addAction and added this....


    public function editAction(Request $request, User $user)
    {
    $form = $this->createForm(UserRegistrationForm::class, $user);

    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
    $user = $form->getData();

    $em = $this->getDoctrine()->getManager();
    $em->persist($user);
    $em->flush();

    $this->addFlash('success', $user->getUsername(). ' Updated');

    return $this->redirectToRoute('homepage');
    }

    return $this->render('user/edit.html.twig', [
    'form' => $form->createView()
    ]);
    }

    I get an error Unable to guess how to get a Doctrine instance from the request information for parameter "user".

  • 2017-08-07 Victor Bocharsky

    Hey Moises,

    Note that we do not add @ORM\Column() annotation for plainPassword field, so the column won't be added to the User table.

    Cheers!

  • 2017-08-06 Moises Cano

    After we define $plainPassword, then we can never migrate this class without Doctrine adding a plainpassword column to our User table?

  • 2017-08-04 Diego Aguiar

    Hey Ángel Manuel Marqués Ruiz

    Great question, and you are right, it may lead to weird behaviour, so you could add a "updatedAt" field to your entity, and change that field

    Cheers!

  • 2017-08-04 Ángel Manuel Marqués Ruiz

    I've noticed that setting password to null in setPlainPassword method may lead to unwanted behaviour:
    https://symfony.com/doc/cur...

    It makes the constraint UserPassword to always return incorrect password in my change password form:
    https://justpaste.it/19pfj

    Is there any work around?

  • 2017-03-04 phphozpter

    Hi Victor,

    Thanks for reply, It works now.

  • 2017-02-27 Victor Bocharsky

    Hi Jayson,

    Are you coding from scratch? Or are you in the "finish/" directory of downloaded course code? Please, search for "setPlainPassword" and "setPassword" calls in your code base and ensure you call these methods in right places. Probably you did some extra calls of these methods for debugging and forget to remove it later.

    Cheers!

  • 2017-02-25 phphozpter

    Hi,

    The password was changing every time I update the entity. Even though the password is not included in the form.

  • 2017-02-14 Blueblazer172

    Victor Bocharsky Thanks for that :) btw awasome tuts Ryan. Totaly worth every penny :) Really like your way of teaching :-) Best instructor for learning Symfony the *funny* and safe way

  • 2017-02-14 Victor Bocharsky

    Yo Hannes,

    Yes, it's the best encrypt algorithm which is suggested by Symfony: http://symfony.com/doc/curr...

    And yes, you can use salt to make your encrypted password more robust - just generate salt in User::__construct() and tweak getSalt() method to return it.

    Cheers!

  • 2017-02-14 Blueblazer172

    btw is bcrypt still safe ?
    and are there any other possibilities to salt the password ?