Authentification dans Vue.js

Publié: 2022-03-10
Résumé rapide ↬ Chaque application Web qui gère des données spécifiques à l'utilisateur doit implémenter l'authentification. Savoir comment faire cela est important pour les développeurs de Vue, et c'est ce sur quoi cet article vise à mettre l'accent. Ce tutoriel s'avérera utile pour les développeurs débutants qui souhaitent en savoir plus sur l'authentification dans Vue. Afin de pouvoir suivre, vous devrez avoir une bonne connaissance de Vue et Vuex.

L'authentification est une fonctionnalité très nécessaire pour les applications qui stockent des données utilisateur. Il s'agit d'un processus de vérification de l'identité des utilisateurs, garantissant que les utilisateurs non autorisés ne peuvent pas accéder aux données privées, c'est-à-dire aux données appartenant à d'autres utilisateurs. Cela conduit à avoir des itinéraires restreints qui ne sont accessibles qu'aux utilisateurs authentifiés. Ces utilisateurs authentifiés sont vérifiés en utilisant leurs informations de connexion (c'est-à-dire nom d'utilisateur/e-mail et mot de passe) et en leur attribuant un jeton à utiliser pour accéder aux ressources protégées d'une application.

Dans cet article, vous allez découvrir :

  1. Configuration de Vuex avec Axios
  2. Définir des itinéraires
  3. Gestion des utilisateurs
  4. Gestion du jeton expiré

Dépendances

Nous allons travailler avec les dépendances suivantes qui aident à l'authentification :

  • Axios
    Pour envoyer et récupérer des données depuis notre API
  • Vuex
    Pour stocker les données obtenues à partir de notre API
  • Vue-Routeur
    Pour la navigation et la protection des Routes

Nous travaillerons avec ces outils et verrons comment ils peuvent fonctionner ensemble pour fournir une fonctionnalité d'authentification robuste pour notre application.

L'API principale

Nous allons construire un site de blog simple, qui utilisera cette API. Vous pouvez consulter les documents pour voir les points de terminaison et comment les demandes doivent être envoyées.

Dans la documentation, vous remarquerez que peu de points de terminaison sont attachés avec un verrou. C'est un moyen de montrer que seuls les utilisateurs autorisés peuvent envoyer des demandes à ces points de terminaison. Les points de terminaison sans restriction sont les points de terminaison /register et /login . Une erreur avec le code d'état 401 doit être renvoyée lorsqu'un utilisateur non authentifié tente d'accéder à un point de terminaison restreint.

Après avoir connecté un utilisateur avec succès, le jeton d'accès ainsi que certaines données seront reçus dans l'application Vue, qui seront utilisées pour définir le cookie et jointes dans l'en-tête de la demande à utiliser pour les demandes futures. Le backend vérifiera l'en-tête de la demande chaque fois qu'une demande est faite à un point de terminaison restreint. Ne soyez pas tenté de stocker le jeton d'accès dans le stockage local.

( Grand aperçu )
Plus après saut! Continuez à lire ci-dessous ↓

Projet d'échafaudage

À l'aide de Vue CLI, exécutez la commande ci-dessous pour générer l'application :

 vue create auth-project

Accédez à votre nouveau dossier :

 cd auth-project

Ajoutez le vue-router et installez plus de dépendances — vuex et axios :

 vue add router npm install vuex axios

Exécutez maintenant votre projet et vous devriez voir ce que j'ai ci-dessous sur votre navigateur :

 npm run serve

1. Configuration de Vuex avec Axios

Axios est une bibliothèque JavaScript utilisée pour envoyer des requêtes du navigateur aux API. Selon la documentation de Vuex ;

"Vuex est un modèle de gestion d'état + une bibliothèque pour les applications Vue.js. Il sert de magasin centralisé pour tous les composants d'une application, avec des règles garantissant que l'état ne peut être muté que de manière prévisible.

Qu'est-ce que ça veut dire? Vuex est un magasin utilisé dans une application Vue qui nous permet d' enregistrer des données qui seront disponibles pour chaque composant et de fournir des moyens de modifier ces données. Nous utiliserons Axios dans Vuex pour envoyer nos demandes et apporter des modifications à notre état (données). Axios sera utilisé dans les actions Vuex pour envoyer GET et POST , la réponse obtenue sera utilisée pour envoyer des informations aux mutations et mettre à jour les données de notre magasin.

Pour gérer la réinitialisation de Vuex après l'actualisation, nous allons travailler avec vuex-persistedstate , une bibliothèque qui enregistre nos données Vuex entre les rechargements de page.

 npm install --save vuex-persistedstate

Créons maintenant un nouveau store de dossiers dans src , pour configurer le magasin Vuex. Dans le dossier du store , créez un nouveau dossier ; modules et un fichier index.js . Il est important de noter que vous n'avez besoin de le faire que si le dossier n'est pas créé automatiquement pour vous.

 import Vuex from 'vuex'; import Vue from 'vue'; import createPersistedState from "vuex-persistedstate"; import auth from './modules/auth'; // Load Vuex Vue.use(Vuex); // Create store export default new Vuex.Store({ modules: { auth }, plugins: [createPersistedState()] });

Ici, nous utilisons Vuex et importons un module d'authentification du dossier modules dans notre magasin.

Modules

Les modules sont différents segments de notre magasin qui gèrent ensemble des tâches similaires, notamment :

  • Etat
  • Actions
  • mutations
  • getters

Avant de continuer, éditons notre fichier main.js

 import Vue from 'vue' import App from './App.vue' import router from './router'; import store from './store'; import axios from 'axios'; axios.defaults.withCredentials = true axios.defaults.baseURL = 'https://gabbyblog.herokuapp.com/'; Vue.config.productionTip = false new Vue({ store, router, render: h => h(App) }).$mount('#app')

Nous avons importé l'objet store du dossier ./store ainsi que le package Axios.

Comme mentionné précédemment, le cookie de jeton d'accès et les autres données nécessaires obtenues de l'API doivent être définis dans les en-têtes de requête pour les requêtes futures. Étant donné que nous utiliserons Axios lors des demandes, nous devons configurer Axios pour l'utiliser. Dans l'extrait ci-dessus, nous le faisons en utilisant axios.defaults.withCredentials = true , cela est nécessaire car par défaut les cookies ne sont pas transmis par Axios.

aaxios.defaults.withCredentials = true est une instruction à Axios pour envoyer toutes les demandes avec des informations d'identification telles que ; les en-têtes d'autorisation, les certificats client TLS ou les cookies (comme dans notre cas).

Nous définissons notre axios.defaults.baseURL pour notre requête Axios à notre API . Ainsi, chaque fois que nous envoyons via Axios, il utilise cette URL de base. Avec cela, nous pouvons ajouter uniquement nos points de terminaison tels que /register et /login à nos actions sans indiquer l'URL complète à chaque fois.

Maintenant, dans le dossier modules du store créez un fichier appelé auth.js

 //store/modules/auth.js import axios from 'axios'; const state = { }; const getters = { }; const actions = { }; const mutations = { }; export default { state, getters, actions, mutations };

state

Dans notre dict state , nous allons définir nos données, et leurs valeurs par défaut :

 const state = { user: null, posts: null, };

Nous définissons la valeur par défaut de state , qui est un objet qui contient user et posts avec leurs valeurs initiales comme null .

Actions

Les actions sont des fonctions qui sont utilisées pour commit une mutation pour changer l'état ou peuvent être utilisées pour dispatch , c'est-à-dire appeler une autre action. Il peut être appelé dans différents composants ou vues et engage alors des mutations de notre état ;

Enregistrer une action

Notre action Register prend des données de formulaire, envoie les données à notre point de terminaison /register et attribue la réponse à une variable response . Ensuite, nous enverrons notre nom d' username et password de passe de formulaire à notre action de login . De cette façon, nous connectons l'utilisateur après son inscription, il est donc redirigé vers la page /posts .

 async Register({dispatch}, form) { await axios.post('register', form) let UserForm = new FormData() UserForm.append('username', form.username) UserForm.append('password', form.password) await dispatch('LogIn', UserForm) },

Action de connexion

C'est ici que se produit l'authentification principale. Lorsqu'un utilisateur remplit son nom d'utilisateur et son mot de passe, il est transmis à un User qui est un objet FormData, la fonction LogIn prend l'objet User et envoie une requête POST au point de terminaison /login pour connecter l'utilisateur.

La fonction Login valide enfin le nom d' username dans la mutation setUser .

 async LogIn({commit}, User) { await axios.post('login', User) await commit('setUser', User.get('username')) },

Créer une action de publication

Notre action CreatePost est une fonction qui prend la post et l'envoie à notre point de terminaison /post , puis distribue l'action GetPosts . Cela permet à l'utilisateur de voir ses publications après leur création.

 async CreatePost({dispatch}, post) { await axios.post('post', post) await dispatch('GetPosts') },

Obtenir l'action des publications

Notre action GetPosts envoie une requête GET à notre point de terminaison /posts pour récupérer les publications dans notre API et valide la mutation setPosts .

 async GetPosts({ commit }){ let response = await axios.get('posts') commit('setPosts', response.data) },

Action de déconnexion

 async LogOut({commit}){ let user = null commit('logout', user) }

Notre action de LogOut supprime notre user du cache du navigateur. Il le fait en commitant une logout :

mutation

 const mutations = { setUser(state, username){ state.user = username }, setPosts(state, posts){ state.posts = posts }, LogOut(state){ state.user = null state.posts = null }, };

Chaque mutation prend l' state et une valeur de l'action qui la commet, à part Logout . La valeur obtenue est utilisée pour modifier certaines parties ou toutes ou comme dans LogOut , remettez toutes les variables à null.

Getters

Les getters sont des fonctionnalités permettant d'obtenir l'état. Il peut être utilisé dans plusieurs composants pour obtenir l'état actuel. La fonction isAuthenticatated vérifie si state.user est défini ou null et renvoie respectivement true ou false . StatePosts et StateUser renvoient respectivement la valeur state.posts et state.user .

 const getters = { isAuthenticated: state => !!state.user, StatePosts: state => state.posts, StateUser: state => state.user, };

Maintenant, tout votre fichier auth.js devrait ressembler à mon code sur GitHub.

Configuration des composants

1. Composants NavBar.vue et App.vue

Dans votre dossier src/components , supprimez HelloWorld.vue et un nouveau fichier appelé NavBar.vue .

Il s'agit du composant de notre barre de navigation, il relie les différentes pages de notre composant qui ont été routées ici. Chaque lien de routeur pointe vers une route/page sur notre application.

Le v-if="isLoggedIn" est une condition pour afficher le lien de Logout si un utilisateur est connecté et masquer les routes d' Register et de Login . Nous avons une méthode de logout qui ne peut être accessible qu'aux utilisateurs connectés, elle sera appelée lorsque le lien de Logout sera cliqué. Il enverra l'action LogOut puis dirigera l'utilisateur vers la page de connexion.

 <template> <div> <router-link to="/">Home</router-link> | <router-link to="/posts">Posts</router-link> | <span v-if="isLoggedIn"> <a @click="logout">Logout</a> </span> <span v-else> <router-link to="/register">Register</router-link> | <router-link to="/login">Login</router-link> </span> </div> </template> <script> export default { name: 'NavBar', computed : { isLoggedIn : function(){ return this.$store.getters.isAuthenticated} }, methods: { async logout (){ await this.$store.dispatch('LogOut') this.$router.push('/login') } }, } </script> <style> #nav { padding: 30px; } #nav a { font-weight: bold; color: #2c3e50; } a:hover { cursor: pointer; } #nav a.router-link-exact-active { color: #42b983; } </style>

Modifiez maintenant votre composant App.vue pour qu'il ressemble à ceci :

 <template> <div> <NavBar /> <router-view/> </div> </template> <script> // @ is an alias to /src import NavBar from '@/components/NavBar.vue' export default { components: { NavBar } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } </style>

Ici, nous avons importé le composant NavBar que nous avons créé ci-dessus et placé dans la section template avant le <router-view /> .

2. Composants des vues

Les composants de vues sont différentes pages de l'application qui seront définies sous un itinéraire et accessibles depuis la barre de navigation. Pour commencer Allez dans le dossier des views , supprimez le composant About.vue et ajoutez les composants suivants :

  • Home.vue
  • Register.vue
  • Login.vue
  • Posts.vue

Home.vue

Réécrivez le Home.vue pour qu'il ressemble à ceci :

 <template> <div class="home"> <p>Heyyyyyy welcome to our blog, check out our posts</p> </div> </template> <script> export default { name: 'Home', components: { } } </script>

Cela affichera un texte de bienvenue aux utilisateurs lorsqu'ils visiteront la page d'accueil.

Register.vue

Il s'agit de la page que nous voulons que nos utilisateurs puissent s'inscrire sur notre application. Lorsque les utilisateurs remplissent le formulaire, leurs informations sont envoyées à l'API et ajoutées à la base de données, puis connectées.

En regardant l'API, le point de terminaison /register nécessite un nom d' username , un nom full_name et un mot de password de notre utilisateur. Créons maintenant une page et un formulaire pour obtenir ces informations :

 <template> <div class="register"> <div> <form @submit.prevent="submit"> <div> <label for="username">Username:</label> <input type="text" name="username" v-model="form.username"> </div> <div> <label for="full_name">Full Name:</label> <input type="text" name="full_name" v-model="form.full_name"> </div> <div> <label for="password">Password:</label> <input type="password" name="password" v-model="form.password"> </div> <button type="submit"> Submit</button> </form> </div> <p v-if="showError">Username already exists</p> </div> </template>

Dans le composant Register , nous devrons appeler l'action Register qui recevra les données du formulaire.

 <script> import { mapActions } from "vuex"; export default { name: "Register", components: {}, data() { return { form: { username: "", full_name: "", password: "", }, showError: false }; }, methods: { ...mapActions(["Register"]), async submit() { try { await this.Register(this.form); this.$router.push("/posts"); this.showError = false } catch (error) { this.showError = true } }, }, }; </script>

Nous commençons par importer mapActions depuis Vuex, ce que cela fait est d'importer des actions de notre magasin vers le composant. Cela nous permet d'appeler l'action depuis le composant.

data() contient la valeur d'état local qui sera utilisée dans ce composant, nous avons un objet de form qui contient username , full_name et password , avec leurs valeurs initiales définies sur une chaîne vide. Nous avons également showError qui est un booléen, à utiliser pour afficher ou non une erreur.

Dans les methods nous importons l'action Register à l'aide des Mapactions dans le composant, de sorte que l'action Register puisse être appelée avec this.Register .

Nous avons une méthode submit qui appelle l'action Register à laquelle nous avons accès en utilisant this.Register , en lui envoyant this.form . Si aucune error n'est rencontrée, nous utilisons this.$router pour envoyer l'utilisateur à la page de connexion. Sinon, nous définissons showError sur true.

Cela fait, nous pouvons inclure un peu de style.

 <style scoped> * { box-sizing: border-box; } label { padding: 12px 12px 12px 0; display: inline-block; } button[type=submit] { background-color: #4CAF50; color: white; padding: 12px 20px; cursor: pointer; border-radius:30px; } button[type=submit]:hover { background-color: #45a049; } input { margin: 5px; box-shadow:0 0 15px 4px rgba(0,0,0,0.06); padding:10px; border-radius:30px; } #error { color: red; } </style>

Login.vue

Notre page de connexion est l'endroit où les utilisateurs enregistrés entreront leur nom d' username et leur mot de password pour s'authentifier par l'API et se connecter à notre site.

 <template> <div class="login"> <div> <form @submit.prevent="submit"> <div> <label for="username">Username:</label> <input type="text" name="username" v-model="form.username" /> </div> <div> <label for="password">Password:</label> <input type="password" name="password" v-model="form.password" /> </div> <button type="submit">Submit</button> </form> <p v-if="showError">Username or Password is incorrect</p> </div> </div> </template>

Maintenant, nous devrons transmettre nos données de formulaire à l'action qui envoie la demande, puis les pousser vers la page sécurisée Posts

 <script> import { mapActions } from "vuex"; export default { name: "Login", components: {}, data() { return { form: { username: "", password: "", }, showError: false }; }, methods: { ...mapActions(["LogIn"]), async submit() { const User = new FormData(); User.append("username", this.form.username); User.append("password", this.form.password); try { await this.LogIn(User); this.$router.push("/posts"); this.showError = false } catch (error) { this.showError = true } }, }, }; </script>

Nous importons Mapactions et l'utilisons pour importer l'action LogIn dans le composant, qui sera utilisé dans notre fonction submit .

Après l'action de Login , l'utilisateur est redirigé vers la page /posts . En cas d'erreur, l'erreur est détectée et ShowError est défini sur true.

Maintenant, un peu de style :

 <style scoped> * { box-sizing: border-box; } label { padding: 12px 12px 12px 0; display: inline-block; } button[type=submit] { background-color: #4CAF50; color: white; padding: 12px 20px; cursor: pointer; border-radius:30px; } button[type=submit]:hover { background-color: #45a049; } input { margin: 5px; box-shadow:0 0 15px 4px rgba(0,0,0,0.06); padding:10px; border-radius:30px; } #error { color: red; } </style>

Posts.vue

Notre page Messages est la page sécurisée qui n'est disponible que pour les utilisateurs authentifiés. Sur cette page, ils ont accès aux publications de la base de données de l'API. Cela permet aux utilisateurs d'avoir accès aux publications et leur permet également de créer des publications sur l'API.

 <template> <div class="posts"> <div v-if="User"> <p>Hi {{User}}</p> </div> <div> <form @submit.prevent="submit"> <div> <label for="title">Title:</label> <input type="text" name="title" v-model="form.title"> </div> <div> <textarea name="write_up" v-model="form.write_up" placeholder="Write up..."></textarea> </div> <button type="submit"> Submit</button> </form> </div> <div class="posts" v-if="Posts"> <ul> <li v-for="post in Posts" :key="post.id"> <div> <p>{{post.title}}</p> <p>{{post.write_up}}</p> <p>Written By: {{post.author.username}}</p> </div> </li> </ul> </div> <div v-else> Oh no!!! We have no posts </div> </div> </template>

Dans le code ci-dessus, nous avons un formulaire permettant à l'utilisateur de créer de nouveaux messages. La soumission du formulaire devrait entraîner l'envoi du message à l'API - nous ajouterons la méthode qui le fait sous peu. Nous avons également une section qui affiche les publications obtenues à partir de l'API (au cas où l'utilisateur en aurait). Si l'utilisateur n'a pas de messages, nous affichons simplement un message indiquant qu'il n'y a pas de messages.

Les getters StateUser et StatePosts sont mappés, c'est-à-dire importés à l'aide mapGetters dans Posts.vue , puis ils peuvent être appelés dans le modèle.

 <script> import { mapGetters, mapActions } from "vuex"; export default { name: 'Posts', components: { }, data() { return { form: { title: '', write_up: '', } }; }, created: function () { // a function to call getposts action this.GetPosts() }, computed: { ...mapGetters({Posts: "StatePosts", User: "StateUser"}), }, methods: { ...mapActions(["CreatePost", "GetPosts"]), async submit() { try { await this.CreatePost(this.form); } catch (error) { throw "Sorry you can't make a post now!" } }, } }; </script>

Nous avons un état initial pour form , qui est un objet qui a title et write_up comme clés et les valeurs sont définies sur une chaîne vide. Ces valeurs seront remplacées par tout ce que l'utilisateur entrera dans le formulaire dans la section modèle de notre composant.

Lorsque l'utilisateur soumet le message, nous appelons le this.CreatePost qui reçoit l'objet de formulaire.

Comme vous pouvez le voir dans le cycle de vie created , nous avons this.GetPosts pour récupérer les publications lorsque le composant est créé.

Un peu de style,

 <style scoped> * { box-sizing: border-box; } label { padding: 12px 12px 12px 0; display: inline-block; } button[type=submit] { background-color: #4CAF50; color: white; padding: 12px 20px; cursor: pointer; border-radius:30px; margin: 10px; } button[type=submit]:hover { background-color: #45a049; } input { width:60%; margin: 15px; border: 0; box-shadow:0 0 15px 4px rgba(0,0,0,0.06); padding:10px; border-radius:30px; } textarea { width:75%; resize: vertical; padding:15px; border-radius:15px; border:0; box-shadow:0 0 15px 4px rgba(0,0,0,0.06); height:150px; margin: 15px; } ul { list-style: none; } #post-div { border: 3px solid #000; width: 500px; margin: auto; margin-bottom: 5px;; } </style>

2. Définir les itinéraires

Dans notre fichier router/index.js , importez nos vues et définissez des routes pour chacune d'elles

 import Vue from 'vue' import VueRouter from 'vue-router' import store from '../store'; import Home from '../views/Home.vue' import Register from '../views/Register' import Login from '../views/Login' import Posts from '../views/Posts' Vue.use(VueRouter) const routes = [ { path: '/', name: 'Home', component: Home }, { path: '/register', name: "Register", component: Register, meta: { guest: true }, }, { path: '/login', name: "Login", component: Login, meta: { guest: true }, }, { path: '/posts', name: Posts, component: Posts, meta: {requiresAuth: true}, } ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) export default router

3. Gestion des utilisateurs

  • Utilisateurs non autorisés
    Si vous avez remarqué dans la définition des routes de nos publications nous avons ajouté une clé meta pour indiquer que l'utilisateur doit être authentifié, maintenant nous avons besoin d'un router.BeforeEach garde de navigation qui vérifie si une route a la clé meta: {requiresAuth: true} . Si une route a la meta -clé, elle recherche un jeton dans le magasin ; s'il est présent, il les redirige vers la route de login .
 const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) router.beforeEach((to, from, next) => { if(to.matched.some(record => record.meta.requiresAuth)) { if (store.getters.isAuthenticated) { next() return } next('/login') } else { next() } }) export default router
  • Utilisateurs autorisés
    Nous avons également une meta sur les routes /register et /login . La meta: {guest: true} empêche les utilisateurs connectés d'accéder aux routes avec la méta guest .
 router.beforeEach((to, from, next) => { if (to.matched.some((record) => record.meta.guest)) { if (store.getters.isAuthenticated) { next("/posts"); return; } next(); } else { next(); } });

Au final, votre fichier devrait ressembler à ceci :

 import Vue from "vue"; import VueRouter from "vue-router"; import store from "../store"; import Home from "../views/Home.vue"; import Register from "../views/Register"; import Login from "../views/Login"; import Posts from "../views/Posts"; Vue.use(VueRouter); const routes = [ { path: "/", name: "Home", component: Home, }, { path: "/register", name: "Register", component: Register, meta: { guest: true }, }, { path: "/login", name: "Login", component: Login, meta: { guest: true }, }, { path: "/posts", name: "Posts", component: Posts, meta: { requiresAuth: true }, }, ]; const router = new VueRouter({ mode: "history", base: process.env.BASE_URL, routes, }); router.beforeEach((to, from, next) => { if (to.matched.some((record) => record.meta.requiresAuth)) { if (store.getters.isAuthenticated) { next(); return; } next("/login"); } else { next(); } }); router.beforeEach((to, from, next) => { if (to.matched.some((record) => record.meta.guest)) { if (store.getters.isAuthenticated) { next("/posts"); return; } next(); } else { next(); } }); export default router;

4.Traitement du jeton expiré (demandes interdites)

Notre API est configurée pour expirer les jetons après 30 minutes, maintenant si nous essayons d'accéder à la page des posts après 30 minutes, nous obtenons une erreur 401 , ce qui signifie que nous devons nous reconnecter, nous allons donc définir un intercepteur qui lit si nous obtenons un Erreur 401 , puis il nous redirige vers la page de login .

Ajoutez l'extrait ci-dessous après la déclaration d'URL par défaut d'Axios dans le fichier main.js

 axios.interceptors.response.use(undefined, function (error) { if (error) { const originalRequest = error.config; if (error.response.status === 401 && !originalRequest._retry) { originalRequest._retry = true; store.dispatch('LogOut') return router.push('/login') } } })

Cela devrait amener votre code au même état que l'exemple sur GitHub.

Conclusion

Si vous avez été en mesure de suivre jusqu'à la fin, vous devriez maintenant être en mesure de créer une application frontale entièrement fonctionnelle et sécurisée. Vous en savez maintenant plus sur Vuex et sur la façon de l'intégrer à Axios, ainsi que sur la sauvegarde de ses données après le rechargement.

  • Le code est disponible sur GitHub →

  • Site hébergé : https://nifty-hopper-1e9895.netlify.app/

  • API : https://gabbyblog.herokuapp.com

  • Documentation API : https://gabbyblog.herokuapp.com/docs

Ressources

  • "Gérer les cookies avec Axios", Aditya Srivastava, Medium
  • "Création d'une protection de navigation d'authentification dans Vue", Laurie Barth, blog Ten Mile Square
  • « Premiers pas avec Vuex », Guide officiel
  • "Authentification Vue.js JWT avec Vuex et Vue Router", BezKoder