Gestion des utilisateurs sans FosUserBundle 1ère partie

FosUserBundle, c’est super, rapide à mettre en place et… magique. A moins d’aller voir le code du bundle, la plupart du temps, on l’utilise uniquement pour sécuriser rapidement un backoffice. C’est bien, ça suit un protocole standard classique, et si l’on veut sortir un peu des clous, il faut surcharger les contrôleurs. Rien de très compliqué en soi, à part qu’il faut aller voir un peu du côté des events car il y en a pas mal.

Mais si FosUserBundle nous simplifie la vie, cela ne doit pas nous dispenser de comprendre ce qui se passe à l’arrière et d’implémenter par nous-même un système de sécurité afin de bien comprendre toutes les rouages du mécanisme. Et surtout, parfois, sous prétexte d’une simple sécurisation du site, on va sortir l’artillerie lourde FosUser alors qu’un simple formulaire d’authentification aurait suffi.

C’est parti!

Le firewall

Uniquement du classique ici:

  • On définit sha512 comme encoder
  • On indique que notre base d’utilisateurs va être associée à notre entité User (que nous allons bientôt créer) et que le login accepté sera le username (libre à vous de choisir un autre champ de votre table)
  • un firewall main dans lequel on définit les chemins check_path et login_path
  • La route du logout et celle de redirection
  • les contrôles d’accès, c’est à dire les routes pour lesquelles on a défini des rôles obligatoires

L’entité User et son interface

Pourquoi une interface?

Pour respecter le principe O de SOLID. En effet, si nous voulons qu’une classe soit ouverte à l’extension mais fermée à la modification, il faut définir une interface qui sera l’argument de nos fonctions. Ainsi, si un jour nous voulons utiliser une autre entité que User, il n’y a pas de problème, il suffira qu’elle implémente notre UserInterface et il n’y aura pas besoin de revenir sur le service dans lequel nous aurions passé directement User (donc un code trop strict et sur lequel nous devrions repasser en cas de changement).

Ici, une seule méthode importante: encodePassword.

 

Et voici notre entité User:

Ici, je n’implémente pas la classique UserInterface. J’ai envie d’aller plus loin et d’utiliser les fonctions de l’AdvancedUserInterface, plus intéressantes, pour interdire les utilisateurs non activés.

L’interface AdvancedUserInterface ajoute quatre méthodes supplémentaires pour valider le statut du compte :

  • isAccountNonExpired() vérifie si le compte de l’utilisateur a expiré,
  • isAccountNonLocked() vérifie si l’utilisateur est verrouillé,
  • isCredentialsNonExpired() vérifie si les informations de connexion de l’utilisateur (mot de passe) ont expiré,
  • isEnabled() vérifie si l’utilisateur est activé.

L’interface Serializable ainsi que ses méthodes serialize et unserialize ont été ajoutées pour permettre à la classe User d’être sérialisable dans la session. Cela peut ou non être nécessaire en fonction de votre configuration.

Le plainPassword n’est pas toujours présent dans les tutos, et il ne l’est pas dans celui de Sensio Labs. Ce champ vous sera utile lors de l’enregistrement (nous y reviendrons).

La fonction encodePassword nous servira plus tard mais sachez que nous préparons le terrain pour correctement découpler toutes les fonctions et créer une application maintenable, testable et réutilisable.

Le contrôleur de sécurité

Ce contrôleur est généralement appelé SecurityController mais ce n’est pas une obligation. Vous pouvez très bien l’appeler AuthenticationController si vous préférez, l’important reste le routing qui est renseigné dans security.yml pour check_path, login_path et path (pour logout).

Chargement en base d’un utilisateur

Il ne reste plus qu’à enregistrer un utilisateur en créant le répertoire DataFixtures/ORM et en y plaçant le fichier loadFixtures.php:

Le template de connexion

Il ne nous reste plus qu’à mettre en place le template de connexion:

 

puis le template du dashboard de l’utilisateur:

Le contrôleur lié aux actions du user connecté

Et la connexion est possible.

Dans le prochain article, nous verrons comment mettre en place l’enregistrement et comment découpler correctement son code en mettant en place plusieurs services et events afin de minimiser le code dans les contrôleurs et découpler au maximum les fonctions.

Le code github se trouve ici: https://github.com/jpsymfony/authentication-demo
Sa version mise à jour (il faudra chercher un peu pour retrouver les classes car l’arborescence a changé) en Symfony 3.2 est ici: https://github.com/jpsymfony/symfony3-generic-project-symfony3-architecture

Rédigé par

21 comments

  1. Bjr je tiens à te remercier pour ces excellents tuto sur la gestion des utilisateur en symfony. je suis entrain de mis former tout en faisant un site en symfony et un peu j’avais du mal avec FosUserBundle donc.
    GRAND MERCI

  2. Merci pour ce genre de tutoriel de grande qualité ou tout est bien expliqué de A à Z. Dans l’attente d’autres tutoriels du meme genre 🙂

    1. Merci! Je viens de commencer une série de tutos sur les design patterns, avec leur implémentation en Symfony2. J’espère que ça te plaira.

  3. Bonjour et bonne année à tous,

    Avant tout, merci pour ce tuto.
    Je suis confronté à un problème.

    Lors de la connexion j’ai le message d’erreur « throw new \Exception(‘This should never be reached!’) » de la méthode « loginCheckAction ».

    J’avoue sécher, pourriez-vous m’aider ?

    Merci d’avance

    1. Bonjour Matthieu,

      Est-ce que tes routes sont bien configurées dans le firewall de security.yml?

      En effet, le nom de la route loginCheckAction doit être renseignée avec le champ check_path et le nom indiqué doit être exactement celui de ta route.

      form_login:
      provider: database_users
      # The route name that the login form submits to
      check_path: security_login_check

      /**
      * This is the route the login form submits to.
      *
      * But, this will never be executed. Symfony will intercept this first
      * and handle the login automatically. See form_login in app/config/security.yml
      *
      * @Route(« /login_check », name= »security_login_check »)
      */
      public function loginCheckAction()
      {
      throw new \Exception(‘This should never be reached!’);
      }

      J’ai été un jour confronté à ce problème en mettant en place une api. Je tombais systématiquement sur ce message, mais c’était parce que ma route était mal configurée et Symfony ne l’interceptait pas.

  4. Bonjour,
    merci pour ton tuto.
    j’ai l’erreur : Unrecognized option « csrf_provider » under « security.firewalls.main.form_login »

  5. Bonjour,
    Je tiens à te remercier pour cet tuto très instructif pour moi qui apprend Symfony.
    Je débute un projet avec Symfony 3, serait t’il possible d’avoir une version actualisée de cet Excellent tuto?
    Cordialement.

    1. Bonjour Ousmane,

      Je manque cruellement de temps en ce moment pour les tutos (j’ai six articles dans les cartons mais manque de temps pour les finir) mais j’ai fait une version Symfony3 de ce tuto: https://github.com/jpsymfony/symfony3-generic-project

      La seule différence, c’est que le CoreBundle est allégé et générique, et que les entités concernant la partie User sont dans le UserBundle.

      Sinon, tu retrouveras exactement le même code, mais en Symfony3. Enjoy!

  6. bonjour
    j’ai ce message a chaque fois que j’essai de me loguer
    Authentication request could not be processed due to a system problem.

    auriez vous une idée pourquoi ?je cherche mais je ne trouve pas

    1. Bonjour Martin,

      J’ai déjà eu ce problème. Il était lié au fichier security.yml (de souvenir, problème de déclaration d’un provider ou d’un encoder). Regarde de ce côté là.

  7. Très bon tutorial, Grand merci , Très Grand merci !
    Au delà du formulaire d’authentification, cela m’a permis d’aborder la définition des Services, les Handlers, des Events avec aise.
    Sincèrement, il le fallait ce tutorial, très bonne continuation Jean Pierre.

  8. Bonjour,
    Tout d’abord un grand merci pour les tutos.

    je suis passé sous Symfony 4 et maintenant j’ai un message d’erreur lors du chargement de l’utilisateur :

    The « security.encoder_factory » service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead.

    Auriez vous une idée ?

    1. Bonjour,

      Malheureusement, Symfony4 étant sorti il y a très peu de temps, je n’ai pas encore eu le temps de me pencher sur cette version (qui change vraiment beaucoup de choses au niveau de la configuration, le fichier AppKernel.php qui devient Bundles.php et est réduit au strict minimum, le fichier de configuration qui est explosé en pleins de petits fichiers de configuration, etc.)

      Je vois que pleins de gens ont le même souci sur le net. Ce service est utilisé dans les fixtures, mais tous les services sont privés par défaut dans Symfony4.

      Il faut donc l’injecter directement dans le constructeur plutôt que de faire un $this->containe->get(‘security.encoder_factory’) comme on le faisait en Symfony3. Tout est indiqué ici: https://symfony.com/doc/master/bundles/DoctrineFixturesBundle/index.html#accessing-services-from-the-fixtures

  9. bonjour,

    J’essaye de reproduire votre tuto sur symfony3 avec le lien github que vous avez laissé en commentaire mais je m’y perds un peu avec les nombreux changements effectué et le nombre de classe ajouté, concrètement pour adapter ce code a SF3 qu’elles sont vraiment les modifications a faire?

    Cordialement,

    1. J’essaierai de lister les modifications mais tout dépend si ton passe à symfony 3.4 avec l’autowiring ou pas (car ma migration en sf3 s’est arrêtée à sf3.2 sans autowiring)

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *