Autentificare în Vue.js
Publicat: 2022-03-10Autentificarea 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:
- Configurare Vuex cu Axios
- Definirea rutelor
- Manipularea Utilizatorilor
- 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ă.
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 ometa
-cheie pentru a indica faptul că utilizatorul trebuie să fie autentificat, acum trebuie să avem unrouter.BeforeEach
de fiecare gardian de navigare care verifică dacă o rută aremeta: {requiresAuth: true}
. Dacă o rută aremeta
-cheia, verifică magazinul pentru un token; dacă există, îi redirecționează către ruta delogin
.
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 unmeta
pe rutele/register
și/login
.meta: {guest: true}
oprește utilizatorii care sunt conectați să acceseze rutele cu metaguest
.
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