Blog ACENSI

Ce que j’aurais aimé lire avant d’écrire ma première application Golang (2/2) : implémentation de l’authentification et de l’autorisation

Introduction

Je suis consultant DevOps à Montréal depuis octobre 2022, mes compétences tournent autour de l’automatisation et du cloud. Mon employeur actuel est ACENSI Canada, une entreprise positionnée entre le cabinet de conseil généraliste et la consultation TI spécialisée (https://acensi.ca).

Ceci est la suite d’un article publié à ce lien : (Partie 1), dans lequel je traite la mise en place du projet. Prenez le temps de lire cette première partie avant d’attaquer celle-ci 😉

Au cours de cette seconde partie, je vais commencer par traiter la dernière partie du développement : la mise en place de l’autorisation sur mon application. Ensuite, je présenterai une analyse critique du code que j’ai produit, puis je terminerai par un exemple de fil de réflexion.



IV) Autorisation avec RBAC : empêcher le n’importe quoi

Avant de démarrer, analysons rapidement les points importants de cette seconde partie : 

  • L’autorisation peut-être définie comme un processus de vérification des droits d’accès d’un utilisateur à une ressource donnée, une fois l’authentification effectuée.
  • RBAC, ou « Role-Based Access Control » en anglais, est un modèle de contrôle d’accès qui permet de définir les autorisations d’accès aux ressources en fonction des rôles des utilisateurs. Dans un système RBAC, chaque utilisateur se voit attribuer un rôle, et chaque rôle se voit attribuer des autorisations d’accès à certaines ressources.

La gestion de droits est quelque chose que je trouve affreusement difficile à coder : on se retrouve très rapidement à mettre des if/else à tort et à travers, ce qui rends le code complexe et illisible en un temps record.

Heureusement, il existe une librairie golang appelé Casbin qui est spécialisée dans la gestion de droits. (https://github.com/casbin/casbin)

Elle permet de définir des systèmes d’habilitations complexes grâce à seulement 2 fichiers :

  • Un fichier de configuration ‘.conf’, qui sert à définir notre système de droits
  • Un fichier CSV qui énumère les politiques d’accès de notre système

Dans le cas de l’application, le système de gestion de droits prend en charge les fonctionnalités suivantes (sans avoir à écrire une seule ligne de code !) :

  • Super-utilisateur (= droits illimités)
  • Gestion de rôles sur 3 niveaux : utilisateur, modérateur et administrateur
  • Droit de modification sur son propre profil
  • Lister uniquement les utilisateurs ayant un rôle inférieur au sien

Cependant, à l’inverse de l’authentification JWT, l’intégration du système de gestion de droits à l’API est plus complexe et nécessite de modifier notre code plus en profondeur.

Afin de garder une application la plus simple possible et de modifier l’existant au minimum, j’ai choisi de :

  • Modéliser les requêtes faites au système de gestion de droits par une structure appelé ‘AuthorizationContext’.
  • Gérer interactions avec la librairie Casbin uniquement via un service, dans le package d’autorisation.
  • Initialiser et récupérer un maximum d’informations pour l’’AuthorizationContext’ dans un middleware.

V) Critique du code : points forts & difficultés 

Le développement de cette application a été pour moi un vrai défi, et je suis globalement satisfait du résultat final.

Je pense que le principal point positif de ce projet est son organisation générale :

  • Au niveau architectural, le découpage fonctionnel a été fait par domaine, puis par couche logique.
  • Au niveau du code, le design pattern “Factory” m’a permis de produire des morceaux de code modulaires.

Malgré tout, il reste certains axes d’amélioration à exploiter : 

  • Le nommage des fonctions/structures/variables peut surement être retravaillé pour être plus explicite.
  • Le package ‘config’ doit pouvoir être mieux découpé pour être moins « fourre-tout ».

Il n’est par ailleurs pas impossible qu’il y ait certaines erreurs de design ou d’architecture qui m’aient échappées.

Enfin, le dernier point qui me pose souci concerne la mise à jour et la suppression d’un utilisateur, ces opérations nécessitent 2 appels à la BDD : Un premier appel pour récupérer l’utilisateur (indispensable pour autoriser ou non l’opération), et un second pour effectuer l’opération.

Ce design augmente la charge sur la base de données et génère des temps de réponse plus longs, par ailleurs je trouve ça contre-intuitif de devoir faire 2 appels BDD pour une seule opération.

Malheureusement je n’ai pas trouvé de solutions pour n’avoir qu’un seul appel BDD sur ces opérations, je ne suis même pas sûr que ce soit possible 🙁


VI) Exemple d’un fil de réflexion

La principale raison qui m’a poussé à développer cette application est le challenge de réflexion autour de la conception logicielle. Pendant une longue période, j’ai été capable de coder en Go sans pour autant être en mesure de construire correctement une application : fonctions de plus de 200 lignes, applications mono-package …En bref, le cocktail parfait pour passer plus de temps à essayer de comprendre ce que tu as fait la veille au lieu de continuer à avancer.


Avec du recul, ce qui me manquait était la capacité à réfléchir sur la conception/l’architecture. Vous trouverez ci-dessous un fil de raisonnement qui montre comment j’aurais dû commencer à penser bien plus tôt.  Je l’ai écrit sous la forme d’un question/réponse (questions que je me suis posées/ réponses que j’ai devinées ou estimées correctes) pour l’un des cas qui m’a posé souci : la récupération dans le domaine « authentification » du hash du mot de passe de l’utilisateur qui essaye de se connecter.

    Question 1 : Est-ce que je créé un DAO dans le domaine d’authentification et y met une fonction pour aller chercher l’utilisateur en BDD ?

  • Je ne pense pas, toutes les opérations qui concernent les utilisateurs doivent être effectuées dans le domaine utilisateur. Si je fais ça je vais « diluer » mon domaine utilisateur.

    Question 2 : Ok donc il suffit que j’appelle le DAO du domaine utilisateurs avec mon service d’authentification ?

  • L’idée ne me paraît pas exceptionnelle, en faisant ça on ajoute une dépendance inter-couches en plus d’une dépendance inter-domaines. On va se retrouver avec du code qui ressemble à un plat de spaghetti.

    Question 3 : Donc l’idée c’est d’appeler le service « utilisateurs » dans le service « authentification » ?

  • A mon avis c’est l’option la moins pire : on limite les dépendances inter-domaines au niveau des services, tout en préservant nos différents domaines.

    Question 4 : Attends j’ai une autre solution : et si j’appelais le service utilisateur dans le Controller d’authentification, puis j’envoyais l’utilisateur récupéré dans le service d’authentification ?

  • Ça ne me plait pas top non plus, on fait remonter un objet métier dans le Controller pour le faire ensuite redescendre dans un autre service …

VII) Conclusion

Pour conclure cet article, le projet présente encore plein d’opportunités d’évolution qui n’attendent qu’à être explorées :

  • Ajouter des tests unitaires
  • Intégrer un système de mise en cache
  • Développer un frontend pour l’application

Je n’ai pas prévu de poursuivre ce projet à court/moyen terme, mais il n’est pas impossible que j’y remette le nez un jour ou l’autre 😊

Lien vers le projet github : https://github.com/a-spn/api-users 

Si vous êtes arrivés jusqu’ici, je tiens à vous remercier d’avoir pris le temps de me lire jusqu’au bout.

N’hésitez pas à partager l’article autour de vous !

Pourquoi ce blog ?

Pour permettre à nos consultants et experts techniques de partager leurs connaissances et retours d’expérience autour des sujets qui les passionnent. Ce blog, intégralement écrit par eux, a pour vocation d’être un véritable lieu d’échanges et d’apprentissage.

Alors n’hésitez pas à commenter nos articles pour rejoindre la conversation !

Une suggestion ?

Si vous avez des idées pour améliorer ce blog, nous sommes à l’écoute de vos remarques. Vous pouvez nous écrire via le formulaire de contact qui se trouve en bas de page.

Bonne visite !