Tip: have a look at the Forgot Password OWASP Cheat Sheet.
Installing this bundle can be done easily through Composer:
composer require tilleuls/forgot-password-bundle
If you're using Flex, all configuration is already done. You can customize it in
config/packages/coop_tilleuls_forgot_password.yaml
file. You can directly go to
Create your entity chapter.
Register this bundle in your kernel:
// config/bundles.php
return [
// ...
CoopTilleuls\ForgotPasswordBundle\CoopTilleulsForgotPasswordBundle::class => ['all' => true],
];
Load routing:
# config/routes/coop_tilleuls_forgot_password.yaml
coop_tilleuls_forgot_password:
resource: .
type: coop_tilleuls_forgot_password
prefix: '/forgot-password'
It provides the following routes:
POST /forgot-password/
: receives user email (or custom field configured throughemail_field
)GET /forgot-password/{tokenValue}
: validates the token and returns it ( cf. Overriding the GET /forgot-password/{tokenValue} response)POST /forgot-password/{tokenValue}
: update user password (or custom field configured throughpassword_field
)
For versions >= 1.5.0
You can manage multi providers. If you want to choose a specific provider, you must set header parameter
FP-provider => {provider}
for these endpoints:
POST /forgot-password/
GET /forgot-password/{tokenValue}
POST /forgot-password/{tokenValue}
This bundle provides an abstract mapped superclass, you'll have to create your own PasswordToken
entity for your
project:
// src/Entity/PasswordToken.php
namespace App\Entity;
use CoopTilleuls\ForgotPasswordBundle\Entity\AbstractPasswordToken;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class PasswordToken extends AbstractPasswordToken
{
#[ORM\Id]
#[ORM\Column(type: 'integer', nullable: false)]
#[ORM\GeneratedValue(strategy: 'AUTO')]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: User::class)]
#[ORM\JoinColumn(nullable: false)]
private ?User $user = null;
public function getId(): ?int
{
return $this->id;
}
public function getUser(): ?User
{
return $this->user;
}
/**
* @param User $user
*/
public function setUser($user): self
{
$this->user = $user;
return $this;
}
}
For version >= 1.5.0
If you need to manage forgotten password for many users, you'll have to create a PasswordToken entity for each user:
// src/Entity/PasswordToken.php
namespace App\Entity;
use CoopTilleuls\ForgotPasswordBundle\Entity\AbstractPasswordToken;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class PasswordToken extends AbstractPasswordToken
{
#[ORM\Id]
#[ORM\Column(type: 'integer', nullable: false)]
#[ORM\GeneratedValue(strategy: 'AUTO')]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: User::class)]
#[ORM\JoinColumn(nullable: false, name: 'admin_id')]
private $admin;
public function getId(): ?int
{
return $this->id;
}
public function getUser()
{
return $this->admin;
}
public function setUser($admin): void
{
$this->admin = $admin;
}
}
// src/Entity/PasswordAdminToken.php
namespace App\Entity;
use CoopTilleuls\ForgotPasswordBundle\Entity\AbstractPasswordToken;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class PasswordAdminToken extends AbstractPasswordToken
{
#[ORM\Id]
#[ORM\Column(type: 'integer', nullable: false)]
#[ORM\GeneratedValue(strategy: 'AUTO')]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: Admin::class)]
#[ORM\JoinColumn(nullable: false, name: 'user_id')]
private $user;
public function getId(): ?int
{
return $this->id;
}
public function getUser()
{
return $this->user;
}
public function setUser($user): void
{
$this->user = $user;
}
}
By default, this bundle will look for email
field on user class to retrieve it, will generate a PasswordToken valid
for 1 day, and will set a password
field when sent. Here is the default configuration:
# config/packages/coop_tilleuls_forgot_password.yaml
coop_tilleuls_forgot_password:
password_token:
class: 'App\Entity\PasswordToken' # Token class fully qualified name (required)
expires_in: '1 day' # Token duration (optional, default value)
user_field: 'user' # User property in token class (optional, default value)
serialization_groups: [ ] # Serialization groups used in GET /forgot-password/{tokenValue} (optional, default value)
user:
class: 'App\Entity\User' # User class fully qualified name (required)
email_field: 'email' # Email property in user class (optional, default value)
password_field: 'password' # Password property in user class (optional, default value)
authorized_fields: [ 'email' ] # User properties authorized to reset the password (optional, default value)
use_jms_serializer: false # Switch between symfony's serializer component or JMS Serializer
For version >= 1.5.0
If you have one or many user providers, here is an example of a configuration with two providers:
# config/packages/coop_tilleuls_forgot_password.yaml
coop_tilleuls_forgot_password:
use_jms_serializer: false # Switch between symfony's serializer component or JMS Serializer'
providers: # At least one provider
customer:
default: true
password_token:
class: 'App\Security\Entity\PasswordToken' # Token class fully qualified name (required)
expires_in: '1 day' # Token duration (optional, default value)
user_field: 'user' # User property in token class (optional, default value)
serialization_groups: [ ] # Serialization groups used in GET /forgot-password/{tokenValue} (optional, default value)
user:
class: 'App\Security\Entity\User' # User class fully qualified name (required)
email_field: 'email' # Email property in user class (required)
password_field: 'password' # Password property in user class (required)
authorized_fields: [ 'email' ] # User properties authorized to reset the password (optional, default value)
admin:
password_token:
class: 'App\Security\Entity\PasswordAdminToken' # Token class fully qualified name (required)
expires_in: '4 hours' # Token duration (optional, default value)
user_field: 'user' # User property in token class (optional, default value)
serialization_groups: [ ] # Serialization groups used in GET /forgot-password/{tokenValue} (optional, default value)
user:
class: 'App\Security\Entity\Admin' # User class fully qualified name (required)
email_field: 'username' # Email property in user class (required)
password_field: 'password' # Password property in user class (required)
authorized_fields: [ 'email' ] # User properties authorized to reset the password (optional, default value)
Update your security to allow anonymous users to reset their password:
# config/packages/security.yaml
security:
# ...
firewalls:
# ...
main:
# ...
lazy: true
access_control:
- { path: '^/forgot-password', role: PUBLIC_ACCESS }
# ...
By default, when you send a GET /forgot-password/{tokenValue} request, it serializes the token object in JSON, including the
User object through the relationship, using the coop_tilleuls_forgot_password.password_token.serialization_groups
configuration option.
If you want, for instance, to return an empty response, you can easily override this route by your own:
# config/routes/coop_tilleuls_forgot_password.yaml
# ...
coop_tilleuls_forgot_password.get_token:
path: /forgot-password/{tokenValue}
methods: [ GET ]
defaults:
_controller: App\Controller\GetTokenController
# src/Controller/GetTokenController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
final class GetTokenController
{
public function __invoke(): Response
{
return new Response('', Response::HTTP_NO_CONTENT);
}
}
When a user requests a new password, or resets it, it shouldn't be authenticated. But this is part of your own application.
Read full documentation about how to ensure user is not authenticated.
This bundle provides 3 events allowing you to build your own business:
coop_tilleuls_forgot_password.create_token
: dispatched when a user requests a new password (POST /forgot-password/
)coop_tilleuls_forgot_password.update_password
: dispatched when a user has reset its password (POST /forgot-password/{tokenValue}
)coop_tilleuls_forgot_password.user_not_found
: dispatched when a user was not found (POST /forgot-password/
)
For version >= 1.5.0 :
If you want to choose a specific provider, you must set header parameter FP-provider => {provider}
for these events.
coop_tilleuls_forgot_password.create_token
: password (POST /forgot-password/
)
{
"replace_with_your_email_field": "johndoe@example.com",
}
coop_tilleuls_forgot_password.update_password
: password (POST /forgot-password/{tokenValue}
)
{
"replace_with_your_password_field": "NewPassword",
}
Read full documentation about usage.
By default, this bundle works with Doctrine ORM, but you're free to connect with any system.
Read full documentation about how to connect your manager.
By default, this bundle works uses bin2hex
combined with
random_bytes
to generate the token, but you're free to create your own
TokenGenerator to create your token.
Read full documentation about how to generate your own token.