10 - Récupération de mot de passe sans bundle (Symfony 6)

Catégories : Symfony

Série : Symfony 6

Fichiers : https://github.com/NouvelleTechno/e-commerce-Symfony-6

Mots-clés : Symfony symfony6 password passe récupération

401 lectures

Auteur : user Benoit

Date :

Partager

Partager sur Facebook Partager sur Twitter Partager sur LinkedIn

Dans ce 10ème tutoriel de la série sur Symfony 6, nous allons mettre en place le système de récupération de mot de passe sans utiliser de Bundle.

Nous allons ajouter un bouton “Mot de passe oublié” sur la page de connexion, qui permettra de demander la réinitialisation du mot de passe de l'utilisateur.

Préparation de l'entité Users

Dans la vidéo précédente nous avons ajouté une propriété is_verified dans notre entité Users. Cette fois, nous allons ajouter une propriété resetToken en ajoutant les lignes suivantes dans l'entité

// Ajouter ce code dans l'entité Users
 
#[ORM\Column(type: 'string', length: 100)]
private $resetToken;

// ...

public function getResetToken(): ?string
{
    return $this->resetToken;
}

public function setResetToken(?string $resetToken): self
{
    $this->resetToken = $resetToken;

    return $this;
}

Nous pourrons y stocker le token d'authentification de l'utilisateur

Ne pas oublier de mettre à jour la base de données au moyen des commandes suivantes

symfony console make:migration
symfony console doctrine:migrations:migrate

Création de la route “Mot de passe oublié”

Nous allons ensuite créer la route qui sera appelée par le bouton “Mot de passe oublié” en créant une méthode dans le SecurityController.

Cette route aura l'adresse /oubli-pass et s'appellera forgotten_password, nous aurons donc l'annotation suivante sur la méthode

#[Route('/oubli-pass', name:'forgotten_password')]
public function forgottenPassword()
{
}

Nous pourrons ensuite ajouter la ligne permettant de faire le rendu de la vue comme ceci

#[Route('/oubli-pass', name:'forgotten_password')]
public function forgottenPassword(): Response
{
    return $this->render('security/reset_password_request.html.twig');
}

Nous pouvons enfin ajouter le lien dans notre formulaire de connexion

<a href="{{ path('forgotten_password') }}" class="btn btn-secondary mt-3">Mot de passe oublié</a>

Formulaire de demande de réinitialisation de mot de passe

Création du formulaire

Nous allons avoir besoin de deux formulaires. Le 1er demandera une adresse e-mail.

Nous allons utiliser le Maker Bundle pour créer le formulaire en utilisant la commande suivante

symfony console make:form

Puis nommer le formulaire ResetPasswordRequestFormType, celui-ci ne sera pas lié à une entité.

Nous allons modifier le contenu du fichier pour y insérer ceci

<?php

namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ResetPasswordRequestFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('email', EmailType::class, [
                'label' => 'Entrez votre e-mail',
                'attr' => [
                    'placeholder' => 'exemple@email.fr',
                    'class' => 'form-control'
                ]
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            // Configure your form options here
        ]);
    }
}

Traitement du formulaire

Nous allons ajouter le traitement du formulaire dans notre contrôleur.

Dans cette logique, nous allons vérifier si un utilisateur existe avec l'e-mail fourni, puis nous allons générer un token au moyen de la TokenGeneratorInterface, puis nous enverrons ce token dans un lien par e-mail

    #[Route('/oubli-pass', name:'forgotten_password')]
    public function forgottenPassword(
        Request $request,
        UsersRepository $usersRepository,
        TokenGeneratorInterface $tokenGenerator,
        EntityManagerInterface $entityManager,
        SendMailService $mail
    ): Response
    {
        $form = $this->createForm(ResetPasswordRequestFormType::class);

        $form->handleRequest($request);

        if($form->isSubmitted() && $form->isValid()){
            //On va chercher l'utilisateur par son email
            $user = $usersRepository->findOneByEmail($form->get('email')->getData());

            // On vérifie si on a un utilisateur
            if($user){
                // On génère un token de réinitialisation
                $token = $tokenGenerator->generateToken();
                $user->setResetToken($token);
                $entityManager->persist($user);
                $entityManager->flush();

                // On génère un lien de réinitialisation du mot de passe
                $url = $this->generateUrl('reset_pass', ['token' => $token], UrlGeneratorInterface::ABSOLUTE_URL);
                
                // On crée les données du mail
                $context = compact('url', 'user');

                // Envoi du mail
                $mail->send(
                    'no-reply@e-commerce.fr',
                    $user->getEmail(),
                    'Réinitialisation de mot de passe',
                    'password_reset',
                    $context
                );

                $this->addFlash('success', 'Email envoyé avec succès');
                return $this->redirectToRoute('app_login');
            }
            // $user est null
            $this->addFlash('danger', 'Un problème est survenu');
            return $this->redirectToRoute('app_login');
        }

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

Création de la vue

Nous allons créer le fichier reset_password_request.html.twig qui contiendra le code du formulaire comme suit

{% extends 'base.html.twig' %}

{% block title %}Réinitialisation de mot de passe{% endblock %}

{% block body %}
<section class="container my-3">
    <div class="row">
        <div class="col">
            <h1>Réinitialisation de mot de passe</h1>
            {{ form_start(requestPassForm) }}
                {{ form_row(requestPassForm.email) }}
                <button type="submit" class="btn btn-primary">Envoyer</button>
            {{ form_end(requestPassForm) }}
        </div>
    </div>
</section>

{% endblock %}

Contenu de l'e-mail

Enfin, nous créons le fichier twig de l'e-mail


<p>Bonjour {{ user.firstname }} {{ user.lastname }}</p>
<p>Pour votre demande de réinitialisation de mot de passe, veuillez cliquer sur le lien suivant : <a href="{{ url|raw }}">{{ url|raw }}</a></p>
<p>Merci</p>

Formulaire de réinitialisation de mot de passe

Création du formulaire

Le 2ème formulaire demandera le nouveau mot de passe, si le token est valide.

Nous allons créer le fichier du formulaire sous le nom ResetPasswordFormType et il contiendra le code suivant

<?php

namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ResetPasswordFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('password', PasswordType::class, [
                'label' => 'Entrez votre mot de passe',
                'attr' => [
                    'class' => 'form-control'
                ]
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            // Configure your form options here
        ]);
    }
}

Traitement du formulaire

Nous allons mettre en place le traitement du formulaire dans une nouvelle méthode dans le contrôleur SecurityController afin de vérifier le token et de réinitialiser le mot de passe s'il est correct.

    #[Route('/oubli-pass/{token}', name:'reset_pass')]
    public function resetPass(
        string $token,
        Request $request,
        UsersRepository $usersRepository,
        EntityManagerInterface $entityManager,
        UserPasswordHasherInterface $passwordHasher
    ): Response
    {
        // On vérifie si on a ce token dans la base
        $user = $usersRepository->findOneByResetToken($token);
        
        // On vérifie si l'utilisateur existe

        if($user){
            $form = $this->createForm(ResetPasswordFormType::class);

            $form->handleRequest($request);

            if($form->isSubmitted() && $form->isValid()){
                // On efface le token
                $user->setResetToken('');
                
                
// On enregistre le nouveau mot de passe en le hashant
                $user->setPassword(
                    $passwordHasher->hashPassword(
                        $user,
                        $form->get('password')->getData()
                    )
                );
                $entityManager->persist($user);
                $entityManager->flush();

                $this->addFlash('success', 'Mot de passe changé avec succès');
                return $this->redirectToRoute('app_login');
            }

            return $this->render('security/reset_password.html.twig', [
                'passForm' => $form->createView()
            ]);
        }
        
        // Si le token est invalide on redirige vers le login
        $this->addFlash('danger', 'Jeton invalide');
        return $this->redirectToRoute('app_login');
    }

Création de la vue

On crée enfin la vue permettant d'afficher le formulaire

{% extends 'base.html.twig' %}

{% block title %}Réinitialisation de mot de passe{% endblock %}

{% block body %}
<section class="container my-3">
    <div class="row">
        <div class="col">
            <h1>Réinitialisation de mot de passe</h1>
            {{ form_start(passForm) }}
                {{ form_row(passForm.password) }}
                <button type="submit" class="btn btn-primary">Envoyer</button>
            {{ form_end(passForm) }}
        </div>
    </div>
</section>

{% endblock %}

Obtenir de l'aide

Pour obtenir de l'aide, vous pouvez accéder au serveur Guilded pour une entraide par chat.