Thứ ba, 15/09/2020 | 00:00 GMT+7

Xử lý xác thực trong Vue bằng Vuex

Theo truyền thống, nhiều người sử dụng bộ nhớ local để quản lý các mã thông báo được tạo thông qua xác thực phía client . Mối quan tâm lớn luôn là cách tốt hơn để quản lý mã thông báo ủy quyền để cho phép ta lưu trữ nhiều thông tin hơn nữa về user .

Đây là nơi Vuex xuất hiện. Vuex quản lý các trạng thái cho các ứng dụng Vue.js. Nó phục vụ như một repository tập trung cho tất cả các thành phần trong một ứng dụng, với các luật đảm bảo trạng thái chỉ có thể được thay đổi theo cách có thể đoán trước được.

Có vẻ như một giải pháp thay thế tốt hơn để luôn kiểm tra localStorage? Hãy cùng khám phá nó.

Yêu cầu

  1. Nút được cài đặt trên hệ thống local của bạn
  2. Kiến thức về JavaScript và Vue
  3. Cài đặt Vue CLI trên hệ thống local của bạn.
  4. Đọc qua Xác thực Vue và Xử lý tuyến đường bằng Vue-router

Nếu bạn muốn chuyển thẳng đến mã demo: Hãy truy cập vue-auth-vuex trên GitHub

Cài đặt các module ứng dụng

Đối với dự án này, ta muốn tạo một ứng dụng vue có vuex và vue-router . Ta sẽ sử dụng vue cli 3.0 để tạo một dự án vue mới và chọn bộ định tuyến và vuex từ các tùy chọn.

Chạy lệnh sau để cài đặt :

$ vue create vue-auth 

Làm theo lời thoại hiển thị, thêm thông tin cần thiết và chọn các tùy chọn ta cần và hoàn tất cài đặt.

Tiếp theo, cài đặt axios :

$ npm install axios --save 

Cài đặt Axios

Ta cần các tiên đề trên nhiều thành phần của ta . Hãy cài đặt nó ở cấp nhập cảnh để ta không phải nhập nó mỗi khi cần.

Mở file ./src/main.js và thêm file sau:

[...] import store from './store' import Axios from 'axios'  Vue.prototype.$http = Axios; const token = localStorage.getItem('token') if (token) {   Vue.prototype.$http.defaults.headers.common['Authorization'] = token } [...] 

Bây giờ, khi ta muốn sử dụng axios bên trong thành phần của bạn , ta có thể thực hiện this.$http và nó sẽ giống như gọi trực tiếp axios. Ta cũng đặt tiêu đề Authorization trên axios cho mã thông báo của ta , vì vậy các yêu cầu của ta có thể được xử lý nếu cần mã thông báo. Bằng cách này, ta không phải đặt mã thông báo bất cứ lúc nào ta muốn đưa ra yêu cầu.

Khi điều đó hoàn tất, hãy cài đặt server để xử lý xác thực.

Cài đặt server để xác thực

Tôi đã viết về điều này khi giải thích cách xử lý xác thực với vue-router. Kiểm tra phần Setup Node.js Server của phần này

Cài đặt thành phần

Thành phần đăng nhập

Tạo một file Login.vue trong folder ./src/components . Sau đó, thêm mẫu cho trang đăng nhập:

<template>  <div>    <form class="login" @submit.prevent="login">      <h1>Sign in</h1>      <label>Email</label>      <input required v-model="email" type="email" placeholder="Name"/>      <label>Password</label>      <input required v-model="password" type="password" placeholder="Password"/>      <hr/>      <button type="submit">Login</button>    </form>  </div> </template> 

Khi bạn hoàn tất, hãy thêm các thuộc tính dữ liệu sẽ liên kết với biểu mẫu HTML:

[...] <script>   export default {     data(){       return {         email : "",         password : ""       }     },   } </script> 

Bây giờ, hãy thêm phương thức để xử lý đăng nhập:

[...] <script>   export default {     [...]     methods: {       login: function () {         let email = this.email          let password = this.password         this.$store.dispatch('login', { email, password })        .then(() => this.$router.push('/'))        .catch(err => console.log(err))       }     }   } </script> 

Ta đang sử dụng hành động vuex - login để xử lý xác thực này. Ta có thể giải quyết các hành động thành các lời hứa để ta có thể thực hiện những điều thú vị với chúng bên trong thành phần của bạn .

Thành phần Đăng ký

Giống như thành phần để đăng nhập, hãy tạo một thành phần để đăng ký user . Bắt đầu bằng cách tạo file Register.vue trong folder thành phần và thêm thông tin sau vào đó:

<template>   <div>     <h4>Register</h4>     <form @submit.prevent="register">       <label for="name">Name</label>       <div>           <input id="name" type="text" v-model="name" required autofocus>       </div>        <label for="email" >E-Mail Address</label>       <div>           <input id="email" type="email" v-model="email" required>       </div>        <label for="password">Password</label>       <div>           <input id="password" type="password" v-model="password" required>       </div>        <label for="password-confirm">Confirm Password</label>       <div>           <input id="password-confirm" type="password" v-model="password_confirmation" required>       </div>        <div>           <button type="submit">Register</button>       </div>     </form>   </div> </template> 

Hãy xác định các thuộc tính dữ liệu mà ta sẽ liên kết với biểu mẫu:

[...] <script>   export default {     data(){       return {         name : "",         email : "",         password : "",         password_confirmation : "",         is_admin : null       }     },   } </script> 

Bây giờ, hãy thêm phương thức để xử lý đăng nhập:

[...] <script>   export default {     [...]     methods: {       register: function () {         let data = {           name: this.name,           email: this.email,           password: this.password,           is_admin: this.is_admin         }         this.$store.dispatch('register', data)        .then(() => this.$router.push('/'))        .catch(err => console.log(err))       }     }   } </script> 

Thành phần bảo mật

Hãy tạo một thành phần đơn giản chỉ hiển thị nếu user của ta được xác thực. Tạo file thành phần Secure.vue và thêm phần sau vào file đó:

<template>   <div>     <h1>This page is protected by auth</h1>   </div> </template> 

Cập nhật thành phần ứng dụng

Mở file ./src/App.vue và thêm phần sau vào file đó:

<template>   <div id="app">     <div id="nav">       <router-link to="/">Home</router-link> |       <router-link to="/about">About</router-link><span v-if="isLoggedIn"> | <a @click="logout">Logout</a></span>     </div>     <router-view/>   </div> </template> 

Bạn có thể thấy liên kết Logout mà ta đặt để chỉ hiển thị nếu user đã đăng nhập không? Tuyệt quá.

Bây giờ, hãy thêm logic đằng sau đăng xuất:

<script>   export default {     computed : {       isLoggedIn : function(){ return this.$store.getters.isLoggedIn}     },     methods: {       logout: function () {         this.$store.dispatch('logout')         .then(() => {           this.$router.push('/login')         })       }     },   } </script> 

Ta đang thực hiện hai việc - tính toán trạng thái xác thực của user và gửi hành động đăng xuất đến cửa hàng vuex của ta khi user nhấp vào nút đăng xuất. Sau khi đăng xuất, ta đưa user đến trang login bằng cách sử dụng trang this.$router.push('/login') . Bạn có thể thay đổi nơi user được gửi đến nếu bạn muốn.

Đó là nó. Hãy tạo module auth bằng vuex.

Mô-đun xác thực Vuex

Nếu bạn đã đọc qua phần Cài đặt server Node.js , bạn sẽ thấy ta phải lưu trữ mã thông báo xác thực của user trong localStorage và ta phải truy xuất cả mã thông báo và thông tin user bất cứ lúc nào ta muốn kiểm tra xem user có được xác thực hay không. Điều này hoạt động, nhưng nó không thực sự thanh lịch. Ta sẽ xây dựng lại xác thực để sử dụng vuex.

Đầu tiên, hãy cài đặt file store.js của ta cho vuex:

import Vue from 'vue' import Vuex from 'vuex' import axios from 'axios'  Vue.use(Vuex)  export default new Vuex.Store({   state: {     status: '',     token: localStorage.getItem('token') || '',     user : {}   },   mutations: {    },   actions: {    },   getters : {    } }) 

Nếu bạn nhận thấy, ta đã nhập vue, vuex và axios, sau đó yêu cầu vue sử dụng vuex. Điều này là bởi vì ta muốn kinh doanh nghiêm túc ở đây.

Ta đã xác định các thuộc tính của trạng thái. Bây giờ trạng thái vuex sẽ giữ trạng thái xác thực, mã thông báo jwt và thông tin user của ta .

Tạo Hành động login Vuex

Các hành động Vuex được sử dụng để thực hiện các đột biến đối với cửa hàng vuex. Ta sẽ tạo một hành động login sẽ xác thực user với server và commit thông tin đăng nhập của user vào cửa hàng vuex. Mở file ./src/store.js và thêm phần sau vào đối tượng hành động:

login({commit}, user){     return new Promise((resolve, reject) => {       commit('auth_request')       axios({url: 'http://localhost:3000/login', data: user, method: 'POST' })       .then(resp => {         const token = resp.data.token         const user = resp.data.user         localStorage.setItem('token', token)         axios.defaults.headers.common['Authorization'] = token         commit('auth_success', token, user)         resolve(resp)       })       .catch(err => {         commit('auth_error')         localStorage.removeItem('token')         reject(err)       })     }) }, 

Hành động đăng nhập chuyển commit trợ giúp commit vuex mà ta sẽ sử dụng để kích hoạt các đột biến. Các đột biến tạo ra các thay đổi đối với cửa hàng vuex.

Ta đang thực hiện cuộc gọi đến đường đăng nhập của server và trả về dữ liệu cần thiết. Ta lưu trữ mã thông báo trên localStorage, sau đó chuyển mã thông báo và thông tin user cho auth_success để cập nhật các thuộc tính của cửa hàng. Ta cũng đặt tiêu đề cho axios vào thời điểm này.

Ta có thể lưu trữ mã thông báo trong cửa hàng vuex, nhưng nếu user rời khỏi ứng dụng của ta , tất cả dữ liệu trong cửa hàng vuex sẽ không xuất hiện . Để đảm bảo ta cho phép user quay lại ứng dụng trong thời gian hiệu lực của mã thông báo và không phải đăng nhập lại, ta phải giữ mã thông báo trong localStorage.

Điều quan trọng là bạn phải biết những thứ này hoạt động như thế nào để có thể quyết định chính xác những gì bạn muốn đạt được.

Ta trả lại một lời hứa để ta có thể trả lại phản hồi cho user sau khi đăng nhập xong.

Tạo Hành động register Vuex

Giống như hành động login , hành động register sẽ hoạt động gần như theo cùng một cách. Trong cùng một file , hãy thêm phần sau vào đối tượng hành động:

register({commit}, user){   return new Promise((resolve, reject) => {     commit('auth_request')     axios({url: 'http://localhost:3000/register', data: user, method: 'POST' })     .then(resp => {       const token = resp.data.token       const user = resp.data.user       localStorage.setItem('token', token)       axios.defaults.headers.common['Authorization'] = token       commit('auth_success', token, user)       resolve(resp)     })     .catch(err => {       commit('auth_error', err)       localStorage.removeItem('token')       reject(err)     })   }) }, 

Thao tác này hoạt động tương tự như hành động login , gọi các trình đột biến giống như hành động loginregister của ta có cùng mục tiêu đơn giản - đưa user vào hệ thống.

Tạo Hành động logout Vuex

Ta muốn user có khả năng đăng xuất khỏi hệ thống và ta muốn hủy tất cả dữ liệu được tạo trong phiên xác thực cuối cùng. Trong cùng một đối tượng actions , hãy thêm phần sau:

logout({commit}){   return new Promise((resolve, reject) => {     commit('logout')     localStorage.removeItem('token')     delete axios.defaults.headers.common['Authorization']     resolve()   }) } 

Bây giờ, khi user nhấp chuột để đăng xuất, ta sẽ loại bỏ các jwt thẻ ta được lưu trữ cùng với axios tiêu đề ta đặt. Không có cách nào họ có thể thực hiện một giao dịch yêu cầu mã thông báo ngay bây giờ.

Tạo đột biến

Giống như tôi đã đề cập trước đó, các trình đột biến được sử dụng để thay đổi trạng thái của một cửa hàng vuex. Hãy xác định các trình đột biến mà ta đã sử dụng trong suốt ứng dụng của bạn . Trong đối tượng mutators, hãy thêm những thứ sau:

mutations: {   auth_request(state){     state.status = 'loading'   },   auth_success(state, token, user){     state.status = 'success'     state.token = token     state.user = user   },   auth_error(state){     state.status = 'error'   },   logout(state){     state.status = ''     state.token = ''   }, }, 

Tạo người nhận

Ta sử dụng getter để lấy giá trị của các thuộc tính của trạng thái vuex. Role của getter của ta trong tình huống này là tách dữ liệu ứng dụng khỏi logic ứng dụng và đảm bảo ta không cung cấp thông tin nhạy cảm.

Thêm phần sau vào đối tượng getters :

getters : {   isLoggedIn: state => !!state.token,   authStatus: state => state.status, } 

Bạn sẽ đồng ý với tôi rằng đây là một cách gọn gàng hơn để truy cập dữ liệu trong cửa hàng.

Ẩn các trang sau xác thực

Toàn bộ mục đích của bài viết này là thực hiện xác thực và giữ các trang nhất định tránh xa user không xác thực. Để làm điều này, ta cần biết trang mà user muốn truy cập và cũng có cách để kiểm tra xem user có được xác thực hay không. Ta cũng cần một cách để nói liệu trang được dành riêng cho user đã được xác thực hay chỉ user chưa được xác thực hoặc cả hai. Những điều này là những cân nhắc quan trọng mà may mắn thay, ta có thể đạt được với vue-router.

Xác định các tuyến đường cho các trang được xác thực và chưa được xác thực

Mở file ./src/router.js và nhập những gì ta cần cho cài đặt này:

import Vue from 'vue' import Router from 'vue-router' import store from './store.js' import Home from './views/Home.vue' import About from './views/About.vue' import Login from './components/Login.vue' import Secure from './components/Secure.vue' import Register from './components/Register.vue'  Vue.use(Router) 

Như bạn thấy , ta đã nhập vue, vue-router và cài đặt cửa hàng vuex của ta . Ta cũng đã nhập tất cả các thành phần mà ta đã xác định và đặt vue để sử dụng bộ định tuyến của ta .

Hãy xác định các tuyến:

[...] let router = new Router({   mode: 'history',   routes: [     {       path: '/',       name: 'home',       component: Home     },     {       path: '/login',       name: 'login',       component: Login     },     {       path: '/register',       name: 'register',       component: Register     },     {       path: '/secure',       name: 'secure',       component: Secure,       meta: {          requiresAuth: true       }     },     {       path: '/about',       name: 'about',       component: About     }   ] })  export default router 

Định nghĩa tuyến đường của ta rất đơn giản. Đối với các tuyến đường yêu cầu xác thực, ta thêm dữ liệu bổ sung vào đó để cho phép ta xác định nó khi user cố gắng truy cập. Đây là bản chất của thuộc tính meta được thêm vào định nghĩa tuyến đường. Nếu bạn đang hỏi "Tôi có thể thêm nhiều dữ liệu hơn vào meta này và sử dụng nó không?" thì tôi xin được nói với bạn rằng bạn hoàn toàn đúng ?.

Xử lý các trường hợp truy cập trái phép

Ta đã xác định các tuyến đường của ta . Bây giờ, hãy kiểm tra truy cập trái phép và thực hiện hành động.
Trong file router.js , hãy thêm phần sau vào trước khi export default router :

router.beforeEach((to, from, next) => {   if(to.matched.some(record => record.meta.requiresAuth)) {     if (store.getters.isLoggedIn) {       next()       return     }     next('/login')    } else {     next()    } }) 

Từ bài viết về cách sử dụng bộ định tuyến vue để xác thực, bạn có thể nhớ lại rằng ta đã có một cơ chế thực sự phức tạp ở đây, nó phát triển rất lớn và rất khó hiểu. Vuex đã giúp ta đơn giản hóa hoàn toàn điều đó và ta có thể tiếp tục thêm bất kỳ điều kiện nào vào lộ trình của bạn . Trong kho vuex của ta , sau đó ta có thể xác định các hành động để kiểm tra các điều kiện này và getters để trả lại chúng.

Xử lý các trường hợp mã thông báo hết hạn

Bởi vì ta lưu trữ mã thông báo của bạn trong localStorage, nó có thể ở đó vĩnh viễn. Điều này nghĩa là khi nào ta mở ứng dụng của bạn , nó sẽ tự động xác thực user ngay cả khi mã thông báo đã hết hạn. Điều sẽ xảy ra nhiều nhất là các yêu cầu của ta sẽ không thành công do mã thông báo không hợp lệ. Điều này có hại cho trải nghiệm user .

Bây giờ, mở file ./src/App.vue và trong tập lệnh, thêm phần sau vào đó:

export default {   [...]   created: function () {     this.$http.interceptors.response.use(undefined, function (err) {       return new Promise(function (resolve, reject) {         if (err.status === 401 && err.config && !err.config.__isRetryRequest) {           this.$store.dispatch(logout)         }         throw err;       });     });   } } 

Ta đang chặn cuộc gọi axios để xác định xem ta có nhận được phản hồi 401 Unauthorized . Nếu ta làm vậy, ta sẽ thực hiện hành động logout và user sẽ đăng xuất khỏi ứng dụng. Thao tác này sẽ đưa họ đến trang login giống như ta đã thiết kế trước đó và họ có thể đăng nhập lại.

Ta có thể đồng ý rằng điều này sẽ cải thiện đáng kể trải nghiệm của user .

Kết luận

Sử dụng vuex cho phép ta lưu trữ và quản lý trạng thái xác thực và tiến hành kiểm tra trạng thái trong ứng dụng của ta chỉ bằng một vài dòng mã.


Tags:

Các tin liên quan