Authentification avec le SecurityBundle de Symfony

Nous allons ici présenter comment faire une authentification en utilisant le SecurityBundle de Symfony . Il permet la connexion et la navigation de manière sécurisée.

Initialisation du projet Symfony

Tout d’abord création d’un nouveau projet Symfony (code pour la v5 de Symfony)

symfony new --full my_project
cd my_project

Installation de SecurityBundle

composer require symfony/security-bundle

Création de l’entité utilisateur

php bin/console make:user
_images/makeUser.jpeg

Cette entité est directement liée au SecurityBundle.

<?php

namespace App\Entity;

use App\Repository\UtilisateurRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * @ORM\Entity(repositoryClass=UtilisateurRepository::class)
 */
class Utilisateur implements UserInterface
{
        /**
         * @ORM\Id()
         * @ORM\GeneratedValue()
         * @ORM\Column(type="integer")
         */
        private $id;

        /**
         * @ORM\Column(type="string", length=180, unique=true)
         */
        private $username;

        /**
         * @ORM\Column(type="json")
         */
        private $roles = [];

        /**
         * @var string The hashed password
         * @ORM\Column(type="string")
         */
        private $password;

        public function getId(): ?int
        {
                return $this->id;
        }

        /**
         * A visual identifier that represents this user.
         *
         * @see UserInterface
         */
        public function getUsername(): string
        {
                return (string) $this->username;
        }

        public function setUsername(string $username): self
        {
                $this->username = $username;

                return $this;
        }

        /**
         * @see UserInterface
         */
        public function getRoles(): array
        {
                $roles = $this->roles;
                // guarantee every user at least has ROLE_USER
                $roles[] = 'ROLE_USER';

                return array_unique($roles);
        }

        public function setRoles(array $roles): self
        {
                $this->roles = $roles;

                return $this;
        }

        /**
         * @see UserInterface
         */
        public function getPassword(): string
        {
                return (string) $this->password;
        }

        public function setPassword(string $password): self
        {
                $this->password = $password;

                return $this;
        }

        /**
         * @see UserInterface
         */
        public function getSalt()
        {
                // not needed when using the "bcrypt" algorithm in security.yaml
        }

        /**
         * @see UserInterface
         */
        public function eraseCredentials()
        {
                // If you store any temporary, sensitive data on the user, clear it here
                // $this->plainPassword = null;
        }
}

Un fichier config/packages/security.yaml est créé. Il contient tous les paramètres pour une gestion sécurisée des utilisateurs.

security:
        encoders:
                App\Entity\Utilisateur:
                        algorithm: auto

        # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
        providers:
                # used to reload user from session & other features (e.g. switch_user)
                app_user_provider:
                        entity:
                                class: App\Entity\Utilisateur
                                property: username
        firewalls:
                dev:
                        pattern: ^/(_(profiler|wdt)|css|images|js)/
                        security: false
                main:
                        anonymous: lazy
                        provider: app_user_provider

                # activate different ways to authenticate
                # https://symfony.com/doc/current/security.html#firewalls-authentication

                # https://symfony.com/doc/current/security/impersonating_user.html
                # switch_user: true

        # Easy way to control access for large sections of your site
        # Note: Only the *first* access control that matches will be used
        access_control:
                # - { path: ^/admin, roles: ROLE_ADMIN }
                # - { path: ^/profile, roles: ROLE_USER }

Faites ensuite la migration :

php bin/console make:migration
php bin/console doctrine:migrations:migrate

Nous pouvons maintenant générer des formulaires spécialement prévu pour de l’authentification :

php bin/console make:auth
_images/formauth.jpeg
Cette commande :
  • modifie security.yaml

  • ajoute un controller

  • ajoute une classe héritiaire de AbstractFormLoginAuthenticator

  • ajoute un template

Nous avons dès lors un formulaire de login sécurisé ainsi qu’une route pour se déconnecter. Cependant le bundle ne prévoit pas la gestion des utilisateurs. Nous allons donc utiliser le maker bundle pour faire un CRUD.

CRUD utilisateur

php bin/console make:crud

Comme pour l’authentication à la main, nous allons devoir apporter quelques modifications.

Le controller :

Ajout de use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

Ceci afin de pour crypter le mot de passe saisi lors de l’inscription.

<?php
/**
 * @Route("/new", name="utilisateur_new", methods={"GET","POST"})
 */
public function new(Request $request, UserPasswordEncoderInterface $passwordEncoder): Response
{
        $utilisateur = new Utilisateur();
        $form = $this->createForm(UtilisateurType::class, $utilisateur);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
                $entityManager = $this->getDoctrine()->getManager();
                //encodage du mot de passe
                $utilisateur->setPassword(
                $passwordEncoder->encodePassword($utilisateur, $utilisateur->getPassword()));
                $entityManager->persist($utilisateur);
                $entityManager->flush();

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

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

Le formulaire :

<?php

namespace App\Form;

use App\Entity\Utilisateur;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
//ajout du use pour utiliser le type input password de Symfony
use Symfony\Component\Form\Extension\Core\Type\PasswordType;

class UtilisateurType extends AbstractType
{
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
                $builder
                        ->add('username')
                        // suppression du role qui sera défini par défaut
                        ->add('password', PasswordType::class)
                ;
        }

        public function configureOptions(OptionsResolver $resolver)
        {
                $resolver->setDefaults([
                'data_class' => Utilisateur::class,
                ]);
        }
}

Pour avoir le champ password caché.

Nous modifions maintenant le template du new afin d’ajouter l’input de vérification du mot de passe :

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

{% block title %}New Utilisateur{% endblock %}

{% block body %}
<h1>Create new Utilisateur</h1>

{{ form_start(form, {'attr': {'id': 'new_edit_utilisateur'}}) }}
{# utilisation de classes bootstrap pour la mise en forme #}
        <div class="row">
                <div class="col-12">
                        {{ form_label(form.username) }}
                        {{ form_widget(form.username) }}
                </div>
                <div class="col-12">
                        {{ form_label(form.password) }}
                        {{ form_widget(form.password) }}
                </div>
                <div class="col-12">
                        <label for="verifpass">Saisir une seconde fois le mot de passe</label>
                        <input type="password" id="verifpass" required>
                </div>
        </div>
        <button class="btn btn-success">{{ button_label|default('Save') }}</button>
{{ form_end(form) }}

<a href="{{ path('utilisateur_index') }}">back to list</a>
{% endblock %}

Création d’un script JS avec jQuery afin de tester si les deux mot de passe sont identiques. Créez tout d’abord un dossier js dans public puis dans ce dossier, un fichier script.js.

Script de vérification :

$("#new_edit_utilisateur").on('submit', function(){
    if($("#utilisateur_password").val() != $("#verifpass").val()) {
        //implémntez votre code
        alert("Les deux mots de passe saisies sont différents");
        alert("Merci de renouveler l'opération");
        return false;
    }
})

Il ne faut pas oublier d’intégrer le lien vers jQuery et notre script.js dans le base template :

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Welcome!{% endblock %}</title>
        {% block stylesheets %}
            <link rel="stylesheet" href="https://bootswatch.com/4/yeti/bootstrap.min.css">
        {% endblock %}
    </head>
    <body>
        {% block body %}{% endblock %}
        {% block javascripts %}
            <script
                src="https://code.jquery.com/jquery-3.5.1.min.js"
                integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
                crossorigin="anonymous">
            </script>
            <script src="/js/script.js"></script>
        {% endblock %}
    </body>
</html>

LoginFormAuthenticator :

Dans le dossier Security nous allons ajouter une route lorsque la connexion a été réalisée avec succès :

<?php

public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
        if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
                return new RedirectResponse($targetPath);
        }
        //on renvoie à la liste des utilisateurs
        return new RedirectResponse($this->urlGenerator->generate('utilisateur_index'));
}

Nous sommes maintenant capable de créer un utilisateur et de se connecter de manière sécurisée. Nous allons maintenant faire un peu de mise en forme. Dans le base template, ajout d’une barre de menu qui sera notre interface de connexion.

Base template :

<!DOCTYPE html>
<html>
        <head>
                <meta charset="UTF-8">
                <title>{% block title %}Welcome!{% endblock %}</title>
                {% block stylesheets %}
                        <link rel="stylesheet" href="https://bootswatch.com/4/yeti/bootstrap.min.css">
                {% endblock %}
        </head>
        <body>
                <nav class="navbar navbar-light bg-light">
                        <a class="navbar-brand">Navbar</a>
                        {% if app.user %}
                                <div>
                                        Bonjour {{ app.user.username }} <a class="btn btn-sm btn-danger" href="{{ path('app_logout') }}">Déconnexion</a>
                                </div>
                        {% else %}
                                <div>
                                        <a class="btn btn-sm btn-primary" href="{{ path('utilisateur_new') }}">S'inscrire</a>
                                        <a class="btn btn-sm btn-success" href="{{ path('app_login') }}">Se connecter</a>
                                </div>
                        {% endif %}
                </nav>
                <div class="container">
                        {% if message is defined %}
                                <div class="alert alert-danger">
                                        {{ message }}
                                </div>
                        {% endif %}
                        {% block body %}
                        {% endblock %}
                </div>
                {% block javascripts %}
                        <script
                                src="https://code.jquery.com/jquery-3.5.1.min.js"
                                integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
                        crossorigin="anonymous">
                        </script>
                        <script src="/js/script.js"></script>
                {% endblock %}
        </body>
</html>

Nous proposons à nos utilisateurs d’acceder aux pages de connexion ou d’inscription s’ils ne sont pas identifiés. Dans le cas contraire nous leur proposons de se déconnecter.

Modification d’un utilisateur

Nous avons besoin lorsque nous allons modifier un utilisateur d’avoir la même double vérification pour le mot de passe.

Dans le template :

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

{% block title %}Edit Utilisateur{% endblock %}

{% block body %}
        <h1>Edit Utilisateur</h1>

        {{ form_start(form, {'attr': {'id': 'new_edit_utilisateur'}}) }}
        {# utilisation de classes bootstrap pour la mise en forme #}
                <div class="row">
                        <div class="col-12">
                                {{ form_label(form.username) }}
                                {{ form_widget(form.username) }}
                        </div>
                        <div class="col-12">
                                {{ form_label(form.password) }}
                                {{ form_widget(form.password) }}
                        </div>
                        <div class="col-12">
                                <label for="verifpass">Saisir une seconde fois le mot de passe</label>
                                <input type="password" id="verifpass" required>
                        </div>
                </div>
                <button class="btn btn-success">{{ button_label|default('Update') }}</button>
        {{ form_end(form) }}

        <a href="{{ path('utilisateur_index') }}">back to list</a>

        {{ include('utilisateur/_delete_form.html.twig') }}
{% endblock %}

Dans le controller :

<?php
/**
 * @Route("/{id}/edit", name="utilisateur_edit", methods={"GET","POST"})
 */
public function edit(Request $request, Utilisateur $utilisateur, UserPasswordEncoderInterface $passwordEncoder): Response
{
        $form = $this->createForm(UtilisateurType::class, $utilisateur);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
                $utilisateur->setPassword($passwordEncoder->encodePassword($utilisateur, $utilisateur->getPassword()));
                $this->getDoctrine()->getManager()->flush();

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

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

Nous pouvons maintenant modifier le mot de passe de façon sécurisée.

Exemple final

Maintenant je vais vous présenter la solution que j’ai retenu pour mon exemple. En fait il s’agit d’un mix de toutes les méthodes vues précédemment.

Fichier de config

security.yaml

security:
        encoders:
                App\Entity\Utilisateur:
                        algorithm: auto

        # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
        providers:
                # used to reload user from session & other features (e.g. switch_user)
                app_user_provider:
                        entity:
                                class: App\Entity\Utilisateur
                                property: username
        firewalls:
                dev:
                        pattern: ^/(_(profiler|wdt)|css|images|js)/
                        security: false
                main:
                        anonymous: lazy
                        provider: app_user_provider
                        guard:
                                authenticators:
                                - App\Security\LogInFormAuthenticator
                        logout:
                                path: app_logout
                                # where to redirect after logout
                                target: home

                # activate different ways to authenticate
                # https://symfony.com/doc/current/security.html#firewalls-authentication

                # https://symfony.com/doc/current/security/impersonating_user.html
                # switch_user: true

        # Easy way to control access for large sections of your site
        # Note: Only the *first* access control that matches will be used
        access_control:
                - { path: ^/logout, roles: ROLE_USER }

                # permet de rendre la route /new accessible pour les utilisateurs anonymes (non connecté)
                - { path: ^/utilisateur/new, roles: IS_AUTHENTICATED_ANONYMOUSLY }

                # bloque toutes les routes commençant par /utilisateur sauf la ligne du dessus
                - { path: ^/utilisateur, roles: ROLE_USER }
                - { path: ^/membre, roles: IS_AUTHENTICATED_FULLY }

Je ne l’utilise que pour les routes nécessitant d’être connecté (sans distinction de droits).

Note

ROLE_USER == IS_AUTHENTICATED_FULLY

Controllers

UtilisateurController

<?php

namespace App\Controller;

use App\Entity\Utilisateur;
use App\Form\UtilisateurType;
use App\Repository\UtilisateurRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\HttpFoundation\Session\Session;

/**
* @Route("/utilisateur")
*/
class UtilisateurController extends AbstractController
{
        /**
        * @Route("/", name="utilisateur_index", methods={"GET"})
        */
        public function index(UtilisateurRepository $utilisateurRepository, Session $session): Response
        {
                //besoin de droits admin
                $utilisateur = $this->getUser();
                if(!$utilisateur)
                {
                        $session->set("message", "Merci de vous connecter");
                        return $this->redirectToRoute('app_login');
                }

                else if(in_array('ROLE_ADMIN', $utilisateur->getRoles())){
                        return $this->render('utilisateur/index.html.twig', [
                                'utilisateurs' => $utilisateurRepository->findAll(),
                        ]);
                }

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

        /**
         * @Route("/new", name="utilisateur_new", methods={"GET","POST"})
         */
        public function new(Request $request, UserPasswordEncoderInterface $passwordEncoder, Session $session): Response
        {

                //test de sécurité, un utilisateur connecté ne peut pas s'inscrire
                $utilisateur = $this->getUser();
                if($utilisateur)
                {
                        $session->set("message", "Vous ne pouvez pas créer un compte lorsque vous êtes connecté");
                        return $this->redirectToRoute('membre');
                }

                $utilisateur = new Utilisateur();
                $form = $this->createForm(UtilisateurType::class, $utilisateur);
                $form->handleRequest($request);

                if ($form->isSubmitted() && $form->isValid()) {
                        $entityManager = $this->getDoctrine()->getManager();
                        $utilisateur->setPassword($passwordEncoder->encodePassword($utilisateur, $utilisateur->getPassword()));
                        /* uniquement pour créer un admin
                        $role = ['ROLE_ADMIN'];
                        $utilisateur->setRoles($role); */
                        $entityManager->persist($utilisateur);
                        $entityManager->flush();

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

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

        /**
         * @Route("/{id}", name="utilisateur_show", methods={"GET"})
         */
        public function show(Utilisateur $utilisateur): Response
        {
                //accès géré dans le security.yaml
                return $this->render('utilisateur/show.html.twig', [
                'utilisateur' => $utilisateur,
                ]);
        }

        /**
         * @Route("/{id}/edit", name="utilisateur_edit", methods={"GET","POST"})
         */
        public function edit(Request $request, Utilisateur $utilisateur, UserPasswordEncoderInterface $passwordEncoder, Session $session, $id): Response
        {
                $utilisateur = $this->getUser();
                if($utilisateur->getId() != $id )
                {
                        // un utilisateur ne peut pas en modifier un autre
                        $session->set("message", "Vous ne pouvez pas modifier cet utilisateur");
                        return $this->redirectToRoute('membre');
                }
                $form = $this->createForm(UtilisateurType::class, $utilisateur);
                $form->handleRequest($request);

                if ($form->isSubmitted() && $form->isValid()) {
                        $utilisateur->setPassword($passwordEncoder->encodePassword($utilisateur, $utilisateur->getPassword()));
                        $this->getDoctrine()->getManager()->flush();

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

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

        /**
         * @Route("/{id}", name="utilisateur_delete", methods={"DELETE"})
         */
        public function delete(Request $request, Utilisateur $utilisateur, Session $session, $id): Response
        {
                $utilisateur = $this->getUser();
                if($utilisateur->getId() != $id )
                {
                        // un utilisateur ne peut pas en supprimer un autre
                        $session->set("message", "Vous ne pouvez pas supprimer cet utilisateur");
                        return $this->redirectToRoute('membre');
                }

                if ($this->isCsrfTokenValid('delete'.$utilisateur->getId(), $request->request->get('_token')))
                {
                        $entityManager = $this->getDoctrine()->getManager();
                        $entityManager->remove($utilisateur);
                        $entityManager->flush();
                        // permet de fermer la session utilisateur et d'éviter que l'EntityProvider ne trouve pas la session
                        $session = new Session();
                        $session->invalidate();
                }

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

SecurityController

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\HttpFoundation\Session\Session;

class SecurityController extends AbstractController
{
        /**
         * @Route("/login", name="app_login")
         */
        public function login(AuthenticationUtils $authenticationUtils, Session $session): Response
        {
                // if ($this->getUser()) {
                //     return $this->redirectToRoute('target_path');
                // }

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

                $return = ['last_username' => $lastUsername, 'error' => $error];

                if($session->has('message'))
                {
                        $message = $session->get('message');
                        $session->remove('message'); //on vide la variable message dans la session
                        $return['message'] = $message; //on ajoute à l'array de paramètres notre message
                }

                return $this->render('security/login.html.twig', $return);
        }

        /**
         * @Route("/logout", name="app_logout")
         */
        public function logout()
        {
                throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
        }
}

Templates

base.html.twig

<!DOCTYPE html>
<html>
        <head>
                <meta charset="UTF-8">
                <title>{% block title %}Welcome!{% endblock %}</title>
                {% block stylesheets %}
                <link rel="stylesheet" href="https://bootswatch.com/4/yeti/bootstrap.min.css">
                <link rel="stylesheet" href="/css/style.css">
                {% endblock %}
        </head>
        <body>
                <nav class="navbar navbar-light bg-light">
                        <a class="navbar-brand" href="{{ path('home') }}">Accueil</a>
                        {% if app.user %}
                                <a class="btn btn-outline-info" href="{{ path('membre') }}">Page membre</a>
                                {% if is_granted('ROLE_ADMIN') %}
                                        <a class="btn btn-outline-warning" href="{{ path('admin') }}">Page admin</a>
                                {% endif %}
                                <div>
                                        Bonjour {{ app.user.username }} <a class="btn btn-sm btn-danger" href="{{ path('app_logout') }}">Déconnexion</a>
                                </div>
                        {% else %}
                                <div>
                                        <a class="btn btn-sm btn-primary" href="{{ path('utilisateur_new') }}">S'inscrire</a>
                                        <a class="btn btn-sm btn-success" href="{{ path('app_login') }}">Se connecter</a>
                                </div>
                        {% endif %}
                </nav>
                <div class="container">
                        {% if message is defined %}
                                <div class="alert alert-danger">
                                        {{ message }}
                                </div>
                        {% endif %}
                        {% block body %}
                        {% endblock %}
                </div>
                <footer class="navbar-light bg-light">
                        <div class="footer-copyright text-center py-3">© 2020 Copyright:
                                <a href="https://www.univ-orleans.fr/iut-orleans/informatique/intra/tuto/php/"> Gérard Rozsavolgyi & Sylvain Austruy</a>
                        </div>
                </footer>
                        {% block javascripts %}
                        <script
                                src="https://code.jquery.com/jquery-3.5.1.min.js"
                                integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
                        crossorigin="anonymous">
                        </script>
                        <script src="/js/script.js"></script>
                {% endblock %}
        </body>
</html>

new.html.twig

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

{% block title %}Inscription{% endblock %}

{% block body %}
        <h1>Inscription</h1>

        <fieldset>
                <legend>Inscrivez vous</legend>
                {{ form_start(form, {'attr': {'id': 'new_edit_utilisateur'}}) }}
                {# utilisation de classes bootstrap pour la mise en forme #}
                <div class="row">
                        <div class="col-12">
                                {{ form_label(form.username) }}
                                {{ form_widget(form.username) }}
                                {{ form_errors(form.username) }}
                        </div>
                        <div class="col-12">
                                {{ form_label(form.password) }}
                                {{ form_widget(form.password) }}
                        </div>
                        <div class="col-12">
                                <label for="verifpass">Saisir une seconde fois le mot de passe</label>
                                <input type="password" id="verifpass" required>
                        </div>
                </div>
                <button class="btn btn-success">{{ button_label|default('S\'enregistrer') }}</button>
                {{ form_end(form) }}
        </fieldset>

        <a class="btn btn-info" href="{{ path('home') }}">Page d'accueil</a>
{% endblock %}

Ajout de {{ form_errors(form.username) }} pour afficher une erreur quand le pseudo est déjà utilisé (la vérification se fait automatiquement, car nous avons choisi l’attribut username comme unique) et un peu de css.

edit.html.twig

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

{% block title %}Modification compte{% endblock %}

{% block body %}
        <h1>Modifier votre compte</h1>
        <fieldset>
                <legend>Modification</legend>
                {{ form_start(form, {'attr': {'id': 'new_edit_utilisateur'}}) }}
                {# utilisation de classes bootstrap pour la mise en forme #}
                        <div class="row">
                                <div class="col-12">
                                        {{ form_label(form.username) }}
                                        {{ form_widget(form.username) }}
                                </div>
                                <div class="col-12">
                                        {{ form_label(form.password) }}
                                        {{ form_widget(form.password) }}
                                </div>
                                <div class="col-12">
                                        <label for="verifpass">Saisir une seconde fois le mot de passe</label>
                                        <input type="password" id="verifpass" required>
                                </div>
                        </div>
                        <button class="btn btn-success">{{ button_label|default('Mettre à jour') }}</button>
                {{ form_end(form) }}
        </fieldset>

        <a class="btn btn-outline-primary" href="{{ path('membre') }}">Page membre</a>

        {{ include('utilisateur/_delete_form.html.twig') }}
{% endblock %}

show.html.twig

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

{% block title %}Utilisateur{% endblock %}

{% block body %}
        <h1>Utilisateur</h1>

        <table class="table">
                <tbody>
                <tr>
                                <th>Id</th>
                                <td>{{ utilisateur.id }}</td>
                        </tr>
                        <tr>
                                <th>Username</th>
                                <td>{{ utilisateur.username }}</td>
                        </tr>
                        <tr>
                                <th>Roles</th>
                                <td>{{ utilisateur.roles ? utilisateur.roles|json_encode : '' }}</td>
                        </tr>
                        <tr>
                                <th>Password</th>
                                <td>{{ utilisateur.password }}</td>
                        </tr>
                </tbody>
        </table>

        <a class="btn btn-outline-primary mt-2" href="{{ path('membre') }}">Page membre</a>

        <a class="btn btn-warning mt-2" href="{{ path('utilisateur_edit', {'id': utilisateur.id}) }}">Modifier</a>

        {{ include('utilisateur/_delete_form.html.twig') }}
{% endblock %}

login.html.twig

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

{% block title %}Connexion{% endblock %}

{% block body %}
        <form method="post" class="mt-5">
                <fieldset>
                        <legend>Connectez vous</legend>

                        {% if error %}
                                <div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
                        {% endif %}

                        {% if app.user %}
                                <div class="mb-3">
                                        Bonjour {{ app.user.username }} <a class="btn btn-sm btn-danger" href="{{ path('app_logout') }}">Déconnexion</a>
                                </div>
                        {% else %}
                                <div class="row">
                                        <div class="col-12 mt-3">
                                                <input type="text" value="{{ last_username }}" name="username" id="inputUsername" class="form-control" required autofocus placeholder="pseudo">
                                        </div>
                                        <div class="col-12 mt-3 mb-3">
                                                <input type="password" name="password" id="inputPassword" class="form-control" required placeholder="mot de passe">
                                        </div>
                                </div>
                                <button class="btn btn-success" type="submit">
                                        Connexion
                                </button>
                        {% endif %}

                        <input type="hidden" name="_csrf_token"
                        value="{{ csrf_token('authenticate') }}"
                        >

                        {#
                        Uncomment this section and add a remember_me option below your firewall to activate remember me functionality.
                        See https://symfony.com/doc/current/security/remember_me.html

                        <div class="checkbox mb-3">
                                <label>
                                        <input type="checkbox" name="_remember_me"> Remember me
                                </label>
                        </div>
                        #}
                </fieldset>
        </form>
{% endblock %}

Les templates de NavigationController

Les templates pour tester la navigation sécurisée.

Admin :

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

{% block title %}Page admin{% endblock %}

{% block body %}
        <h1>Ma page admin</h1>
        <h2>{{ app.user.username }} tu es un admin</h2>

        <p>
                You’ve probably heard of Lorem Ipsum before – it’s the most-used dummy text excerpt out there.
                People use it because it has a fairly normal distribution of letters and words (making it look like normal English),
                but it’s also Latin, which means your average reader won’t get distracted by trying to read it.
                It’s perfect for showcasing design work as it should look when fleshed out with text, because it allows viewers to focus on the design work itself, instead of the text.
                It’s also a great way to showcase the functionality of programs like word processors, font types, and more.
                We’ve taken Lorem Ipsum to the next level with our HTML-Ipsum tool.
                As you can see, this Lorem Ipsum is tailor-made for websites and other online projects that are based in HTML. Most web design projects use Lorem Ipsum excerpts to begin with,
                but you always have to spend an annoying extra minute or two formatting it properly for the web.
                Maybe you have a custom-styled ordered list you want to show off, or maybe you just want a long chunk of Lorem Ipsum that’s already wrapped in paragraph tags.
                No matter the need, we’ve put together a number of Lorem Ipsum samples ready to go with HTML tags and formatting in place.
                All you have to do is click the heading of any section on this page, and that HTML-Ipsum block is copied to your clipboard
                nd ready to be loaded into a website redesign template, brand new design mockup, or any other digital project you might need dummy text for.
                No matter the project, please remember to replace your fancy HTML-Ipsum with real content before you go live - t
                his is especially important if you're planning to implement a content-based marketing strategy for your new creation!
                Lorem Ipsum text is all well and good to demonstrate a design or project, but if you set a website loose on the Internet
                without replacing dummy text with relevant, high quality content, you’ll run into all sorts of potential Search Engine Optimization issues
                like thin content, duplicate content, and more.
                HTML-Ipsum is maintained by WebFX.
                For more information about us, please visit WebFX Reviews.
                To learn more about the industries we drive Internet marketing performormance for and our revenue driving services:
                SEO, PPC, social media, web design, local SEO and online advertising services.
        </p>
        <a class="btn btn-primary mt-3" href="{{ path('utilisateur_edit', {'id': app.user.id}) }}">Modifier mon compte</a>
        <a class="btn btn-info mt-3" href="{{ path('utilisateur_index') }}">Voir la liste des utilisateurs inscrit</a>
{% endblock %}

Home :

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

{% block title %}Page d'accueil{% endblock %}

{% block body %}
        <h1>Mon super site</h1>

        <p><strong>Pellentesque habitant morbi tristique</strong> senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam,
        feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. <em>Aenean ultricies mi vitae est.</em> Mauris
        placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, <code>commodo vitae</code>, ornare sit amet, wisi.
         Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. <a href="#">Donec non enim</a> in turpis pulvinar facilisis. Ut felis.</p>

        <h2>Header Level 2</h2>
        <ol>
                <li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
                <li>Aliquam tincidunt mauris eu risus.</li>
        </ol>
        <blockquote><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna.
        Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa.
        Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est.</p></blockquote>

        <h3>Header Level 3</h3>
        <ul>
                <li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
                <li>Aliquam tincidunt mauris eu risus.</li>
        </ul>
        <pre><code>
                #header h1 a {
                display: block;
                width: 300px;
                height: 80px;
                }
        </code></pre>
{% endblock %}

Membre :

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

{% block title %}Page membre{% endblock %}

{% block body %}
        <h1>Mon espace membre</h1>

        <h2>{{ app.user.username }} voici ton espace personnel</h2>

        <dl>
                <dt>Definition list</dt>
                <dd>Consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
                aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
                commodo consequat.</dd>
                <dt>Lorem ipsum dolor sit amet</dt>
                <dd>Consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
                aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
                commodo consequat.</dd>
        </dl>

        <ol>
                <li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
                <li>Aliquam tincidunt mauris eu risus.</li>
                <li>Vestibulum auctor dapibus neque.</li>
        </ol>


        <a class="btn btn-primary" href="{{ path('utilisateur_edit', {'id': app.user.id}) }}">Modifier mon compte</a>
{% endblock %}

CSS

public/css/style.css

fieldset {
        margin: 0 auto;
        width: 50%;
        padding: 1em;
        border: 1px solid #CCC;
        border-radius: 1em;
}

legend {
        width: 50%;
}

body {
        margin: 0;
        min-height: 100vh;
        display: grid;
        grid-template-rows: auto 1fr auto;
}

Note

Ce tuto est terminé, il ne vous reste qu’à l’adapter à votre projet. Ainsi que de réaliser un beau CSS avec Bootstrap ou autre.