Skip to content

[Cookbook] Make registration_form follow best practices #6073

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 69 additions & 30 deletions cookbook/doctrine/registration_form.rst
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
.. index::
single: Doctrine; Simple Registration Form
single: Form; Simple Registration Form
single: Security; Simple Registration Form

How to Implement a simple Registration Form
How to Implement a Simple Registration Form
===========================================

Creating a registration form is pretty easy - it *really* means just creating
a form that will update some ``User`` model object (a Doctrine entity in this example)
and then save it.
a form that will update some ``User`` model object (a Doctrine entity in this
example) and then save it.

.. tip::

The popular `FOSUserBundle`_ provides a registration form, reset password form
and other user management functionality.
The popular `FOSUserBundle`_ provides a registration form, reset password
form and other user management functionality.

.. _the-simple-user-model:

The Simple User Entity
----------------------

If you don't already have a ``User`` entity and a working login system,
first start with :doc:`/cookbook/security/entity_provider`.
Expand All @@ -31,11 +37,6 @@ Your ``User`` entity will probably at least have the following fields:
``password``
The encoded password.

``plainPassword``
This field is *not* persisted: (notice no ``@ORM\Column`` above it). It
temporarily stores the plain password from the registration form. This field
can be validated then used to populate the ``password`` field.

With some validation added, your class may look something like this::

// src/AppBundle/Entity/User.php
Expand All @@ -61,7 +62,7 @@ With some validation added, your class may look something like this::
private $id;

/**
* @ORM\Column(type="string", length=255)
* @ORM\Column(type="string", length=255, unique=true)
* @Assert\NotBlank()
* @Assert\Email()
*/
Expand All @@ -73,17 +74,13 @@ With some validation added, your class may look something like this::
*/
private $username;

/**
* @Assert\NotBlank()
* @Assert\Length(max = 4096)
*/
private $plainPassword;

/**
* The below length depends on the "algorithm" you use for encoding
* the password, but this works well with bcrypt
*
* @ORM\Column(type="string", length=64)
* @ORM\Column(type="string", length=255)
* @Assert\NotBlank()
* @Assert\Length(max=4096)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is useless imo as we do not store the plain password.

*/
private $password;

Expand All @@ -109,14 +106,14 @@ With some validation added, your class may look something like this::
$this->username = $username;
}

public function getPlainPassword()
public function getPassword()
{
return $this->plainPassword;
return $this->password;
}

public function setPlainPassword($password)
public function setPassword($password)
{
$this->plainPassword = $password;
$this->password = $password;
}

public function setPassword($password)
Expand All @@ -135,7 +132,7 @@ example, see the :ref:`Entity Provider <security-crete-user-entity>` article.

.. sidebar:: Why the 4096 Password Limit?

Notice that the ``plainPassword`` field has a max length of 4096 characters.
Notice that the ``password`` field has a max length of 4096 characters.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole paragraph must be reworded to reflect the fact that we only store the hashed (encrypted) password.

For security purposes (`CVE-2013-5750`_), Symfony limits the plain password
length to 4096 characters when encoding it. Adding this constraint makes
sure that your form will give a validation error if anyone tries a super-long
Expand All @@ -146,8 +143,10 @@ example, see the :ref:`Entity Provider <security-crete-user-entity>` article.
only place where you don't need to worry about this is your login form,
since Symfony's Security component handles this for you.

Create a Form for the Model
---------------------------
.. _create-a-form-for-the-model:

Create a Form for the Entity
----------------------------

Next, create the form for the ``User`` entity::

Expand All @@ -165,7 +164,7 @@ Next, create the form for the ``User`` entity::
$builder
->add('email', 'email')
->add('username', 'text')
->add('plainPassword', 'repeated', array(
->add('password', 'repeated', array(
'type' => 'password',
'first_options' => array('label' => 'Password'),
'second_options' => array('label' => 'Repeat Password'),
Expand All @@ -186,8 +185,8 @@ Next, create the form for the ``User`` entity::
}
}

There are just three fields: ``email``, ``username`` and ``plainPassword``
(repeated to confirm the entered password).
There are just three fields: ``email``, ``username`` and ``password`` (repeated
to confirm the entered password).

.. tip::

Expand Down Expand Up @@ -224,8 +223,7 @@ controller for displaying the registration form::
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// 3) Encode the password (you could also do this via Doctrine listener)
$encoder = $this->get('security.encoder_factory')
->getEncoder($user);
$encoder = $this->get('security.encoder_factory')->getEncoder($user);
$password = $encoder->encodePassword($user->getPlainPassword(), $user->getSalt());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The form handling doesn't work as is as we do not have a property for the plain password in the registration form. I think we should use a DTO class to back the form instead.

$user->setPassword($password);

Expand All @@ -249,6 +247,47 @@ controller for displaying the registration form::
}
}

Storing plain-text passwords is bad practice, so before saving the user data
into the database the submitted plain-text password is replaced by an encoded
one. To define the algorithm used to encode the password configure the encoder
in the security configuration:

.. configuration-block::

.. code-block:: yaml

# app/config/security.yml
security:
encoders:
AppBundle\Entity\User: bcrypt

.. code-block:: xml

<!-- app/config/security.xml -->
<?xml version="1.0" charset="UTF-8" ?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:srv="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<config>
<encoder class="AppBundle\Entity\User">bcrypt</encoder>
</config>
</srv:container>

.. code-block:: php

// app/config/security.php
$container->loadFromExtension('security', array(
'encoders' => array(
'AppBundle\Entity\User' => 'bcrypt',
),
));

In this case the recommended ``bcrypt`` algorithm is used. To learn more
about how to encode the users password have a look into the
:ref:`security chapter <book-security-encoding-user-password>`.

.. note::

If you decide to NOT use annotation routing (shown above), then you'll
Expand Down