📘

Utilisateurs et sécurité

Très souple donc assez complexe.

⚠️
Authentification : je dis qui je suis et je le prouve. Identification : je dis qui je suis. Autorisation : j’ai les droits.

Créer un système d’authentification


  1. Créer l’entité utilisateur
  1. Créer le système d’authentification
  1. Créer le formulaire d’inscription
  1. Autres :
    • “se souvenir de moi”
    • “mot de passe oublié” (mail)
    • lien de connexion magique (mail)

Etape 1 : entité utilisateur

L’entité User est créée, avec le UserRepository. Elle est particulière car elle implémente deux interfaces supplémentaires, UserInterface et PasswordAuthenticatedUserInterface, qui lui donnent accès à des méthodes comme getUserIdentifier(), getRoles(), setRoles(), eraseCredentials().

ℹ️
Par défaut, tous les utilisateurs ont le rôle ROLE_USER.

La configuration du security.yaml dans config/packages a été mise à jour selon les réponses que l’on a données, avec notamment l’entité et la propriété utilisées pour l’authentification et la méthode pour hasher les mots de passe.

security:
    # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
    password_hashers:
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
    # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
    providers:
        # used to reload user from session & other features (e.g. switch_user)
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email
📌
Pour aller plus loin : Il est possible de changer la propriété unique d’authentification, notamment pour en avoir plusieurs. https://symfony.com/doc/current/security/user_providers.html#using-a-custom-query-to-load-the-user
⚠️
Un mot de passe hashé ne peut pas être déhashé : pour savoir si le mot de passe hashé est le bon, on utilise le même processus de hash et on vérifie si on obtient le même résultat hashé. Un mot de passe chiffré peut être déchiffré : méthode moins sécurisée et donc déconseillée.

Etape 2 : système d’authentification

La classe AppAuthenticator est créée, dans un nouveau dossier Security. Elle hérite de la classe AbstractLoginFormAuthenticator et définit les méthodes authenticate(), onAuthenticationSuccess() et getLoginUrl(). Elle permet de s’authentifier.

Le contrôleur SecurityController est créé, qui définit les routes de ‘/login’ et ‘/logout’.

Le Twig login est créé afin de pouvoir se connecter.

ℹ️
Penser à le traduire au besoin et à l’intégrer aux autres pages.

Etape 3 : formulaire d’inscription

L'entité User est mise à jour, une contrainte “front” sur la propriété unique de l’utilisateur est ajoutée au-dessus de la classe.

#[UniqueEntity(fields: ['email'], message: 'Un compte existe déjà avec cet email.')]

Le formulaire d’inscription RegistrationFormType est créé, avec son contrôleur RegistrationController. La route ‘/register’ est définie. Le mot de passe saisi par l’utilisateur est récupéré dans le formulaire, hashé dans le contrôleur, avant d’être stocké en base de données. L’utilisateur est immédiatement connecté après inscription.

Le Twig register est créé afin de pouvoir se connecter.

ℹ️
Penser à le traduire au besoin et à l’intégrer aux autres pages.

Gérer un système d’authentification


Récupérer les informations de l’utilisateur connecté

Dans un Twig, on a accès à l’objet app.user et toutes les propriétés d’un utilisateur :

{% if app.user %}
	<spa>Bienvenue à toi : {{ app.user.email }}</span>
{% endif %}
ℹ️
Dans app, il y a beaucoup d’informations, comme les sessions et cookies.

Dans un contrôleur, on peut récupérer l’utilisateur actuellement connecté avec :

$userCo = $this->getUser();

Définir les rôles

Les rôles sont stockées dans la table User, sous la forme d’un tableau (sauf le ROLE_USER qui est dans le getter). Un utilisateur peut donc avoir plusieurs rôles. La seule obligation est qu’un rôle doit commencer par ROLE_.

ex: blog avec auteur, modérateur, lecteur…

Restreindre l’accès

Pour restreindre l’accès à une page pour certains rôles, on utilise l’attribut #[isGranted('ROLE_UNTEL')] au-dessus de la méthode souhaitée dans son contrôleur. La classe nécessite l’import de use Symfony\Component\Security\Http\Attribute\IsGranted;.

#[Route('/movie', name: 'movie')]
class MovieController extends AbstractController
{
    #[isGranted('ROLE_USER')]
    #[Route('/create', name: '_create')]
    public function create(
        Request $request,
        EntityManagerInterface $entityManager
    ): Response
    {...}
ℹ️
La page de création de films est restreinte aux utilisateurs connectés. Si un utilisateur non connecté clique sur le bouton de redirection vers la route ‘/movie/create’, il est alors redirigé vers la page de connexion.
📌
Pour aller plus loin : Dans ce cas, il n’est pas possible de savoir si l’utilisateur a le bon rôle, d’où la redirection vers ‘/login’. Si l’utilisateur n’a effectivement pas le bon rôle, la redirection est faite vers un statut HTTP 403, qui signifie qu'un client n'a pas le droit d'accéder à une URL valide. https://symfony.com/doc/current/security/access_denied_handler.html

Pour restreindre l’accès à de multiples pages, on utilise le access_control dans config/packages/security.yaml.

security:
	...
	access_control:
	         - { path: ^/admin, roles: ROLE_ADMIN }
	         - { path: ^/profile, roles: ROLE_USER }
ℹ️
Les URL sont des REGEX, on peut y mettre ce que l’on veut avec notamment ^ pour début de chaine et $ pour fin de chaine.
BP : Cette méthode ne peut fonctionner que si les routes sont bien organisées, ce qui se réfléchit en amont.

Vérifier un rôle dans un Twig

On utilise la méthode is_granted().

{% if is_granted('ROLE_USER') %}
  <h3><a href="{{ path('movie_create') }}" class="bt">Ajouter un film</a></h3>
{% endif %}

Hiérarchiser les rôles

Un rôle peut hériter d’un autre, et donc de toutes ses permissions.

Dans le fichier security.yaml, c’est là qu’on donne l’ordre de hiérarchie : du bas vers le haut (celui du haut a son rôle et tout ceux dessous).

role_hierarchy:
	ROLE_PRINCE: ROLE_USER // ici le prince a son role et le user
	ROLE_PRINCESSE: ROLE_USER
	ROLE_REINE: ROLE_PRINCESSE // ici la reine a son rôle et celui de princesse et user
	ROLE_ROI: ROLE_PRINCE
ℹ️
La hiérarchie n’est pas obligatoire. Elle permet juste de pas répéter les #[isGranted(role1, role2…)].
⚠️
On lit du bas vers le haut : comme ROI a le rôle PRINCE, on doit définir PRINCE au dessus, par contre le roi et la reine n’ont pas de lien de hiérarchie.

Autres fonctionnalités


Mot de passe oublié

Principe : permettre à l’utilisateur de réinitialiser son mot de passe grâce à un mail.

Il faut un serveur de mails : il est possible de simuler avec l’outil Papercut SMTP qui intercepte les mails rentrants et sortants.

- https://github.com/ChangemakerStudios/Papercut-SMTP/ (le Papercut.setup.exe en 5.8)

(dans la vraie vie on utilise un vrai serveur de mails…)

L’entité ResetPasswordRequest est créée, avec son contrôleur ResetPasswordController et son repository ResetPasswordRequestRepository. Ils gèrent le formulaire de demande de réinitialisation ResetPasswordRequestFormType, l’envoi du mail de réinitialisation avec le token, le formulaire de changement de mot de passe ChangePassewordFormType et le hashage du nouveau mot de passe.

Un fichier reset_password.yaml est créé dans config/packages .

Les Twigs reset, request, check_email et email sont créés.

ℹ️
Penser à les traduire au besoin et à les intégrer aux autres pages.
BP : Le APP_SECRET est une variable hyper secrète qui devrait être dans le .env.local puisqu’elle est utilisée dans le chiffrement et sert aussi pour le mot de passe oublié. Le MAILER_DSN aussi devrait être dans le .env.local.

Se souvenir de moi

Dans le security.yaml, on active le système remember_me.

security:
    # ...

    firewalls:
        main:
            # ...
            remember_me:
                secret:   '%kernel.secret%' # required
                lifetime: 604800 # 1 week in seconds
                # by default, the feature is enabled by checking a
                # checkbox in the login form (see below), uncomment the
                # following line to always enable it.
                #always_remember_me: true

Dans le Twig login, on décommente les lignes correspondantes et on les modifie à façon.

<div class="checkbox mb-3">
    <label>
        <input type="checkbox" name="_remember_me"> Se souvenir de moi
    </label>
</div>
⚠️
Le name doit être _remember_me.
📌
Pour aller plus loin : https://symfony.com/doc/current/security/remember_me.html

EasyAdmin

Hors programme.

EasyAdmin est un paquet qui permet de créer un beau panneau d’administration (dashboard).

ℹ️
Pour l’instant la page /admin est jolie mais vide…
ℹ️
Le configureDashboard sert pour les configurations.
⚠️
Si erreur avec PHP Intl, il faut le décommenter de php.ini.

On laisse le panneau d’administration en production. Comme les URL commencent par ‘/admin’, seuls les rôles sélectionnés y auront accès.

BP : quand on fait un projet pour un client, on fait un EasyAdmin qui n’est accessible qu’à nous, ça peut simplifier la gestion et la maintenance (à préciser dans le cahier des charges).

En vrac