Authentification et inscription

Authentification et inscription

1 mai 2021

Symfony nous propose des outils afin de créer un espace membre complet. Nous allons voir comment mettre en place une authentification utilisateurs, une inscription et une récupération du mot de passe.

Avant d'aller plus loin, assurez-vous que votre base de données MySQL soit à la version 5.7 au minimum, si vous utilisez MySQL bien entendu.

En premier lieu...

Symfony va utiliser un package de sécurité déjà présent sur votre projet. Nous allons nous servir de ce bundle pour créer tout notre système d'authentification utilisateur.

Dans votre terminal de commande, entrez la ligne suivante :

symfony console make:user

# Vous pouvez aussi utiliser une autre commande
# php bin/console make:user

Cette commande nous permet de créer une entité "User". Nous passons par cette commande et non par la commande de création d'entité de base, car Symfony va en profiter pour générer un fichier important et relier l'ensemble avec l'entité "User".

Répondez à la série de questions qui suivra.

Détaillons ces questions/réponses :


The name of the security user class (e.g. User) [User] :
Il s'agit ici de définir un nom pour notre entité. Ayant choisi dans les autres articles sur Symfony d'utiliser le pluriel, je garde le même principe. Je vais donc écrire "User".

Do you want to store user data in the database (via Doctrine)? (yes/no) [yes] :
Souhaitez-vous stocker les données de vos utilisateurs en base de données ? ?... Oui !

Enter a property name that will be the unique "display" name for the user (e.g. email, username, uuid) [email] :
Il nous demande quelle propriété nous voulons utiliser comme nom d'affichage unique pour l'utilisateur : l'e-mail me convient très bien.

Does this app need to hash/check user passwords? (yes/no) [yes] :
Ici, la demande est de savoir si Symfony doit hacher et vérifier les mots de passe utilisateur. Évidemment, pour plus de sécurité, nous répondons yes.


Terminé ! Enfin... presque terminé. ?

Maintenant que l'entité a été générée, il nous faut mettre à jour la base de données :

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

# Vous pouvez aussi utiliser une autre commande
# php bin/console make:migration
# php bin/console doctrine:migrations:migrate

Si vous possédez déjà des entités et que votre base de données est déjà composée de tables SQL, préférez cette commande :

symfony console doctrine:schema:update --force

# Vous pouvez aussi utiliser une autre commande
# php bin/console doctrine:update:schema --force

Authentification

Maintenant que nous avons notre entité "Users" et que notre fichier de configuration "security.yaml" est à jour, nous allons pouvoir mettre en place nos pages de connexion et de déconnexion.

Dans votre terminal, tapez cette commande :

symfony console make:auth

# Vous pouvez aussi utiliser une autre commande
# php bin/console make:auth

Répondez aux différentes questions.

Détaillons un peu le tout ici aussi :


What style of authentication do you want? [Empty authenticator] :
Nous choisissons "1" afin de créer notre formulaire d'identification automatiquement.

The class name of the authenticator to create (e.g. AppCustomAuthenticator) :
On nous demande de choisir un nom au fichier qui se chargera de l'authentification : ça sera "UserAuthenticator".

Choose a name for the controller class (e.g. SecurityController) [SecurityController] :
Même chose ici : nous devons choisir un nom pour le contrôleur qui contient les routes vers le formulaire de connexion et vers la déconnexion. Je choisis celui fourni par défaut : "SecurityController".

Do you want to generate a '/logout' URL? (yes/no) [yes] :
Nous répondons "yes" afin de donner la possibilité à l'utilisateur de se déconnecter.


Trois fichiers ont donc été créés :

  • src/Security/UserAuthenticator.php
  • src/Controller/SecurityController.php
  • templates/security/login.html.twig

Bien que l'ensemble soit fonctionnel, quelques modifications sont à apporter pour compléter l'identification.
Ouvrez le fichier "src/Security/UserAuthenticator.php". Nous allons modifier la méthode onAuthenticationSuccess() qui pour le moment ressemble à ça :

public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
    if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
        return new RedirectResponse($targetPath);
    }

    // For example : return new RedirectResponse($this->urlGenerator->generate('some_route'));throw new \Exception('TODO: provide a valid redirect inside '.__FILE__);
}

Dans cette méthode, nous allons définir un chemin de redirection une fois l'utilisateur identifié. Dans le commentaire de cette méthode, le code nécessaire est fourni. Récupérons-le et ajoutons le nom de la route vers laquelle on souhaite rediriger l'utilisateur :

public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
    if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
        return new RedirectResponse($targetPath);
    }

    return new RedirectResponse($this->urlGenerator->generate('account'));
}

Maintenant, ouvrez le fichier "src/Controller/SecurityController.php" et placez-vous dans la méthode login(). Il nous suffit de dé-commenter le code présent au début de la méthode :

/**
 * @Route("/login", name="app_login")
 */
public function login(AuthenticationUtils $authenticationUtils): Response
{
    if ($this->getUser()) {
        return $this->redirectToRoute('account');
    }

    // get the login error if there is one
    $error = $authenticationUtils->getLastAuthenticationError();
    // last username entered by the user
    $lastUsername = $authenticationUtils->getLastUsername();

    return $this->render('security/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]);
}

Ce bout de code commenté permet de rediriger automatiquement l'utilisateur vers une page différente si jamais il est déjà identifié et qu'il tente d'aller sur la page contenant le formulaire de connexion.

Vous pouvez aussi modifier le formulaire de connexion "templates/security/login.html.twig", pour le traduire par exemple, ou lui donner une forme plus sympathique.

Deux routes sont disponibles :

  • Pour se connecter : "/login", qui a pour nom "app_login"
  • Pour se déconnecter : "/logout", dont le nom est "app_logout"

Inscription

Super ! On peut se connecter.
On ne peut toujours pas créer de compte. Une nouvelle commande de Symfony va générer de quoi faire cette création de compte, mais il va falloir un peu de travail de votre part.

Allez hop ! On ouvre le terminal de commande et on tape :

symfony console make:registration-form

# Vous pouvez aussi utiliser une autre commande
# php bin/console make:registration-form

Une nouvelle série de questions vous est posée.


Do you want to add a @UniqueEntity validation annotation on your Users class to make sure duplicate accounts aren't created? (yes/no) [yes] :
Je vous conseille de répondre yes à cette question, car cela permet d'éviter que plusieurs comptes utilisateur utilisent la même adresse e-mail.

Do you want to send an email to verify the user's email address after registration? (yes/no) [yes] :
Ici, on nous demande si l'on veut envoyer un e-mail à l'utilisateur afin de vérifier la validité de son adresse e-mail.

What email address will be used to send registration confirmations? e.g. mailer@your-domain.com :
Si vous avez répondu oui à la question précédente, entrez une adresse e-mail. Elle sera utilisée en tant qu'adresse expéditeur.

What "name" should be associated with that email address? e.g. "Acme Mail Bot" :
Entrez ici un nom qui sera utilisé lors de l'envoi de l'e-mail de vérification.

Do you want to automatically authenticate the user after registration? (yes/no) [yes] :
Répondez oui si vous souhaitez connecter automatiquement l'utilisateur une fois qu'il est inscrit.

What route should the user be redirected to after registration? :
Si vous avez répondu non à la question précédente, entrez le nom d'une route existante pour rediriger l'utilisateur. Cependant, il ne sera pas connecté automatiquement.


Si vous avez opté pour l'envoi d'un e-mail pour la vérification de l'adresse e-mail de l'utilisateur, suivez les étapes suivantes. Dans le cas contraire, vous pouvez passer au chapitre suivant.

composer require symfonycasts/verify-email-bundle

Ensuite, ouvrez le fichier "src/Controller/RegistrationController.php" et modifiez la méthode verifyUserEmail(). Pour le moment, elle ressemble à cela :

/**
 * @Route("/verify/email", name="app_verify_email")
 */
public function verifyUserEmail(Request $request): Response
{
    $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');

    // validate email confirmation link, sets User::isVerified=true and persiststry {$this->emailVerifier->handleEmailConfirmation($request, $this->getUser());
    } catch (VerifyEmailExceptionInterface $exception) {
        $this->addFlash('verify_email_error', $exception->getReason());

        return $this->redirectToRoute('app_register');
    }

    // @TODO Change the redirect on success and handle or remove the flash message in your templates$this->addFlash('success', 'Your email address has been verified.');

    return $this->redirectToRoute('app_register');
}

Modifiez la dernière ligne de la méthode, à savoir $this->redirectToRoute(), en indiquant un nom de route où renvoyer l'utilisateur une fois son adresse e-mail vérifiée :

return $this->redirectToRoute('home');

Un message flash est créé contenant un message de succès. Pensez à bien l'afficher dans la vue vers laquelle l'utilisateur sera redirigé !

Mettez à jour votre base de données avec la commande suivante :

symfony console doctrine:schema:update --force

# Vous pouvez aussi utiliser une autre commande
# php bin/console doctrine:update:schema --force

Une route est maintenant disponible :

  • Pour s'inscrire : "/register". Le nom de cette route est "app_register"

Mot de passe oublié ?

On continue sur l'utilisation des outils mis à disposition par Symfony. Attaquons-nous maintenant au mot de passe oublié !
Toujours dans le terminal de commande, entrez ceci :

composer require symfonycasts/reset-password-bundle

Ce bundle n'est en effet pas installé par défaut. Ensuite, tapez la commande suivante :

symfony console make:reset-password

# Vous pouvez aussi utiliser une autre commande
# php bin/console make:reset-password

On repart avec une nouvelle série de questions. ?


What route should users be redirected to after their password has been successfully reset? [app_home] :
Une fois que l'utilisateur a correctement modifié son mot de passe, où le rediriger ? Le mieux reste le formulaire de connexion soit "app_login".

What email address will be used to send reset confirmations? e.g. mailer@your-domain.com :
Ici, nous pouvons choisir l'adresse e-mail utilisée pour envoyer l'e-mail permettant de modifier le mot de passe.

What "name" should be associated with that email address? e.g. "Acme Mail Bot" :
On définit le nom qui sera affiché dans le mail de modification du mot de passe envoyé.


Mettez à jour votre base de données avec la commande suivante :

symfony console doctrine:schema:update --force

# Vous pouvez aussi utiliser une autre commande
# php bin/console doctrine:update:schema --force

Pensez à fournir les informations nécessaires pour l'envoi de l'e-mail dans le fichier ".env", à la variable d'environnement MAILER_DSN.

Une route est maintenant disponible :

  • Pour retrouver son mot de passe : "/reset-password" ; le nom de la route est "app_forgot_password_request"

Sécuriser une route

Avoir des membres, c'est bien, mais sécuriser les routes de votre application, c'est mieux.
Pourquoi sécuriser ? Tout simplement pour bloquer l'accès de certaines pages aux visiteurs non enregistrés, sans un compte membre par exemple.

Ouvrez un contrôleur et placez-vous sur une méthode dont vous souhaitez ouvrir l'accès seulement aux membres. Au-dessus de cette méthode, inscrivez l'annotation suivante :

// ...

use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;

// ...

/**
 * @IsGranted("ROLE_USER")
 * @Route("/ma_route", name="nom_de_ma_route")
 */
public function maMethode()
{
  //...
}

J'ai ajouté l'annotation IsGranted("ROLE_USER") sans oublier son namespace. ?
ROLE_USER est important, car c'est le rôle attribué à un utilisateur quand celui-ci s'inscrit. Dorénavant, l'accès à cette page est autorisé uniquement aux utilisateurs inscrits et connectés.

Si par contre, vous souhaitez bloquer la totalité des routes dans un contrôleur, déplacez l'annotation au-dessus de la classe comme ceci :

// ...

use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;

// ...

/**
 * @IsGranted("ROLE_USER")
 */
class NomDeMonController extends AbstractController
{
  // ... 
}