Kaherecode

Créer un blog avec Symfony 4 - Authentification et Autorisation 2

Mamadou Aliou Diallo
@alioukahere 6 janv. 2020
0

Hey salut, bienvenue dans la suite de ce tutoriel sur Symfony. Dans la dernière partie, nous avons réussi à inscrire nos utilisateurs sur notre site à travers un formulaire d'inscription basique, avec une adresse email, un nom d'utilisateur et un mot de passe. Dans ce tutoriel, nous allons permettre à nos utilisateurs de s'authentifier sur notre site en renseignant leur adresse email et mot de passe, nous allons ensuite vérifier quel utilisateur peut ajouter un article, le modifier et aussi le supprimer. Nous allons donc commencer maintenant.

Le formulaire de connexion

Les utilisateurs qui visitent notre site doivent pouvoir s'y connecter en renseignant leur adresse email et mot de passe. Pour cela nous allons donc créer un formulaire de connexion et le traiter pour valider les informations saisis par l'utilisateur et ensuite l'identifier.

Pour créer le formulaire de connexion, nous allons utiliser la commande make:auth de MakerBundle (on est trop paresseux c'est vrai). Ouvrez donc un terminal et entrez la commande:

$ php bin/console make:auth

 What style of authentication do you want? [Empty authenticator]:
  [0] Empty authenticator
  [1] Login form authenticator
 > 1

 The class name of the authenticator to create (e.g. AppCustomAuthenticator):
 > LoginFormAuthenticator

 Choose a name for the controller class (e.g. SecurityController) [SecurityController]:
 >

 Do you want to generate a '/logout' URL? (yes/no) [yes]:
 >

 created: src/Security/LoginFormAuthenticator.php
 updated: config/packages/security.yaml
 created: src/Controller/SecurityController.php
 created: templates/security/login.html.twig

  Success!

 Next:
 - Customize your new authenticator.
 - Finish the redirect "TODO" in the App\Security\LoginFormAuthenticator::onAuthenticationSuccess() method.
 - Review & adapt the login template: templates/security/login.html.twig.

Des questions vont donc vous être posées:

Cette commande va générer 3 nouveaux fichiers:

La commande a aussi modifier le fichier security.yaml qui se trouve dans config/packages/ et y a rajouter l'authenticator et une entrée pour le logout. Je vous laisse découvrir cela.

Rendez-vous sur http://127.0.0.1:8000/login et en temps normal vous devez avoir le formulaire de connexion qui s'affiche.

Si vous avez utiliser les mêmes blocs que moi, vous risquez de ne pas voir le formulaire s'afficher, le même problème que nous avions avec la page d'inscription.

Nous allons pour commencer ouvrir le fichier login.html.twig, il ressemble à ceci:

{# templates/security/login.html.twig #}

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

{% block title %}Log in!{% endblock %}

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

    {% if app.user %}
        <div class="mb-3">
            You are logged in as {{ app.user.username }}, <a href="{{ path('app_logout') }}">Logout</a>
        </div>
    {% endif %}

    <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
    <label for="inputEmail">Email</label>
    <input type="email" value="{{ last_username }}" name="email" id="inputEmail" class="form-control" required autofocus>
    <label for="inputPassword">Password</label>
    <input type="password" name="password" id="inputPassword" class="form-control" required>

    <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>
    #}

    <button class="btn btn-lg btn-primary" type="submit">
        Sign in
    </button>
</form>
{% endblock %}

Je vais juste le modifier un petit peu pour avoir le design que je souhaite pour mon application.

Et voici mon nouveau fichier login.html.twig:

{# templates/security/login.html.twig #}

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

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

{% block content %}
    <div class="container center">
        <h1>Connexion</h1>
        <form method="post" class="row">
            {% if error %}
                <div class="col s12 m6 l6 offset-m3 offset-l3">
                    <span class="error">{{ error.messageKey|trans(error.messageData, 'security') }}</span>
                </div>
            {% endif %}

            {% if app.user %}
                <div class="col s12 m6 l6 offset-m3 offset-l3">
                    Tu es actuellement connect&#xE9; en tant que {{ app.user.username }}, <a href="{{ path('app_logout') }}">D&#xE9;connecte-toi</a>
                </div>
            {% endif %}
            <div class="input-field col s12 m6 l6 offset-m3 offset-l3">
                <input type="email" value="{{ last_username }}" name="email" id="inputEmail" class="" required autofocus placeholder="Adresse email">
            </div>
            <div class="input-field col s12 m6 l6 offset-m3 offset-l3">
                <input type="password" name="password" id="inputPassword" class="form-control" required placeholder="Mot de passe">
            </div>

            <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>
            #}

            <div class="input-field col s12">
                <input type="submit" class="btn btn-primary" value="Me connecter">
            </div>
        </form>
    </div>
{% endblock %}

Et ma page de connexion http://127.0.0.1:8000/login s'affiche comme ceci:

Avant de procéder à la connexion, nous avons juste un dernier détail à régler, quand l'utilisateur se connecte avec succès, il faut que nous le redirigeons vers la page d'accueil, pour cela, nous allons ouvrir le fichier LoginFormAuthenticator.php qui se trouve dans src/Security/ et modifier la fonction onAuthenticationSuccess() comme ceci:

<?php

// src/Security/LoginFormAuthenticator.php

namespace App\Security;

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Http\Util\TargetPathTrait;

class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
    use TargetPathTrait;

    private $entityManager;
    private $urlGenerator;
    private $csrfTokenManager;
    private $passwordEncoder;

    public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
    {
        $this->entityManager = $entityManager;
        $this->urlGenerator = $urlGenerator;
        $this->csrfTokenManager = $csrfTokenManager;
        $this->passwordEncoder = $passwordEncoder;
    }

    // ...

    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('homepage'));
    }

   // ..
}

Vous pouvez maintenant vous rendre sur la page de connexion et vous connecter. Si c'est bon vous êtes automatiquement rediriger vers la page d'accueil et votre adresse email est afficher dans la debug bar.

Juste une chose sur la barre de navigation, nous allons retirer le lien Login et le remplacer par Logout quand l'utilisateur est connecté. La barre de navigation se trouve dans le fichier base.html.twig:

{# templates/base.html.twig #}

<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}Symfony Blog{% endblock %}</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    {% block stylesheets %}
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
        <link rel="stylesheet" type="text/css" href="{{ asset('assets/css/app.css') }}">
    {% endblock %}
</head>
<body>
    <nav>
        <div class="nav-wrapper">
            <div class="container">
                <a href="{{ path('homepage') }}" class="brand-logo">Symfony Blog</a>
                    <ul id="nav-mobile" class="right hide-on-med-and-down">
                    <li><a href="{{ path('homepage') }}">Home</a></li>
                    {% if app.user %}
                        <li><a href="/logout">Logout</a></li>
                    {% else %}
                        <li><a href="/login">Login</a></li>
                    {% endif %}
                </ul>
            </div>
        </div>
    </nav>

    {% block content %}

    {% endblock %}

    {% block scripts %}
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>

        <script type="text/javascript">
            $('select').formSelect();
        </script>
    {% endblock %}
</body>
</html>

Sur la ligne 22, je vérifie juste si un objet user existe, si c'est le cas, c'est qu'un utilisateur est connecté j'affiche donc le lien de déconnexion, sinon j'affiche le lien de connexion.

Vous pouvez cliquer sur Logout pour vous déconnecter et Login pour vous connecter.

Pour récupérer l'utilisateur connecté dans une vue twig vous faites {{ app.user }}, et vous pouvez accéder à tous ses attributs comme son email avec {{ app.user.email }}. Si vous êtes dans un controlleur qui hérite de la classe AbstractController de Symfony, pour récupérer l'utilisateur connecté vous faites $this->getUser(). Là aussi vous pouvez avoir son adresse email avec $this->getUser()->getEmail().

Créer un espace d'administration

Nous allons maintenant créer un espace d'administration. Pour cela nous allons:

Je vous pari que vous saurez le faire, on en a déjà parler dans les tutoriels précédents.  Essayez et vous verrez.

Voici mon fichier routes.yaml:

# config/routes.yaml

homepage:
  path: /
  controller: App\Controller\BlogController::index

article_add:
  path: /add
  controller: App\Controller\BlogController::add

article_show:
  path: /show/{url}
  controller: App\Controller\BlogController::show

article_edit:
  path: /edit/{id}
  controller: App\Controller\BlogController::edit
  requirements:
    id: '\d+'

article_remove:
  path: /remove/{id}
  controller: App\Controller\BlogController::remove
  requirements:
    id: '\d+'

admin:
  path: /admin
  controller: App\Controller\BlogController::admin

Le contrôleur admin() dans le fichier BlogController.php

// src/Controller/BlogController.php

<?php

namespace App\Controller;

use App\Entity\User;
use App\Entity\Article;
use App\Form\ArticleType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class BlogController extends AbstractController
{
    // ...

    public function admin()
    {
        $articles = $this->getDoctrine()->getRepository(Article::class)->findBy(
            [],
            ['lastUpdateDate' => 'DESC']
        );

        $users = $this->getDoctrine()->getRepository(User::class)->findAll();

        return $this->render('admin/index.html.twig', [
            'articles' => $articles,
            'users' => $users
        ]);
    }

   // ...
}

Je récupère la liste de tous les articles et des utilisateurs que je renvoie à la vue. Le code de la vue:

{# templates/admin/index.html.twig #}

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

{% block title %}Admin. | {{ parent() }}{% endblock %}

{% block content %}
    <div class="admin-page">
        <div class="container">
            <div class="row">
                <div class="col s12">
                    <a href="{{ path('article_add') }}" class="btn btn blue darken-4" style="float: right">Ajouter</a>
                    <h5>Articles</h5>
                    <table>
                        <tr>
                            <th>#</th>
                            <th>Titre</th>
                            <th>Publier</th>
                            <th>Date de publication</th>
                            <th>Date de modification</th>
                            <th>Actions</th>
                        </tr>
                        {% set i = 1 %}
                        {% for article in articles %}
                            <tr>
                                <td>{{ i }}</td>
                                <td><a href="{{ path('article_show', {'url': article.url}) }}" target="_blank">{{ article.title }}</a></td>
                                <td>
                                    <span class="badge {{ article.isPublished ? 'blue' : 'red' }}">
                                        {{ article.isPublished ? 'oui' : 'non' }}
                                    </span>
                                </td>
                                <td>{{ article.isPublished ? article.publicationDate|date('d/m/Y') : '-' }}</td>
                                <td>{{ article.lastUpdateDate|date('d/m/Y') }}</td>
                                <td>
                                    <a href="{{ path('article_edit', {'id': article.id}) }}"><i class="material-icons blue-text">edit</i></a>
                                    <a href="{{ path('article_remove', {'id': article.id}) }}"><i class="material-icons red-text">delete</i></a>
                                </td>
                            </tr>
                            {% set i = i+1 %}
                        {% endfor %}
                    </table>
                </div>
            </div>

            <div class="row">
                <div class="col s12 m-t-30">
                    <h5>Utilisateurs</h5>
                    <table>
                        <tr>
                            <th>#</th>
                            <th>Adresse email</th>
                            <th>Username</th>
                            <th>Roles</th>
                            <th>Actions</th>
                        </tr>
                        {% set i = 1 %}
                        {% for user in users %}
                            <tr>
                                <td>{{ i }}</td>
                                <td>{{ user.email }}</td>
                                <td>{{ user.username }}</td>
                                <td>
                                    <span class="badge blue">
                                        admin.
                                    </span>
                                </td>
                                <td>
                                    <a href=""><i class="material-icons red-text">delete</i></a>
                                </td>
                            </tr>
                            {% set i = i+1 %}
                        {% endfor %}
                    </table>
                </div>
            </div>
        </div>
    </div>
{% endblock %}

Et ce qui donne la page suivante:

J'utilise principalement materializecss sur cette application et aussi material icons pour les icônes.

Si vous accédez maintenant au lien http://127.0.0.1:8000/admin vous devez normalement voir votre espace d'administration. Bravo.

Interdire l'accès à l'administration

Maintenant que nous avons notre espace d'administration, nous allons interdire l'accès à cet espace. Si vous remarquez bien à ce niveau, tout le monde peut y accéder, même si vous êtes pas connecté.

Chaque utilisateur authentifié sur Symfony a par défaut le rôle ROLE_USER. On peut à notre tour créé les rôles que nous souhaitons, c'est juste des chaînes de caractères qui doivent commencer par ROLE_ puis le nom de votre rôle par exemple ROLE_ADMIN, ROLE_EDITOR, ... le tout en majuscule. Pour attribuer un rôle à un utilisateur, nous ajoutons juste le rôle aux tableaux $roles[] de l'objet User. Nous verrons cela sous peu.

Nous allons donc interdire l'accès à l'espace d'administration a une personne qui n'est pas connecter et qui n'a pas le rôle ROLE_ADMIN. Pour cela, nous pouvons soit définir un contrôle d'accès dans le fichier config/packages/security.yaml ou directement dans le contrôleur.

Définir un contrôle d'accès dans security.yaml

Nous allons utiliser cette méthode généralement pour protéger une route ou un ensemble de route.

Si nous voulons par exemple interdire l'accès à la route /admin :

# config/packages/security.yaml

security:
    # ...

    access_control:
        - { path: ^/admin, roles: ROLE_ADMIN }

Si vous essayez maintenant d'acceder à http://127.0.0.1:8000/admin vous êtes automatiquement rediriger vers la page de connexion, pour vous dire de vous connecter avant d'y accéder.

Ce que nous faisons ici au fait, c'est que nous disons: toutes les routes qui commencent (^) par admin requiert le rôle ROLE_ADMIN. Ceci est donc valable pour /admin/users, /admin/articles, mais pas /home/admin, il faut forcément que la route commence par admin juste après la racine.

Je vous invite à donc vous connecter et essayez encore une fois d'accéder à l'espace d'administration http://127.0.0.1:8000/admin vous allez avoir une erreur Access Denied (accès refusé).

Ce qui s'est passé, c'est que le système vous a déjà authentifier, vous essayez ensuite d'accéder a une page alors que votre rôle ne vous le permet pas, logique qu'on vous refuse l'accès. Nous allons donc nous ajouter ce ROLE_ADMIN.

Ajouter un rôle à un utilisateur

Nous allons créer une commande Symfony pour nous permettre d'ajouter un rôle à n'importe quel utilisateur en renseignant juste son adresse email et le rôle que nous voulons lui rajouter. Mais avant, nous allons rajouter une méthode addRoles() dans la classe User.php qui se situe dans src/Entity, cette fonction va prendre en paramètre un rôle et l'ajouter au tableau $roles[] de l'utilisateur.

<?php

// src/Entity/User.php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 * @UniqueEntity(fields={"email"}, message="Un utilisateur existe d&#xE9;j&#xE0; avec cette adresse email.")
 */
class User implements UserInterface
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    // ...

    public function addRoles(string $roles): self
    {
        if (!in_array($roles, $this->roles)) {
            $this->roles[] = $roles;
        }

        return $this;
    }

    // ...
}

Pour créer une commande console avec Symfony, vous faites juste make:command comme ceci:

$ php bin/console make:command

Le système va alors vous demander de renseigner le nom de votre commande, moi j'ai choisi app:user:promote.

Un nouveau fichier UserPromoteCommand.php va donc être creer dans src/Command/, c'est dans ce fichier que va être défini les paramètres de la commande, comment est-ce qu'elle va fonctionner, ... Je vous invite à lire la documentation pour en apprendre plus.

J'ai apporter quelques modifications au contenu du fichier UserPromoteCommand.php, voici le resultat final:

<?php

// src/Command/UserPromoteCommand.php

namespace App\Command;

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class UserPromoteCommand extends Command
{
    protected static $defaultName = 'app:user:promote';

    private $om;

    public function __construct(EntityManagerInterface $om)
    {
        $this->om = $om;

        parent::__construct();
    }

    protected function configure()
    {
        $this
            ->setDescription('Promote a user by adding him a new roles.')
            ->addArgument('email', InputArgument::REQUIRED, 'Email address of the user you want to promote.')
            ->addArgument('roles', InputArgument::REQUIRED, 'The roles you want to add to the user.')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $email = $input->getArgument('email');
        $roles = $input->getArgument('roles');

        $userRepository = $this->om->getRepository(User::class);
        $user = $userRepository->findOneByEmail($email);

        if ($user) {
            $user->addRoles($roles);
            $this->om->flush();

            $io->success('The roles has been successfully added to the user.');
        } else {
            $io->error('There is no user with that email address.');
        }

        return 0;
    }
}

Notre commande prend deux paramètres:

Si l'adresse email n'existe pas, une erreur lui est renvoyer pour dire que l'utilisateur n'existe pas

Sinon le rôle est ajouté à l'utilisateur

Pour utiliser la commande:

$ php bin/console app:user:promote aliou@kaherecode.com ROLE_ADMIN

Et c'est tout.

Je vais juste me déconnecter puis me reconnecter pour que le nouveau rôle puisse entrer en vigueur. Et si j'essaie d'acceder à http://127.0.0.1:8000/admin ça marche bien.

Interdire l'accès directement dans le contrôleur

La deuxième méthode pour contrôler les accès consiste à définir l'accès directement dans le contrôleur.

Pour interdire l'accès au contrôleur d'ajout d'un article a une personne qui n'a pas le role ROLE_ADMIN, nous allons faire:

<?php

// src/Controller/BlogController.php

namespace App\Controller;

// ...

class BlogController extends AbstractController
{
    // ...

    public function add(Request $request)
    {
        $this->denyAccessUnlessGranted('ROLE_ADMIN');

        $article = new Article();
        $form = $this->createForm(ArticleType::class, $article);

        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $article->setLastUpdateDate(new \DateTime());
            $article->setUrl($this->generateSlug($article->getTitle()));

            if ($article->getPicture() !== null) {
                $file = $form->get('picture')->getData();
                $fileName = uniqid(). '.' .$file->guessExtension();

                try {
                    $file->move(
                        $this->getParameter('images_directory'),
                        $fileName
                    );
                } catch (FileException $e) {
                    return new Response($e->getMessage());
                }

                $article->setPicture($fileName);
            }

            if ($article->getIsPublished()) {
                $article->setPublicationDate(new \DateTime());
            }

            $em = $this->getDoctrine()->getManager();
            $em->persist($article);
            $em->flush();

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

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

    // ...
}

Dans la contrôleur add(), on appelle la methode denyAccessUnlessGranted() et en paramètre on lui donne le rôle requis pour accéder à ce contrôleur.

Nous pouvons aussi utiliser les annotations pour contrôler l'accès à un contrôleur:

<?php

// src/Controller/BlogController.php

namespace App\Controller;

// ..
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;

class BlogController extends AbstractController
{
    // ..

    /**
     * @IsGranted("ROLE_ADMIN")
     */
    public function edit(Article $article, Request $request)
    {
        $oldPicture = $article->getPicture();

        $form = $this->createForm(ArticleType::class, $article);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $article->setLastUpdateDate(new \DateTime());

            if (!$article->getIsPublished()) {
                $article->setUrl($this->generateSlug($article->getTitle()));
            }

            if ($article->getIsPublished()) {
                $article->setPublicationDate(new \DateTime());
            }

            if ($article->getPicture() !== null && $article->getPicture() !== $oldPicture) {
                $file = $form->get('picture')->getData();
                $fileName = uniqid(). '.' .$file->guessExtension();

                try {
                    $file->move(
                        $this->getParameter('images_directory'),
                        $fileName
                    );
                } catch (FileException $e) {
                    return new Response($e->getMessage());
                }

                $article->setPicture($fileName);
            } else {
                $article->setPicture($oldPicture);
            }

            $em = $this->getDoctrine()->getManager();
            $em->persist($article);
            $em->flush();

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

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

    // ...
}

Et voilà, pour accéder à la méthode edit() il faut avoir le rôle ROLE_ADMIN.

Et dans la vue, nous pouvons aussi contrôler le rôle de la personne connecter en utilisant la méthode is_granted(), cela va nous permettre d'afficher ou pas des sections de notre page en fonction du rôle de l'utilisateur:

{# templates/base.html.twig #}

{# ... #}
<body>
    <nav>
        <div class="nav-wrapper">
            <div class="container">
                <a href="{{ path('homepage') }}" class="brand-logo">Symfony Blog</a>
                    <ul id="nav-mobile" class="right hide-on-med-and-down">
                    <li><a href="{{ path('homepage') }}">Home</a></li>
                    {% if app.user %}
                        {% if is_granted('ROLE_ADMIN') %}
                            <li><a href="/admin">Admin.</a></li>
                        {% endif %}
                        <li><a href="/logout">Logout</a></li>
                    {% else %}
                        <li><a href="/login">

Partage ce tutoriel


Merci à

Mamadou Aliou Diallo

Mamadou Aliou Diallo

@alioukahere

Développeur web fullstack avec une passion pour l’entrepreneuriat et les nouvelles technologies. Fondateur de Kaherecode.

Continue de lire

Discussion

Tu dois être connecté pour participer à la discussion. Me connecter.