Autentificare în Vue.js

Publicat: 2022-03-10
Rezumat rapid ↬ Fiecare aplicație web care gestionează date specifice utilizatorului trebuie să implementeze autentificarea. Este important să știi cum să faci acest lucru pentru dezvoltatorii Vue și despre asta își propune acest articol să pună în lumină. Acest tutorial se va dovedi a fi util pentru dezvoltatorii începători care doresc să învețe despre autentificare în Vue. Pentru a putea urma, va trebui să aveți cunoștințe bune despre Vue și Vuex.

Autentificarea este o caracteristică foarte necesară pentru aplicațiile care stochează datele utilizatorului. Este un proces de verificare a identității utilizatorilor, asigurându-se că utilizatorii neautorizați nu pot accesa date private - date aparținând altor utilizatori. Acest lucru duce la existența unor rute restricționate care pot fi accesate doar de utilizatorii autentificați. Acești utilizatori autentificați sunt verificați utilizând detaliile lor de conectare (adică numele de utilizator/e-mail și parolă) și atribuindu-le un token pentru a fi utilizat pentru a accesa resursele protejate ale unei aplicații.

În acest articol, veți învăța despre:

  1. Configurare Vuex cu Axios
  2. Definirea rutelor
  3. Manipularea Utilizatorilor
  4. Gestionarea simbolului expirat

Dependente

Vom lucra cu următoarele dependențe care ajută la autentificare:

  • Axios
    Pentru trimiterea și preluarea datelor din API-ul nostru
  • Vuex
    Pentru stocarea datelor obținute din API-ul nostru
  • Vue-Router
    Pentru navigarea și protecția rutelor

Vom lucra cu aceste instrumente și vom vedea cum pot lucra împreună pentru a oferi o funcționalitate robustă de autentificare pentru aplicația noastră.

API-ul Backend

Vom construi un blog simplu, care va folosi acest API. Puteți consulta documentele pentru a vedea punctele finale și cum trebuie trimise solicitările.

Din documente, veți observa că câteva puncte finale sunt atașate cu un lacăt. Aceasta este o modalitate de a arăta că numai utilizatorii autorizați pot trimite cereri către acele puncte finale. Punctele finale nerestricționate sunt punctele finale /register și /login . O eroare cu codul de stare 401 ar trebui returnată atunci când un utilizator neautentificat încearcă să acceseze un punct final restricționat.

După conectarea cu succes a unui utilizator, token-ul de acces alături de unele date va fi primit în aplicația Vue, care va fi folosită la setarea cookie-ului și atașat în antetul cererii pentru a fi utilizat pentru cererile viitoare. Backend-ul va verifica antetul solicitării de fiecare dată când se face o solicitare către un punct final restricţionat. Nu fi tentat să stocați jetonul de acces în stocarea locală.

(Previzualizare mare)
Mai multe după săritură! Continuați să citiți mai jos ↓

Proiectul schelei

Folosind Vue CLI, rulați comanda de mai jos pentru a genera aplicația:

 vue create auth-project

Navigați în noul folder:

 cd auth-project

Adăugați router-ul vue și instalați mai multe dependențe - vuex și axios:

 vue add router npm install vuex axios

Acum rulați proiectul și ar trebui să vedeți ce am mai jos în browser:

 npm run serve

1. Configurare Vuex cu Axios

Axios este o bibliotecă JavaScript care este utilizată pentru a trimite solicitări din browser către API-uri. Conform documentației Vuex;

„Vuex este un model de gestionare a stării + bibliotecă pentru aplicațiile Vue.js. Acesta servește ca un magazin centralizat pentru toate componentele unei aplicații, cu reguli care asigură că statul poate fi mutat doar într-un mod previzibil.”

Ce inseamna asta? Vuex este un magazin folosit într-o aplicație Vue, care ne permite să salvăm date care vor fi disponibile pentru fiecare componentă și să furnizăm modalități de modificare a acestor date. Vom folosi Axios în Vuex pentru a ne trimite solicitările și pentru a face modificări în starea noastră (date). Axios va fi folosit în actions Vuex pentru a trimite GET și POST , răspunsul obținut va fi folosit pentru a trimite informații către mutations și care actualizează datele din magazinul nostru.

Pentru a face față resetării Vuex după reîmprospătare, vom lucra cu vuex-persistedstate , o bibliotecă care salvează datele noastre Vuex între reîncărcările paginii.

 npm install --save vuex-persistedstate

Acum să creăm un nou store foldere în src , pentru configurarea magazinului Vuex. În folderul store , creați un folder nou; modules și un fișier index.js . Este important să rețineți că trebuie să faceți acest lucru doar dacă folderul nu este creat automat pentru dvs.

 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()] });

Aici folosim Vuex și importăm un module de autentificare din folderul modules în magazinul nostru.

Module

Modulele sunt segmente diferite ale magazinului nostru care se ocupă împreună de sarcini similare, inclusiv:

  • stat
  • actiuni
  • mutatii
  • getters

Înainte de a continua, să edităm fișierul nostru 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')

Am importat obiectul store din folderul ./store , precum și pachetul Axios.

După cum s-a menționat mai devreme, cookie-ul token de acces și alte date necesare obținute de la API trebuie setate în antetele solicitărilor pentru cereri viitoare. Deoarece vom folosi Axios atunci când facem cereri, trebuie să configuram Axios să folosească acest lucru. În fragmentul de mai sus, facem asta folosind axios.defaults.withCredentials = true , acest lucru este necesar deoarece cookie-urile implicite nu sunt transmise de Axios.

aaxios.defaults.withCredentials = true este o instrucțiune către Axios pentru a trimite toate cererile cu acreditări precum; anteturi de autorizare, certificate de client TLS sau cookie-uri (ca și în cazul nostru).

Am setat axios.defaults.baseURL pentru cererea noastră Axios la API -ul nostru. În acest fel, ori de câte ori trimitem prin Axios, folosește această adresă URL de bază. Cu asta, putem adăuga doar punctele noastre finale, cum ar fi /register și /login , la acțiunile noastre, fără a indica adresa URL completă de fiecare dată.

Acum, în folderul modules din store creați un fișier numit 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

În dictatul nostru state , vom defini datele noastre și valorile lor implicite:

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

Setăm valoarea implicită a state , care este un obiect care conține user și posts cu valorile lor inițiale ca null .

Acțiuni

Acțiunile sunt funcții care sunt folosite pentru a commit o mutație pentru a schimba starea sau pot fi folosite pentru a dispatch , adică pentru a apela o altă acțiune. Poate fi numit în diferite componente sau vederi și apoi comite mutații ale stării noastre;

Înregistrați acțiunea

Acțiunea noastră Register preia date în formă, trimite datele către punctul nostru de terminare /register și atribuie răspunsul unui response variabil . În continuare, vom trimite numele de username și password formularului la acțiunea noastră de login . În acest fel, ne autentificăm utilizatorul după ce s-a înscris, astfel încât acesta să fie redirecționat către pagina /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) },

Acțiune de conectare

Aici are loc autentificarea principală. Când un utilizator își completează numele de utilizator și parola, acestea sunt transmise unui User care este un obiect FormData, funcția LogIn preia obiectul User și face o solicitare POST către punctul final /login pentru a conecta utilizatorul.

Funcția de Login trimite în cele din urmă numele de username la mutația setUser .

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

Creați o acțiune post

Acțiunea noastră CreatePost este o funcție, care preia post și o trimite la punctul nostru final /post , apoi trimite acțiunea GetPosts . Acest lucru permite utilizatorului să-și vadă postările după creare.

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

Obțineți acțiune pentru postări

Acțiunea noastră GetPosts trimite o solicitare GET către punctul nostru final /posts pentru a prelua postările din API-ul nostru și comite mutația setPosts .

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

Acțiune de deconectare

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

Acțiunea noastră LogOut elimină user nostru din memoria cache a browserului. Face acest lucru comitând o logout :

Mutații

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

Fiecare mutație ia state și o valoare din acțiunea care o comite, în afară de Logout . Valoarea obținută este folosită pentru a schimba anumite părți sau toate sau ca în LogOut setați toate variabilele înapoi la null.

Getters

Getters sunt funcționalități pentru a obține statul. Poate fi folosit în mai multe componente pentru a obține starea curentă. Funcția isAuthenticatated verifică dacă state.user este definit sau nul și returnează true sau, respectiv, false . StatePosts și StateUser returnează state.posts și, respectiv, state.user valoare.

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

Acum, întregul fișier auth.js ar trebui să semene cu codul meu de pe GitHub.

Configurarea componentelor

1. Componentele NavBar.vue și App.vue

În folderul src/components , ștergeți HelloWorld.vue și un fișier nou numit NavBar.vue .

Aceasta este componenta pentru bara noastră de navigare, ea face link-uri către diferite pagini ale componentei noastre care au fost direcționate aici. Fiecare link de router indică o rută/pagină din aplicația noastră.

v-if="isLoggedIn" este o condiție pentru a afișa linkul de Logout dacă un utilizator este conectat și pentru a ascunde rutele de Register și de Login . Avem o metodă de logout care poate fi accesibilă numai utilizatorilor conectați, aceasta va fi apelată când se face clic pe linkul Logout . Acesta va trimite acțiunea LogOut și apoi va direcționa utilizatorul către pagina de conectare.

 <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>

Acum editați componenta App.vue pentru a arăta astfel:

 <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>

Aici am importat componenta NavBar pe care am creat-o mai sus și am plasat-o în secțiunea șablon înainte de <router-view /> .

2. Vizualizări Componente

Componentele vizualizărilor sunt pagini diferite din aplicație care vor fi definite sub o rută și pot fi accesate din bara de navigare. Pentru a începe Accesați folderul views , ștergeți componenta About.vue și adăugați următoarele componente:

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

Home.vue

Rescrie Home.vue pentru a arăta astfel:

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

Acest lucru va afișa un text de bun venit utilizatorilor atunci când accesează pagina de pornire.

Register.vue

Aceasta este pagina pe care dorim ca utilizatorii noștri să se poată înscrie în aplicația noastră. Când utilizatorii completează formularul, informațiile lor sunt trimise la API și adăugate la baza de date, apoi sunt conectate.

Privind API-ul, punctul final /register necesită un nume de username , un full_name și o password a utilizatorului nostru. Acum să creăm o pagină și un formular pentru a obține acele informații:

 <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>

În componenta Register , va trebui să apelăm acțiunea Register care va primi datele formularului.

 <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>

Începem prin a importa mapActions din Vuex, ceea ce face este să importăm acțiuni din magazinul nostru în componentă. Acest lucru ne permite să apelăm acțiunea din componentă.

data() conține valoarea locală a stării care va fi folosită în această componentă, avem un obiect form care conține username , full_name și password , cu valorile lor inițiale setate la un șir gol. Avem, de asemenea, showError , care este un boolean, pentru a fi folosit fie pentru a afișa o eroare, fie nu.

În methods importăm acțiunea Register folosind Mapactions în componentă, astfel încât acțiunea Register poate fi apelată cu this.Register .

Avem o metodă de trimitere care numește acțiunea Register la care avem acces utilizând this.Register , trimițându-i this.form . Dacă nu se întâlnește nicio error , folosim this.$router pentru a trimite utilizatorul la pagina de conectare. În caz contrar, setăm showError la adevărat.

După ce am făcut asta, putem include unele stiluri.

 <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

Pagina noastră de conectare este locul în care utilizatorii înregistrați își vor introduce numele de username și password pentru a se autentifica de API și se vor conecta la site-ul nostru.

 <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>

Acum va trebui să transmitem datele formularului nostru acțiunii care trimite solicitarea și apoi să le împingem către pagina securizată 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>

Importăm Mapactions și le folosim în importul acțiunii LogIn în componentă, care va fi folosită în funcția noastră de submit .

După acțiunea de Login , utilizatorul este redirecționat către pagina /posts . În cazul unei erori, eroarea este detectată și ShowError este setat la adevărat.

Acum, ceva stil:

 <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

Pagina noastră Postări este pagina securizată care este disponibilă numai pentru utilizatorii autentificați. Pe această pagină, au acces la postări din baza de date a API-ului. Acest lucru permite utilizatorilor să aibă acces la postări și, de asemenea, le permite să creeze postări în 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>

În codul de mai sus, avem un formular pentru ca utilizatorul să poată crea postări noi. Trimiterea formularului ar trebui să determine trimiterea postării către API - vom adăuga metoda care face asta în scurt timp. Avem și o secțiune care afișează postările obținute din API (în cazul în care utilizatorul are vreuna). Dacă utilizatorul nu are nicio postare, pur și simplu afișăm un mesaj că nu există postări.

Getter- StateUser și StatePosts sunt mapate, adică importate folosind mapGetters în Posts.vue și apoi pot fi apelate în șablon.

 <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>

Avem o stare inițială pentru form , care este un obiect care are title și write_up ca chei, iar valorile sunt setate la un șir gol. Aceste valori se vor schimba în orice introduce utilizatorul în formular în secțiunea șablon a componentei noastre.

Când utilizatorul trimite postarea, numim this.CreatePost care primește obiectul formular.

După cum puteți vedea în ciclul de viață created , avem acest this.GetPosts pentru a prelua postările atunci când componenta este creată.

Un pic de stil,

 <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. Definirea rutelor

În fișierul nostru router/index.js , importați vizualizările noastre și definiți rute pentru fiecare dintre ele

 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. Manipularea Utilizatorilor

  • Utilizatori neautorizați
    Dacă ați observat că în definirea rutelor postărilor noastre am adăugat o meta -cheie pentru a indica faptul că utilizatorul trebuie să fie autentificat, acum trebuie să avem un router.BeforeEach de fiecare gardian de navigare care verifică dacă o rută are meta: {requiresAuth: true} . Dacă o rută are meta -cheia, verifică magazinul pentru un token; dacă există, îi redirecționează către ruta 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
  • Utilizatori autorizați
    Avem și un meta pe rutele /register și /login . meta: {guest: true} oprește utilizatorii care sunt conectați să acceseze rutele cu meta guest .
 router.beforeEach((to, from, next) => { if (to.matched.some((record) => record.meta.guest)) { if (store.getters.isAuthenticated) { next("/posts"); return; } next(); } else { next(); } });

În cele din urmă, fișierul dvs. ar trebui să fie așa:

 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. Gestionarea simbolului expirat (cereri interzise)

API-ul nostru este setat să expire token-urile după 30 de minute, acum dacă încercăm să accesăm pagina de posts după 30 de minute, primim o eroare 401 , ceea ce înseamnă că trebuie să ne autentificăm din nou, așa că vom seta un interceptor care citește dacă obținem un Eroare 401 , apoi ne redirecționează înapoi la pagina de login .

Adăugați fragmentul de mai jos după declarația URL implicită Axios în fișierul 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') } } })

Acest lucru ar trebui să vă ducă codul în aceeași stare ca exemplul de pe GitHub.

Concluzie

Dacă ați reușit să urmăriți până la sfârșit, acum ar trebui să puteți construi o aplicație front-end complet funcțională și sigură. Acum ați aflat mai multe despre Vuex și despre cum să îl integrați cu Axios și, de asemenea, despre cum să-i salvați datele după reîncărcare.

  • Codul este disponibil pe GitHub →

  • Site găzduit: https://nifty-hopper-1e9895.netlify.app/

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

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

Resurse

  • „Manevrarea cookie-urilor cu Axios”, Aditya Srivastava, medie
  • „Crearea unui gard de navigație de autentificare în Vue”, Laurie Barth, blogul Ten Mile Square
  • „Noțiuni introductive cu Vuex”, Ghid oficial
  • „Autentificare JWT Vue.js cu Router Vuex și Vue”, BezKoder