Users Need Passwords (plainPassword)
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
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:
// ... 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?
// ... 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
:
// ... 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
:
// ... 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:
// ... 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
:
// ... 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
:
// ... 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
:
// ... 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.
btw is bcrypt still safe ?
and are there any other possibilities to salt the password ?