
Utilisateurs et sécurité
Très souple donc assez complexe.
Créer un système d’authentification
- Créer l’entité utilisateur
- Créer le système d’authentification
- Créer le formulaire d’inscription
- Autres :
- “se souvenir de moi”
- “mot de passe oublié” (mail)
- lien de connexion magique (mail)
Etape 1 : entité utilisateur
- installer le paquet :
composer require security
- créer l’entité utilisateur :
symfony console make:user> choix du nom de l’entité :
User> stockage des informations utilisateurs dans la base de données (ou en dehors avec Google par exemple)
yes || no> propriété unique
email || username || uuid> ❗
YES❗pour hash/check les mots de passe
- ajouter des propriétés à l’entité User :
symfony console make:entity>User(comme d’habitude)
- mettre à jour la base de données car on a joué avec des entités :
symfony console d:s:u --force
- mettre à jour la base de données car on a joué avec des entités :
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().
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
Etape 2 : système d’authentification
- créer le système d’authentification :
symfony console make:auth> soit
0(vide), on fait tout à la main (rare) ; soit1, il fait pour nous (souvent)> nom de la classe =
AppAuthenticator(BP)> nom du contrôleur =
SecurityController(BP)> générer une url ‘/logout’ pour déconnecter l’user :
yes
- TODO = finir la redirection dans AppAuthenticator :
> classe pré-faite, on ne touche pas
> méthode
onAuthenticationSuccess(): c’est ici qu’on lui dit où rediriger l’utilisateur qui a réussi à se connecter- on commente le
throw new \Exception(ou on le supprime)
- on décommente le
return new RedirectResponsejuste au dessus et dans legenerate(), on lui donne le nom de la route (ex : ‘serie_list’)
- on commente le
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.
Etape 3 : formulaire d’inscription
- installer le paquet :
composer require doctrine/annotations
- potentiellement :
composer require form validator
- on propose à l’utilisateur un moyen de s’enregistrer :
symfony console make:registration-form> ajouter un Assert sur l’entité pour limiter les doublons ?
yes> envoyer un email à l’utilisateur pour vérifier son email ?
nopour l’instant (mais oui on va l’utiliser)> authentifier la personne quand elle s’enregistre ?
yes
- avant toute chose, on met à jour la base de données car on a joué avec des entités :
symfony console d:s:u --force
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.
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 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
{...}
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 }^ 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
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…)
- On installe un paquet :
composer require symfonycasts/reset-password-bundleet le paquet pour envoyer les mails :
composer require symfony/mailer
- On a maintenant accès à :
symfony console make:reset-password- on choisit la route pour rediriger le user une fois son mdp reset :
app_login
- quel mail utilisé pour envoyer l’email reset :
administration@mail.fr
- nom associé à l’adresse :
Team Admin
- on choisit la route pour rediriger le user une fois son mdp reset :
- On a modifié une entité (reset_password_request), on
symfony console d:s:u --forceet ça nous crée la table.
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.
- Dans le .env du projet, des choses ont changé : le
MAILER_DSNest apparu, on le décommente.- pour Papercut SMTP :
MAILER_DSN=smtp://localhost
- pour Papercut SMTP :
BP : LeAPP_SECRETest 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é. LeMAILER_DSNaussi 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: trueDans 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>_remember_me.
EasyAdmin
Hors programme.
EasyAdmin est un paquet qui permet de créer un beau panneau d’administration (dashboard).
- installer le paquet :
composer require easycorp/easyadmin-bundle
- créer le panneau d’administration :
symfony console make:admin:dashboard>
DashboardController> où le générer ?
src/Controller/Admin/
- configurer le dashboard dans le contrôleur :
- dans
index(), plein d’options commentées : on veut la easier (3) et on décommente lereturn(on commente lereturnparent du dessus)
- dans
- créer un nouveau Twig :
admin/dashboard.html.twig- on fait hériter le Twig comme conseillé :
{% extends '@EasyAdmin/page/content.html.twig %}
- on fait hériter le Twig comme conseillé :
- créer un CRUD pour chaque entité qu’on veut dans le dashboard :
symfony console make:admin:crud
- dans le DashboardController, dans la méthode
configureMenuItems(), ajouter un lien vers chacun des CRUDℹ️Il y a un exemple commenté qu’on utilise et complète.yield MenuItem::linkToCrud('Serie','fas fa-list', Serie::class);
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
- Dans la barre de débogage, on voit l’utilisateur connecté et ses informations.
- Dans la base de données, on a la table User où le champ des rôles est un tableau. Si l’unique rôle est celui par défaut
ROLE_USER, alors le tableau apparaît vide.
- Le maker-bundle n’est pas fait par les gars de Symfony, ils n’ont donc pas forcément les mêmes BP.
- Il y a de nombreux bundles, pour un peu tout, mais en mobile, Symfony n’est pas au top : Flutter est mieux pour le mobile —> tous les devs sont d’accord pour dire que c’est le meilleur.
- fontawesome.com propose plein d’icônes (toutes celles qui sont gratuites sont déjà accessibles).
- On peut avoir des php.ini par projet avec des interpréteurs différents.
- Un exemple de easter egg avec le code konami HHBBGDGDBA.
- GitHub Actions = intégration continue (normalement ça finit par la mise en prod)