Buy Access to Course
09.

Users Need Passwords (plainPassword)

Share this awesome video!

|

Keep on Learning!

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.