Autenticazione in Vue.js

Pubblicato: 2022-03-10
Riepilogo rapido ↬ Ogni applicazione Web che gestisce dati specifici dell'utente deve implementare l'autenticazione. Sapere come farlo è importante per gli sviluppatori Vue, ed è su questo che questo articolo mira a puntare i riflettori. Questo tutorial si rivelerà utile per gli sviluppatori principianti che desiderano conoscere l'autenticazione in Vue. Per poter seguire, dovrai avere una buona conoscenza di Vue e Vuex.

L'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:

  1. Configurazione Vuex con Axios
  2. Definizione di percorsi
  3. Gestione degli utenti
  4. 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.

(Grande anteprima)
Altro dopo il salto! Continua a leggere sotto ↓

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 una meta chiave per indicare che l'utente deve essere autenticato, ora abbiamo bisogno di un router.BeforeEach di ogni guardia di navigazione che controlla se una rotta ha la meta: {requiresAuth: true} . Se un percorso ha la chiave meta , controlla l'archivio per un token; se presente, li reindirizza al percorso di 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
  • Utenti Autorizzati
    Abbiamo anche un meta sui percorsi /register e /login . Il meta: {guest: true} impedisce agli utenti che hanno effettuato l'accesso di accedere ai percorsi con il 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(); } });

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