Blog

Présentation de Guard

Nous le savons, la sécurité dans Symfony2 est l’une des briques les plus difficiles à appréhender pour de nombreux développeurs. Mais c’est aussi un des composants les plus extensibles et flexibles du framework.
L’une de ses utilisations les plus fréquentes est la mise en place d’un système d’authentification par le biais de la méthode « form_login ». Ce système est parfait si l’on doit mettre en place une authentification assez classique et souvent couplée à l’utilisation d’une base de données où sont stockés les utilisateurs.
En revanche, cela devient plus compliqué si l’on veut personnaliser le processus d’authentification : chargement des données utilisateurs, comportement en cas d’échec ou de succès de l’authentification…

En effet, mettre en place une méthode d’authentification ayant une logique spécifique demande la création d’au moins 4 classes dédiées :

  • un firewall
  • un token
  • un AuthenticationProvider
  • une factory

C’est l’un des challenges principaux pour un développeur peu habitué à travailler avec la sécurité dans Symfony. Afin de rendre ce composant plus souple et aussi robuste, une équipe de développeurs (KNP) a créée GuardBundle. Celui-ci intègre une nouvelle méthode d’authentification apportant une nouvelle approche en regroupant toutes les étapes de l’authentification Symfony dans une seule interface :
GuardAuthenticatorInterface
Ce bundle a donc tout simplement été inclus dans le composant sécurité de Symfony depuis sa version 2.8.

Voici les méthodes utilisées dans un cas nominal d’authentification :

graph

Pour utiliser ces méthodes, vous aurez besoin des éléments suivants :

  • une classe « User » implémentant Symfony\Component\Security\Core\User\UserInterface
  • un user provider
  • un service de gestion de l’authentification implémentant GuardAuthenticatorInterface « Authenticator » qui étend
  • de Symfony\Component\Security\Guard\AbstractGuardAuthenticator

Voici plus en détails la description des méthodes utilisées :

getCredentials(Request $request) : La méthode est appelée à chaque requête. Son but est de récupérer les informations utiles à l’authentification (username, password) depuis la requête et de les retourner. Si elle retourne “null” le reste du processus est stoppé. Sinon, la méthode getUser() est appelée.

getUser($credentials, UserProviderInterface $userProvider) : Cette méthode retourne un objet User implémentant UserInterface via le UserProvider en s’appuyant sur les informations présentes dans le paramètre $credentials.

checkCredentials($credentials, UserInterface $user) : Elle vérifie si les données d’authentification fournies dans $credentials correspondent au données de l’utilisateur et renvoie “true” si c’est le cas. Elle vérifiera par exemple que le couple login/password est valide.

onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) : La méthode est appelée si checkCredentials() renvoie “true”. Son but est de renvoyer un objet Response. Cela permet par exemple de rediriger l’utilisateur vers la page sécurisée qu’il souhaitait visionner avant de s’authentifier.

Contexte projet

Notre application se divise en 3 sites:

  • un site public : accessible à tous
  • un site dédié à un groupe d’utilisateurs : accessible uniquement si l’utilisateur est authentifié et appartient à ce groupe
  • un site dédié à tous les autres utilisateurs authentifiés

Une fois l’authentification réussie, l’application redirige l’utilisateur vers le site correspondant à son groupe.

La base de données des utilisateurs est accessible uniquement via l’appel de WebServices. Aucune donnée de l’utilisateur n’est stockée dans la base de données de notre application.

Pour récupérer les informations de l’utilisateur, notre application utilise deux webservices REST:

  • /authenticate : le service permet d’authentifier un utilisateur. Elle prend en paramètres un login et un mot de passe. En cas de succès, elle renvoie un token utilisé pour l’appel sécurisé d’autres services REST. Pour éviter toute confusion avec le token Symfony, nous l’appelerons wsToken.
  • /user : le service prend en paramètre un login et le wsToken. Il renvoie les données de l’utilisateur.

L’obligation d’utiliser un wsToken pour récupérer les données de l’utilisateur nous oblige à suivre ces étapes pour charger l’utilisateur dans le session:

graph2

Problématique

Pour réaliser le système d’authentification en partant du contexte projet, nous avions plusieurs solutions :

  • service “form_login” : de nombreuses surcharges sont à faire pour répondre au contexte projet
  • FOSuserBundle : le bundle très complet ne correspond pas à notre besoin d’une authentification simple et de nombreuses surcharges sont aussi à prévoir.
  • service Guard : le service promettant une personnalisation de l’authentification simple tout en restant au coeur du composant Security de Symfony.

Nous avons donc choisi Guard.

Mise en pratique de Guard dans le contexte projet

Dans un premier temps nous avons donc créé les classes nécessaires à l’utilisation de Guard :

  • une classe “User”
  • un user provider
  • un service de gestion de l’authentification implémentant

GuardAuthenticatorInterface

Contrairement à ce qu’attend Guard nativement dans son processus d’authentification, nous ne pouvons pas charger les données de l’utilisateur avant de vérifier que les credentials correspondent (cf. Cas nominal de l’authentification Guard).
Nous avons donc triché pour que le premier chargement de l’utilisateur prévu dans la méthode getUser() ne renvoie qu’un objet quasiment vide, seul les champs username et password sont renseignés

  1. 1. Récupération des données d’authentification (getCredentials()) : Cette méthode ne change pas
  2. 2. Récupération de l’utilisateur (getUser()): L’objet “User” est donc créé et ne contiendra que $username et $password
  3. 3. Vérification des données d’authentification (checkCredentials()): C’est ici que nous allons appeler le WebService authenticate() et si l’utilisateur est correctement authentifié, appeler la méthode getUserInfos() afin de charger les données de l’utilisateur dans l’objet de User passé en paramètre.

Voici un exemple des classes créées
https://gist.github.com/itkg-mraiffe/78f971d7fd77b940be63486910f81499

Pour rediriger l’utilisateur à la fin de l’authentification, il suffit de développer le comportement souhaité dans la méthode onAuthenticationSuccess().

Conclusion

L’utilisation de Guard nous a permis de mettre en place simplement et rapidement une authentification personnalisée via l’implémentation d’une classe Authenticator.

La présentation ici de la mise en oeuvre de Guard a été simplifiée pour ne montrer que l’aspect le plus représentatif de la force du composant pour notre projet. Nous avons aussi pu simplement définir les fonctionnalités suivantes:

  • Authentification depuis n’importe quelle page du site (formulaire présent dans le header du site public)
  • Redirection de l’utilisateur sur le site public lors de la déconnexion
  • Redirection vers une page d’erreur spécifique si l’utilisateur ne doit pas accéder à la page en cours

Auteur
Mathieu Raiffé LinkedIn

Voir toutes les tendances