Autenticazione in Vue.js
Pubblicato: 2022-03-10L'autenticazione è una funzionalità molto necessaria per le applicazioni che memorizzano i dati degli utenti. È un processo di verifica dell'identità degli utenti, assicurando che gli utenti non autorizzati non possano accedere a dati privati, dati appartenenti ad altri utenti. Ciò porta ad avere percorsi limitati a cui possono accedere solo gli utenti autenticati. Questi utenti autenticati vengono verificati utilizzando i loro dati di accesso (es. nome utente/e-mail e password) e assegnando loro un token da utilizzare per accedere alle risorse protette di un'applicazione.
In questo articolo imparerai a conoscere:
- Configurazione Vuex con Axios
- Definizione di percorsi
- Gestione degli utenti
- Gestione del token scaduto
Dipendenze
Lavoreremo con le seguenti dipendenze che aiutano nell'autenticazione:
- Asso
Per inviare e recuperare dati dalla nostra API - Vuex
Per la memorizzazione dei dati ottenuti dalla nostra API - Vue-Router
Per la navigazione e la protezione delle Rotte
Lavoreremo con questi strumenti e vedremo come possono collaborare per fornire solide funzionalità di autenticazione per la nostra app.
L'API di back-end
Costruiremo un semplice blog, che utilizzerà questa API. Puoi controllare i documenti per vedere gli endpoint e come devono essere inviate le richieste.
Dai documenti, noterai che alcuni endpoint sono collegati con un lucchetto. Questo è un modo per dimostrare che solo gli utenti autorizzati possono inviare richieste a tali endpoint. Gli endpoint senza restrizioni sono gli endpoint /register
e /login
. Un errore con il codice di stato 401
dovrebbe essere restituito quando un utente non autenticato tenta di accedere a un endpoint limitato.
Dopo aver effettuato correttamente l'accesso di un utente, il token di accesso insieme ad alcuni dati verrà ricevuto nell'app Vue, che verrà utilizzata per impostare il cookie e allegato nell'intestazione della richiesta da utilizzare per richieste future. Il back-end controllerà l'intestazione della richiesta ogni volta che viene effettuata una richiesta a un endpoint limitato. Non essere tentato di archiviare il token di accesso nella memoria locale.
Progetto ponteggio
Utilizzando Vue CLI, eseguire il comando seguente per generare l'applicazione:
vue create auth-project
Naviga nella tua nuova cartella:
cd auth-project
Aggiungi il router vue e installa più dipendenze — vuex e axios:
vue add router npm install vuex axios
Ora esegui il tuo progetto e dovresti vedere cosa ho qui sotto sul tuo browser:
npm run serve
1. Configurazione Vuex con Axios
Axios è una libreria JavaScript utilizzata per inviare richieste dal browser alle API. Secondo la documentazione Vuex;
“Vuex è un modello di gestione dello stato + libreria per le applicazioni Vue.js. Funge da archivio centralizzato per tutti i componenti di un'applicazione, con regole che assicurano che lo stato possa essere mutato solo in modo prevedibile".
Cosa significa? Vuex è un negozio utilizzato in un'applicazione Vue che ci consente di salvare i dati che saranno disponibili per ogni componente e fornire modi per modificare tali dati. Useremo Axios in Vuex per inviare le nostre richieste e apportare modifiche al nostro stato (dati). Axios verrà utilizzato nelle actions
Vuex per inviare GET
e POST
, la risposta ottenuta verrà utilizzata per inviare informazioni alle mutations
e che aggiorna i dati del nostro negozio.
Per gestire il ripristino di Vuex dopo l'aggiornamento, lavoreremo con vuex-persistedstate
, una libreria che salva i nostri dati Vuex tra i ricaricamenti della pagina.
npm install --save vuex-persistedstate
Ora creiamo un nuovo store
di cartelle in src
, per la configurazione del negozio Vuex. Nella cartella store
, crea una nuova cartella; modules
e un file index.js
. È importante notare che devi farlo solo se la cartella non viene creata automaticamente per te.
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()] });
Qui stiamo facendo uso di Vuex
e importiamo un module
di autenticazione dalla cartella dei modules
nel nostro negozio.
Moduli
I moduli sono segmenti diversi del nostro negozio che gestiscono insieme attività simili, tra cui:
- stato
- Azioni
- mutazioni
- getter
Prima di procedere, modifichiamo il nostro file 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')
Abbiamo importato l'oggetto store
dalla cartella ./store
e dal pacchetto Axios.
Come accennato in precedenza, il cookie del token di accesso e altri dati necessari ottenuti dall'API devono essere impostati nelle intestazioni delle richieste per richieste future. Dal momento che utilizzeremo Axios quando faremo richieste, dobbiamo configurare Axios per utilizzarlo. Nello snippet sopra, lo facciamo usando axios.defaults.withCredentials = true
, questo è necessario perché per impostazione predefinita i cookie non vengono passati da Axios.
aaxios.defaults.withCredentials = true
è un'istruzione ad Axios di inviare tutte le richieste con credenziali come; intestazioni di autorizzazione, certificati client TLS o cookie (come nel nostro caso).
Impostiamo il nostro axios.defaults.baseURL
per la nostra richiesta Axios sulla nostra API
In questo modo, ogni volta che inviamo tramite Axios, utilizza questo URL di base. Con ciò, possiamo aggiungere solo i nostri endpoint come /register
e /login
alle nostre azioni senza indicare ogni volta l'URL completo.
Ora all'interno della cartella dei modules
in store
crea un file chiamato 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
Nel nostro dict di state
, definiremo i nostri dati e i loro valori predefiniti:
const state = { user: null, posts: null, };
Stiamo impostando il valore predefinito di state
, che è un oggetto che contiene user
e posts
con i loro valori iniziali come null
.
Azioni
Le azioni sono funzioni che vengono utilizzate per commit
una mutazione per modificare lo stato o possono essere utilizzate per dispatch
, ad esempio, chiamate a un'altra azione. Può essere chiamato in diverse componenti o punti di vista e quindi commette mutazioni del nostro stato;
Registra azione
La nostra azione Register
prende i dati del modulo, invia i dati al nostro endpoint /register
e assegna la risposta a una response
variabile . Successivamente, invieremo il username
e la password
del modulo alla nostra azione di login
. In questo modo, accediamo all'utente dopo che si è registrato, in modo che venga reindirizzato alla 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) },
Azione di accesso
Qui è dove avviene l'autenticazione principale. Quando un utente inserisce il proprio nome utente e password, viene passato a un User
che è un oggetto FormData, la funzione LogIn
prende l'oggetto User
ed effettua una richiesta POST
all'endpoint /login
per accedere all'utente.
La funzione Login
, infine, assegna il username
alla mutazione setUser
.
async LogIn({commit}, User) { await axios.post('login', User) await commit('setUser', User.get('username')) },
Crea azione post
La nostra azione CreatePost
è una funzione che accetta il post
e lo invia al nostro endpoint /post
, quindi invia l'azione GetPosts
. Ciò consente all'utente di vedere i propri post dopo la creazione.
async CreatePost({dispatch}, post) { await axios.post('post', post) await dispatch('GetPosts') },
Ottieni un'azione sui post
La nostra azione GetPosts
invia una richiesta GET
al nostro endpoint /posts
per recuperare i post nella nostra API e commette la mutazione setPosts
.
async GetPosts({ commit }){ let response = await axios.get('posts') commit('setPosts', response.data) },
Azione di disconnessione
async LogOut({commit}){ let user = null commit('logout', user) }
La nostra azione LogOut
rimuove il nostro user
dalla cache del browser. Lo fa commettendo un logout
:
Mutazioni
const mutations = { setUser(state, username){ state.user = username }, setPosts(state, posts){ state.posts = posts }, LogOut(state){ state.user = null state.posts = null }, };
Ogni mutazione assume lo state
e un valore dall'azione che la commette, a parte Logout
. Il valore ottenuto viene utilizzato per modificare alcune parti o tutte o come in LogOut
riportare tutte le variabili su null.
Getter
I getter sono funzionalità per ottenere lo stato. Può essere utilizzato in più componenti per ottenere lo stato corrente. La funzione isAuthenticatated
controlla se state.user
è definito o null e restituisce rispettivamente true
o false
. StatePosts
e StateUser
restituiscono rispettivamente il valore state.posts
e state.user
.
const getters = { isAuthenticated: state => !!state.user, StatePosts: state => state.posts, StateUser: state => state.user, };
Ora il tuo intero file auth.js
dovrebbe assomigliare al mio codice su GitHub.
Configurazione dei componenti
1. Componenti NavBar.vue
e App.vue
Nella cartella src/components
, elimina HelloWorld.vue
e un nuovo file chiamato NavBar.vue
.
Questo è il componente per la nostra barra di navigazione, si collega a diverse pagine del nostro componente che sono state indirizzate qui. Ogni collegamento del router punta a un percorso/pagina sulla nostra app.
v-if="isLoggedIn"
è una condizione per visualizzare il collegamento Logout
se un utente ha effettuato l'accesso e nascondere i percorsi di Register
e di Login
. Abbiamo un metodo di logout
che può essere accessibile solo agli utenti che hanno effettuato l'accesso, questo verrà chiamato quando si fa clic sul collegamento Logout
. Invierà l'azione LogOut
e quindi indirizzerà l'utente alla pagina di accesso.
<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>
Ora modifica il tuo componente App.vue
in modo che assomigli a questo:
<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>
Qui abbiamo importato il componente NavBar che abbiamo creato sopra e posizionato nella sezione del modello prima di <router-view />
.
2. Visualizza i componenti
I componenti delle viste sono pagine diverse dell'app che verranno definite in un percorso e accessibili dalla barra di navigazione. Per iniziare Vai alla cartella delle views
, elimina il componente About.vue
e aggiungi i seguenti componenti:
-
Home.vue
-
Register.vue
-
Login.vue
-
Posts.vue
Home.vue
Riscrivi Home.vue
in modo che assomigli a questo:
<template> <div class="home"> <p>Heyyyyyy welcome to our blog, check out our posts</p> </div> </template> <script> export default { name: 'Home', components: { } } </script>
Questo mostrerà un testo di benvenuto agli utenti quando visitano la home page.
Register.vue
Questa è la Pagina che vogliamo che i nostri utenti possano registrarsi sulla nostra applicazione. Quando gli utenti compilano il modulo, le loro informazioni vengono inviate all'API e aggiunte al database, quindi effettuano l'accesso.
Guardando l'API, l'endpoint /register
richiede un username
, un full_name
e una password
del nostro utente. Ora creiamo una pagina e un modulo per ottenere queste informazioni:
<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>
Nel componente Register
, dovremo chiamare l'azione Register
che riceverà i dati del modulo.
<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>
Iniziamo importando mapActions
da Vuex, ciò che fa è importare azioni dal nostro negozio al componente. Questo ci consente di chiamare l'azione dal componente.
data()
contiene il valore dello stato locale che verrà utilizzato in questo componente, abbiamo un oggetto form
che contiene username
, full_name
e password
, con i loro valori iniziali impostati su una stringa vuota. Abbiamo anche showError
che è un valore booleano, da utilizzare per mostrare un errore o meno.
Nei methods
importiamo l'azione Register
usando Mapactions
nel componente, quindi l'azione Register
può essere chiamata con this.Register
.
Abbiamo un metodo di invio che chiama l'azione Register
a cui abbiamo accesso usando this.Register
, inviandolo this.form
. Se non viene riscontrato alcun error
, utilizziamo this.$router
per inviare l'utente alla pagina di accesso. Altrimenti impostiamo showError
su true.
Fatto ciò, possiamo includere un po' di stile.
<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
La nostra pagina di accesso è dove gli utenti registrati inseriranno il loro username
e password
per essere autenticati dall'API e accedere al nostro sito.
<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>
Ora dovremo passare i dati del nostro modulo all'azione che invia la richiesta e quindi spingerli alla pagina sicura 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>
Importiamo Mapactions
e lo utilizziamo per importare l'azione LogIn
nel componente, che verrà utilizzato nella nostra funzione di submit
.
Dopo l'azione di Login
, l'utente viene reindirizzato alla pagina /posts
. In caso di errore, l'errore viene rilevato e ShowError
viene impostato su true.
Ora, un po' di stile:
<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
La nostra pagina Post è la pagina protetta disponibile solo per gli utenti autenticati. In questa pagina, ottengono l'accesso ai post nel database dell'API. Ciò consente agli utenti di accedere ai post e consente loro anche di creare post per 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>
Nel codice sopra, abbiamo un modulo per consentire all'utente di creare nuovi post. L'invio del modulo dovrebbe causare l'invio del post all'API: aggiungeremo il metodo che lo farà a breve. Abbiamo anche una sezione che mostra i post ottenuti dall'API (nel caso in cui l'utente ne abbia). Se l'utente non ha alcun post, visualizziamo semplicemente un messaggio che indica che non ci sono post.
I getter StateUser
e StatePosts
vengono mappati, ovvero importati utilizzando mapGetters
in Posts.vue
e quindi possono essere richiamati nel modello.
<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>
Abbiamo uno stato iniziale per form
, che è un oggetto che ha title
e write_up
come chiavi e i valori sono impostati su una stringa vuota. Questi valori cambieranno in qualsiasi cosa l'utente inserisca nel modulo nella sezione del modello del nostro componente.
Quando l'utente invia il post, chiamiamo this.CreatePost
che riceve l'oggetto form.
Come puoi vedere nel ciclo di vita created
, abbiamo this.GetPosts
per recuperare i post quando viene creato il componente.
Un po' di stile,
<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. Definizione dei percorsi
Nel nostro file router/index.js
, importa le nostre viste e definisci i percorsi per ciascuna di esse
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. Gestione degli utenti
- Utenti non autorizzati
Se hai notato nel definire le rotte dei nostri post abbiamo aggiunto unameta
chiave per indicare che l'utente deve essere autenticato, ora abbiamo bisogno di unrouter.BeforeEach
di ogni guardia di navigazione che controlla se una rotta ha lameta: {requiresAuth: true}
. Se un percorso ha la chiavemeta
, controlla l'archivio per un token; se presente, li reindirizza al percorso dilogin
.
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
- Utenti Autorizzati
Abbiamo anche unmeta
sui percorsi/register
e/login
. Ilmeta: {guest: true}
impedisce agli utenti che hanno effettuato l'accesso di accedere ai percorsi con il metaguest
.
router.beforeEach((to, from, next) => { if (to.matched.some((record) => record.meta.guest)) { if (store.getters.isAuthenticated) { next("/posts"); return; } next(); } else { next(); } });
Alla fine, il tuo file dovrebbe essere così:
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. Gestione del token scaduto (richieste vietate)
La nostra API è impostata per far scadere i token dopo 30 minuti, ora se proviamo ad accedere alla pagina dei posts
dopo 30 minuti, riceviamo un errore 401
, il che significa che dobbiamo accedere di nuovo, quindi imposteremo un intercettore che legge se otteniamo un 401
, quindi ci reindirizza alla pagina di login
.
Aggiungi lo snippet di seguito dopo la dichiarazione dell'URL predefinito di Axios nel file 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') } } })
Questo dovrebbe portare il tuo codice allo stesso stato dell'esempio su GitHub.
Conclusione
Se sei stato in grado di seguire fino alla fine, ora dovresti essere in grado di creare un'applicazione front-end completamente funzionante e sicura. Ora hai imparato di più su Vuex e su come integrarlo con Axios e anche su come salvarne i dati dopo il ricaricamento.
Il codice è disponibile su GitHub →
Sito ospitato:
https://nifty-hopper-1e9895.netlify.app/
API:
https://gabbyblog.herokuapp.com
Documenti API:
https://gabbyblog.herokuapp.com/docs
Risorse
- "Maneggiare i biscotti con Axios", Aditya Srivastava, Medium
- "Creazione di una guardia di navigazione di autenticazione in Vue", Laurie Barth, blog di Ten Mile Square
- "Guida introduttiva a Vuex", Guida ufficiale
- "Autenticazione JWT Vue.js con Vuex e Vue Router", BezKoder