Vous allez poursuivre le TP précédent en ajoutant la gestion de l'authentification pour accéder à des ressources sur une API privée.
Ce TP sera donc l'occasion de comprendre le fonctionnement de l'authentification par JWT, avec un mécanisme de rafraîchissement, et sa mise en place dans une application Web/Mobile.
L'API Contacts servira de support pour ce TP.
Les notes des TP seront attribuées à partir de vos dépôts Git. Vous devrez donc accorder une attention particulière à la qualité de ces dépôts et de leurs « commits ».
De même, les messages de « commit » devront être clairs et informatifs afin de faciliter l'évaluation de la progression de votre travail.
Avant de pouvoir vous authentifier auprès de l'API Contacts, vous devez vous y enregistrer.
L'application de gestion de contacts que vous êtes en train de réaliser ne comporte que des fonctionnalités nécessitant une authentification. Vous allez donc mettre en place l'authentification dès l'ouverture de l'application.
Pour simplifier le processus d'authentification et pouvoir se focaliser sur le cycle de vie des jetons, nous considérerons que le serveur d'authentification fera confiance à l'application que vous êtes en train de créer. Dans un cadre réel, on lui préférerait une authentification avec redirection, que vous découvrirez au semestre suivant.
Votre application devra donc présenter un formulaire d'authentification permettant de saisir un login et un mot de passe. Ces informations seront transmises à l'aide d'une requête AJAX à l'API Contacts afin d'obtenir un JWT, que vous pourrez afficher dans la console pour l'instant.
Vous veillerez à l'ergonomie de votre application : UI/UX, lisibilité, indicateurs d'interaction, etc. Vous organiserez également votre base de code afin de limiter les gros blocs de code, d'améliorer la mutualisation et de faciliter la relecture.
Ces aspects seront pris en compte dans l'évaluation de votre travail.
Dans un futur TP, vous allez devoir revenir sur cette étape de votre développement. Prenez donc soin de bien l'identifier dans votre dépôt.
Vous allez ajouter un contexte à votre application pour gérer l'authentification. En effet, pour l'instant, c'est le formulaire qui obtient le JWT, lequel devra être disponible pour toute l'application. Vous allez donc réorganiser légèrement votre application pour placer toute la gestion du JWT d'authentification dans un contexte, qui fournira les services utilisant ce JWT à toute l'application.
Pour le moment, le contexte d'authentification fournira :
login, une fonction qui, recevant le login et le mot de passe, réalisera une requête AJAX pour obtenir un JWT et le stockera dans une variable d'état en cas de succès.logout, une fonction qui supprimera le JWT de la variable d'état.isAuthenticated, un booléen indiquant si l'utilisateur est authentifié.Vous modifierez votre formulaire d'authentification pour utiliser ce contexte. Vous pourrez alors masquer le formulaire d'authentification lorsqu'un utilisateur est authentifié et afficher un message indiquant que l'utilisateur est connecté à l'application.
Afin de simplifier la gestion des états et des écrans de l'application, vous allez ajouter un routeur à votre application. Dans un souci de simplicité, vous utiliserez wouter.
Vous configurerez ensuite votre application pour que :
/) soit la liste des contacts de l'utilisateur ;La liste des contacts et le profil de l'utilisateur ne comporteront pour l'instant qu'un message informatif.
Vous allez mettre en place la navigation permettant de naviguer entre les deux routes que vous avez créées. Cependant, avant cela, vous allez extraire l'identifiant de l'utilisateur depuis le JWT.
Comme vous l'avez lu, si vous ne le saviez pas déjà, un JWT comporte trois parties, séparées par des points. Les informations de l'utilisateur sont stockées dans la partie centrale : la charge utile. Ces données sont simplement encodées en Base64 et sont donc facilement accessibles.
Vous pouvez faire l'expérience en collant votre JWT dans le site JWT.io pour constater ce qu'il contient.
Vous ajouterez une nouvelle propriété au contexte d'authentification contenant la charge utile du JWT ou null si l'utilisateur n'est pas authentifié.
Pour réaliser le décodage de la partie encodée, vous utiliserez la fonction atob. La chaîne de caractères correspondant à la charge utile contient du JSON. Afin de faciliter son exploitation, vous la convertirez en objet JavaScript.
Vous pouvez maintenant afficher l'identifiant de l'utilisateur dans le pied de l'application lorsqu'un utilisateur est connecté.
Ensuite, vous ferez en sorte qu'un clic sur :
Vous allez maintenant utiliser le JWT pour authentifier vos requêtes AJAX auprès de l'API Contacts. Le JWT étant isolé dans le contexte d'authentification, il semblerait logique d'y placer toutes les requêtes utilisant le JWT, mais le code du contexte deviendrait vite trop volumineux et traduirait une conception malheureuse.
Vous allez plutôt ajouter au contexte une nouvelle fonction permettant de réaliser une requête authentifiée. Cette fonction recevra une URL et un objet d'initialisation de requête et retournera la promesse d'une réponse HTTP. Si le jeton n'est pas défini, alors la fonction pourra directement retourner une réponse 401. Sinon, elle injectera dans les en-têtes de l'objet d'initialisation de la requête un en-tête Authorization contenant la valeur "Bearer LE_JETON", avant de réaliser la requête AJAX et d'en retourner le résultat.
Vous pourrez ensuite utiliser cette fonction dans un hook proposant les fonctions d'accès à l'API :
Enfin, vous utiliserez ces fonctions pour compléter l'affichage des écrans de profil utilisateur et de liste des contacts. Dans un premier temps, vous vous contenterez d'afficher la liste des logins des contacts.
Encore une fois, vous prendrez soin de l'ergonomie de l'application en utilisant des indicateurs d'activité.
Maintenant que vous pouvez facilement générer des requêtes authentifiées en naviguant entre vos deux routes, vous allez pouvoir vous occuper du jeton de rafraîchissement.
En effet, vous avez dû constater que lors de l'authentification, vous obtenez un jeton de rafraîchissement avec votre JWT. Ce jeton permet de minimiser les risques de sécurité tout en conservant une bonne expérience utilisateur.
Le JWT est conçu pour être vérifiable de manière autonome par le serveur qui reçoit la requête. Une fois généré, il contient notamment les informations nécessaires à son identification et à sa validation, ainsi qu'une durée de validité. Il est donc important de limiter sa durée de vie afin de réduire les conséquences d'une éventuelle compromission.
Une durée de vie trop courte obligerait cependant l'utilisateur à se réauthentifier fréquemment. Le jeton de rafraîchissement permet d'authentifier l'utilisateur et d'obtenir un nouveau JWT directement, sans intervention de l'utilisateur. Si la durée de vie du jeton de rafraîchissement est supérieure à celle du JWT, on peut renouveler automatiquement le JWT de manière transparente pour l'utilisateur, tout en conservant une durée de vie courte pour le JWT.
Vous vous demandez peut-être quel est l'intérêt d'avoir une durée de vie courte pour le JWT si le jeton de rafraîchissement, permettant d'obtenir un nouveau JWT, a une durée de vie plus longue. C'est que le jeton de rafraîchissement n'est pas autosuffisant et n'identifie pas directement l'utilisateur. Celui-ci est enregistré au niveau du serveur et peut être révoqué afin de prévenir son utilisation. Plusieurs stratégies de sécurité sont alors envisageables.
Vous allez maintenant modifier votre contexte d'authentification pour gérer le jeton de rafraîchissement et le rafraîchissement automatique. Vous utiliserez le localStorage pour stocker le jeton de rafraîchissement afin de pouvoir reconnecter automatiquement votre utilisateur au démarrage de l'application.
De plus, afin de minimiser les rendus lors des changements d'état du JWT, vous n'utiliserez plus un hook d'état pour le stocker, mais un hook de référence. Vous aurez probablement besoin d'un booléen dans un hook d'état pour stocker l'état de l'authentification.
Vous ajouterez ensuite une fonction utilisant le jeton de rafraîchissement présent dans le localStorage pour obtenir un nouveau couple JWT/jeton de rafraîchissement.
Vous ferez en sorte que cette fonction soit invoquée au démarrage de l'application afin d'authentifier automatiquement l'utilisateur si un jeton de rafraîchissement est disponible.
Enfin, dans la fonction de requête authentifiée fournie par le contexte, vous ajouterez un contrôle sur le résultat de la requête. Si celui-ci a pour code de statut 401, vous provoquerez un rafraîchissement des jetons, puis vous rejouerez la requête.
Pour tester ces fonctionnalités, vous pourrez constater que les routes d'authentification et de rafraîchissement de l'API Contacts permettent de définir la durée de vie du jeton à l'aide du paramètre de requête ttl, que vous pouvez mettre en dur dans vos URL de services.
L'application est pour l'instant relativement simple, mais dans une application plus conséquente, il est possible que le rafraîchissement des jetons soit demandé plusieurs fois en parallèle, pouvant provoquer des incohérences dans les jetons.
Pour éviter ce problème, vous utiliserez le paquet async-mutex pour garantir de ne faire la requête AJAXem> de rafraîchissement qu'une seule à la fois.
Vous ajouterez ensuite dans l'en-tête de l'application un bouton permettant de se déconnecter et vous contrôlerez que l'utilisateur reste bien déconnecté lors du rechargement de la page.
Vous pourrez aussi constater, sur la page de votre profil de l'API Contacts, que les jetons de rafraîchissement ne sont pas supprimés. En effet, aucune stratégie de gestion des jetons de rafraîchissement n'a été mise en place.
Vous compléterez l'affichage de la liste des contacts en ajoutant toutes les informations du contact : avatar, nom, prénom, login et types de contact.
Vous devriez constater des erreurs lors de l'affichage des images des avatars. En effet, celles-ci sont protégées et nécessitent la présence de l'en-tête d'authentification.
Vous verrez plus tard une autre solution plus élégante, mais il existe généralement deux approches pour résoudre ce problème.
La première, que nous ne retiendrons pas par simplicité, consiste à effectuer des requêtes AJAX avec l'en-tête d'authentification, puis à injecter l'image résultante dans la page.
La seconde consiste à autoriser, au niveau du serveur, l'authentification par chaîne de requête.
Vous ajouterez au contexte d'authentification une nouvelle fonction permettant de créer une URL authentifiée en ajoutant un paramètre bearer de chaîne de requête contenant le JWT à l'URL reçue en paramètre.
Vous pourrez ensuite créer un service dans votre hook d'accès à l'API permettant d'obtenir une URL d'avatar authentifiée pour un login.