6 - Envoi du mail de confirmation du compte des utilisateurs (Symfony 7)

Temps de lecture : 20 minutes environ.

Dans ce tutoriel, nous allons apprendre comment envoyer un email d'activation de compte pour vérifier l'adresse email de l'utilisateur qui vient de s'inscrire. Nous utiliserons Docker, Symfony et le service JSON Web Token (JWT) pour réaliser cette fonctionnalité.

Configuration de Mailhog avec Docker

Mailhog est un outil pratique pour intercepter les emails en développement.

Ajouter un service Mailhog à docker-compose.yaml :

version: '3.8'

services:

# Contenu déjà existant

mailhog:
image: mailhog/mailhog
logging:
driver: 'none'
ports:
- 1025:1025 #SMTP
- 8025:8025 #Boite mail

Relancer Docker Compose :

docker-compose down
docker-compose up --build

Mailhog sera accessible à l'adresse http://127.0.0.1:8025.

Configuration du Mailer dans Symfony

Configurer le DSN du mailer dans .env :

MAILER_DSN=smtp://mailhog:1025

Désactiver Symfony Messenger pour les emails :

Dans config/packages/messenger.yaml, commenter la ligne suivante :

# routing:
# 'Symfony\Component\Mailer\Messenger\SendEmailMessage': async

Implémentation du Service d'Envoi d'Email

Créer le service d'email :

Pour envoyer les e-mails, nous allons créer un service qui sera le fichier src/Service/SendEmailService.php :

<?php
namespace App\Service;

use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mailer\MailerInterface;

class SendEmailService
{
public function __construct(private MailerInterface $mailer)
{}

public function send(
string $from,
string $to,
string $subject,
string $template,
array $context
): void
{
// On crée le mail
$email = (new TemplatedEmail())
->from($from)
->to($to)
->subject($subject)
->htmlTemplate("emails/$template.html.twig")
->context($context);

// On envoie le mail
$this->mailer->send($email);
}
}

Créer le template email :

Nous allons écrire le contenu de l'email dans le fichier templates/emails/register.html.twig :

<h1>Activation de votre compte sur Openblog</h1>
<p>Bonjour {{ user.nickname }},</p>
<p>Pour activer votre compte sur OpenBlog cliquez sur le lien ci-dessous.</p>
<p><a href="{{ absolute_url(path('verify_user', {token: token})) }}">Lien</a></p>

<p>Merci</p>

Génération et Vérification du JWT

Cette partie a déjà été traitée dans plusieurs tutoriels. Nous allons créer un jeton (Token) pour authentifier l'utilisateur.

Configurer la clé secrète JWT dans .env :

JWT_SECRET=votre_cle_secrete

Ajouter la clé secrète dans config/services.yaml :

parameters:
app.jwt_secret: '%env(JWT_SECRET)%'

Créer le service JWT :

Créer un fichier src/Service/JWTService.php :

<?php
namespace App\Service;

use DateTimeImmutable;

class JWTService
{
// On génère le token

/**
* Génération du JWT
* @param array $header
* @param array $payload
* @param string $secret
* @param int $validity
* @return string
*/
public function generate(array $header, array $payload, string $secret, int $validity = 10800): string
{
if($validity > 0){
$now = new DateTimeImmutable();
$exp = $now->getTimestamp() + $validity;

$payload['iat'] = $now->getTimestamp();
$payload['exp'] = $exp;
}

// On encode en base64
$base64Header = base64_encode(json_encode($header));
$base64Payload = base64_encode(json_encode($payload));

// On "nettoie" les valeurs encodées (retrait des +, / et =)
$base64Header = str_replace(['+', '/', '='], ['-', '_', ''], $base64Header);
$base64Payload = str_replace(['+', '/', '='], ['-', '_', ''], $base64Payload);

// On génère la signature
$secret = base64_encode($secret);
$signature = hash_hmac('sha256', $base64Header . '.' . $base64Payload, $secret, true);

$base64Signature = base64_encode($signature);

$signature = str_replace(['+', '/', '='], ['-', '_', ''], $base64Signature);

// On crée le token
$jwt = $base64Header . '.' . $base64Payload . '.' . $signature;

return $jwt;
}

//On vérifie que le token est valide (correctement formé)
public function isValid(string $token): bool
{
return preg_match(
'/^[a-zA-Z0-9\-\_\=]+\.[a-zA-Z0-9\-\_\=]+\.[a-zA-Z0-9\-\_\=]+$/',
$token
) === 1;
}

// On récupère le Payload
public function getPayload(string $token): array
{
// On démonte le token
$array = explode('.', $token);

// On décode le Payload
$payload = json_decode(base64_decode($array[1]), true);

return $payload;
}

// On récupère le Header
public function getHeader(string $token): array
{
// On démonte le token
$array = explode('.', $token);

// On décode le Header
$header = json_decode(base64_decode($array[0]), true);

return $header;
}

// On vérifie si le token a expiré
public function isExpired(string $token): bool
{
$payload = $this->getPayload($token);

$now = new DateTimeImmutable();

return $payload['exp'] < $now->getTimestamp();
}

// On vérifie la signature du Token
public function check(string $token, string $secret)
{
// On récupère le header et le payload
$header = $this->getHeader($token);
$payload = $this->getPayload($token);

// On régénère un token
$verifToken = $this->generate($header, $payload, $secret, 0);

return $token === $verifToken;
}
}

Implémentation du Contrôleur d'Inscription

Modifier le contrôleur RegistrationController :

Pour permettre l'envoi de l'e-mail, nous allons modifier le fichier src/Controller/RegistrationController.php en ajoutant une partie dans la méthode register

    #[Route('/register', name: 'app_register')]
public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher, UserAuthenticatorInterface $userAuthenticator, UsersAuthenticator $authenticator, EntityManagerInterface $entityManager, JWTService $jwt, SendEmailService $mail): Response
{
$user = new Users();
$form = $this->createForm(RegistrationFormType::class, $user);
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
// encode the plain password
$user->setPassword(
$userPasswordHasher->hashPassword(
$user,
$form->get('plainPassword')->getData()
)
);

$entityManager->persist($user);
$entityManager->flush();
// do anything else you need here, like send an email

// Générer le token
// Header
$header = [
'typ' => 'JWT',
'alg' => 'HS256'
];

// Payload
$payload = [
'user_id' => $user->getId()
];

// On génère le token
$token = $jwt->generate($header, $payload, $this->getParameter('app.jwtsecret'));

// Envoyer l'e-mail
$mail->send(
'no-reply@openblog.test',
$user->getEmail(),
'Activation de votre compte sur le site OpenBlog',
'register',
compact('user', 'token') // ['user' => $user, 'token'=>$token]
);

$this->addFlash('success', 'Utilisateur inscrit, veuillez cliquer sur le lien reçu pour confirmer votre adresse e-mail');

return $userAuthenticator->authenticateUser(
$user,
$authenticator,
$request
);
}

return $this->render('registration/register.html.twig', [
'registrationForm' => $form->createView(),
]);
}

Nous allons ensuite ajouter la méthode verifUser pour vérifier le token

    #[Route('/verif/{token}', name: 'verify_user')]
public function verifUser($token, JWTService $jwt, UsersRepository $usersRepository, EntityManagerInterface $em): Response
{
// On vérifie si le token est valide (cohérent, pas expiré et signature correcte)
if($jwt->isValid($token) && !$jwt->isExpired($token) && $jwt->check($token, $this->getParameter('app.jwtsecret'))){
// Le token est valide
// On récupère les données (payload)
$payload = $jwt->getPayload($token);

// On récupère le user
$user = $usersRepository->find($payload['user_id']);

// On vérifie qu'on a bien un user et qu'il n'est pas déjà activé
if($user && !$user->isVerified()){
$user->setIsVerified(true);
$em->flush();

$this->addFlash('success', 'Utilisateur activé');
return $this->redirectToRoute('app_main');
}
}
$this->addFlash('danger', 'Le token est invalide ou a expiré');
return $this->redirectToRoute('app_login');
}

Conclusion

Vous avez maintenant mis en place un système d'envoi d'email d'activation de compte avec Symfony. Ce tutoriel couvre les étapes de configuration de Mailhog, la configuration du mailer Symfony, la création de services pour l'envoi d'email et la gestion des tokens JWT. Vous pouvez étendre cette fonctionnalité pour inclure la réinitialisation du mot de passe ou d'autres vérifications.

Obtenir de l'aide

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

6 - Envoi du mail de confirmation du compte des utilisateurs (Symfony 7)
Article publié le

Catégories : Symfony Symfony 7

Mots-clés : Symfony enregistrement inscription emails Symfony 7

Partager : Partager sur Facebook Partager sur Twitter Partager sur LinkedIn