مدیریت احراز هویت در Vue با استفاده از Vuex

گردآوری و تالیف : عرفان کاکایی
تاریخ انتشار : 03 شهریور 1397
دسته بندی ها : جاوا اسکریپت

به طور سنتی، افراد از حافظه‌های محلی (Local Storage) برای مدیریت نشانه‌های تولید شده از طریق احراز هویت سمت کاربر استفاده می‌کنند. روش دیگری برای مدیریت نشانه‌های احراز هویت وجود دارد که ما را قادر می‌سازد تا حتی اطلاعات بیشتری درباره کاربر ذخیره کنیم.

Vuex در اینجا به کار می‌آید. Vuex، state را برای برنامه‌های Vue.js مدیریت کرده، و به عنوان مرکزی برای خیره تمام کامپوننت‌های موجود در یک برنامه عمل می‌کند. پس در نتیجه راه بهتری نسبت به بررسی localStorage به نظر می‌رسد. حال بیایید آن را بررسی کنیم.

جدول محتویات:

  1. پیش‌نیازها
  2. راه‌اندازی ماژول‌های برنامه
  3. راه‌اندازی سرور برای احراز هویت
  4. راه‌اندازی کامپوننت‌ها
  5. ماژول احراز هویت Vuex
  6. صفحات را پشت احراز هویت مخفی کنید
  7. نتیجه گیری

پیش‌نیازها:

  1. دانش در زمینه JavaScript
  2. نصب داشتن Node بر روی سیستم
  3. دانش در زمینه Vue
  4. نصب داشتن Vue CLI
  5. مطالعه مقاله «احراز هویت Vue و مدیریت Route با استفاده از Vue-router»

راه‌اندازی ماژول‌های برنامه

برای این پروژه، ما می‌خواهیم یک برنامه Vue بسازیم که Vuex و Vue-router را به همراه دارد. ما از Vue CLI 3.0 برای ساخت یک پروژه Vue جدید و انتخاب router و Vuex از میان گزینه‌ها استفاده خواهیم کرد.

دستور زیر را اجرا کرده، و آن را راه‌اندازی کنید:

vue create vue-auth

دیالوگی که نمایش داده می‌شود را دنبال کرده، اطلاعات مورد نیاز را وارد کنید و گزینه‌هایی که برای اتمام نصب نیاز داریم را انتخاب کنید.

سپس، axios را نصب کنید:

npm install axios --save

راه‌اندازی Axios

ما به axios در بسیاری از کامپوننت‌های خود نیاز خواهیم داشت. بیایید آن را در سطح ورودی راه‌اندازی کنیم، تا مجبور نشویم آن را هر زمان که نیاز داریم وارد کنیم.

فایل ./src/main.jsرا باز کرده، و این کد را به آن اضافه کنید:

[...]
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
}
[...]

حالا، می‌خواهیم از axios‌ در کامپوننت خود استفاده کنیم. می‌توانیم از this.$http استفاده کنیم، که به مانند فراخوانی مستقیم آن خواهد بود. همچنین Authorization را بر روی هِدِر axios قرار می‌دهیم، تا اگر یک نشانه مورد نیاز است، درخواست ما بتواند پردازش شود. به این صورت، مجبور نخواهیم بود تا هر زمان که می‌خواهیم یک درخواست ارسال کنیم، نشانه را تنظیم کنید.

پس از آن، بیایید سرور مربوط به مدیریت احراز هویت را راه‌اندازی کنیم.

راه‌اندازی سرور برای احراز هویت

این بخش مربوط به مدیریت احراز هویت با استفاده از Vue-router را قبلا در مقاله «احراز هویت Vue و مدیریت Route با استفاده از Vue-router» توضیح داده‌ایم.

راه‌اندازی کامپوننت‌ها

کامپوننت ورود

فایل جدیدی به نام Login.vue در شاخه ./src/components بسازید. سپس، الگوهای مربوط به صفحه ورود را اضافه کنید:

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

پس از انجام این کار، صفات داده که فرم HTML را اتصال می‌دهند، اضافه کنید:

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

حال، بیایید متد مربوط به مدیریت ورود را اضافه کنیم:

[...]
<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>

ما از اکشن Vuex، یعنی login برای مدیریت این احراز هویت استفاده می‌کنیم.

کامپوننت ثبت نام

بیایید یک کامپوننت دیگر به مانند کامپوننت ورود، برای ثبت نام بسازیم. با ساخت یک فایل به نام Register.vue در شاخه کامپوننت‌ها شروع کرده، و این کد را به آن اضافه کنید:

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

حال صفات داده‌ای که به فرم اتصال خواهیم داد را تعریف می‌کنیم:

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

و سپس، ‌متد مربوط به مدیریت ورود را تعریف می‌کنیم:

[...]
<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>

کامپوننت امنیت

بیایید کامپوننت ساده‌ای بسازیم که وضعیت احراز هویت شده بودن کاربر را نشان می‌دهد. فایل کامپوننت را با نام Secure.vue بسازید و این کد را به آن اضافه کنید:

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

بروزرسانی کامپوننت برنامه

فایل ./src/App.vue را باز کرده، و این کد را به آن اضافه کنید:

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

آیا دیدید که چگونه لینک Logout را فقط در صورتی که کاربر وارد شده است نمایش دادیم؟

حال، منطق پشت خروج را اضافه می‌کنیم:

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

ما در اینجا دو کار را انجام می‌دهیم: ‌محاسبه وضعیت احراز هویت کاربر و اعزام action مربوط به خروج به مخزن Vuex خود، وقتی که کاربر بر روی دکمه خروج کلیک می‌کند. پس از خروج، کاربر را با استفاده از this.$router.push(‘/login’) به صفحه ورود می‌فرستیم. اگر خواستید، می‌توانید محل ارسال کاربر را تغییر دهید.

این بخش در اینجا به پایان می‌رسد. حال بیایید با استفاده از Vuex، ماژول احراز هویت را بسازیم.

ماژول احراز هویت Vuex

اگر بخش «راه‌اندازی سرور Node.js» در مقاله «احراز هویت Vue و مدیریت Route با استفاده از Vue-router» را خوانده باشید، متوجه می‌شوید که ما مجبور بودیم نشانه احراز هویت کاربر را در یک localStorage ذخیره کنیم، و همچنین هر زمان که می‌خواستیم وضعیت احراز هویت شدن کاربر را بررسی کنیم، مجبور بودیم که هم نشانه و هم اطلاعات کاربر را دریافت کنیم.

در ابتدا، بیایید فایل store.js را برای 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 : {

  }
})

اگر دقت کرده باشید، ما Vue، Vuex و axios را وارد کرده، و سپس از Vue درخواست کردیم که از Vuex استفاده کند.

ما صفات state را تعریف کرده‌ایم. state (وضعیت) Vuex در اینجا وضعیت احراز هویت، نشانه jwt و اطلاعات کاربر را در خود نگه خواهد داشت.

ساخت action مربوط به ورود (login)

اکشن‌های Vuex برای اعمال جهش به مخزن Vuex استفاده می‌شوند. ما یک اکشن login ساختیم که یک کاربر را با سرور احراز هویت می‌کند و مدارک او را به مخزن Vuex می‌سپرد. فایل ./src/store.js را باز کرده، و این آبجکت action را به آن اضافه کنید:

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)
      })
    })
},

این action ورود، متد کمکی commit را منتقل می‌کند، که ما از آن برای اعمال جهش‌ها استفاده خواهیم کرد. جهش‌ها، تغییراتی به مخزن Vuex اعمال می‌کنند.

ما یک فراخوانی به route ورود سرور انجام می‌دهیم و داده‌های ضروری را بر می‌گردانیم. نشانه را بر روی localStorage ذخیره کرده، و سپس نشانه و اطلاعات کاربر را به auth_success منتقل می‌کنیم تا صفات مخزن خود را بروزرسانی کنیم. همچنین در این نقطه، یک header برای axios تعیین می‌کنیم.

نکته:‌ ما می‌توانیم نشانه را در مخزن Vuex ذخیره کنیم، اما اگر کاربر برنامه را ترک کند، تمام داده‌های موجود در مخزن Vuex از بین می‌روند. برای اطمینان از این که کاربر در زمان تعیین شده می‌تواند به برنامه باز گردد و مجبور نباشد که دوباره وارد شود، باید نشانه را در localStorage نگه داریم.

دانستن نحوه کار این موارد مهم است، تا بتوانید تصمیم بگیرید که دقیقا چه چیزی را می‌خواهید به دست بیاورید.

ما یک promise را بر می‌گردانیم تا پس از اتمام ورود یک کاربر، بتوانیم پاسخی به او بدهیم.

ساخت action مربوط به ثبت نام (register)

درست به مانند اکشن login، اکشن register نیز تقریبا به همان صورت کار خواهد کرد. در همان فایل، این آبجکت اکشن را اضافه کنید:

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)
    })
  })
},

این اکشن نیز مشابه به اکشن ورود کار می‌کند و همان جهش‌دهنده‌های مشابه را فراخوانی می‌کند؛ زیرا هر دو اکشن یک هدف مشابه دارند: وارد کردن یک کاربر به سیستم.

ساخت action مربوط به خروج (logout)

ما می‌خواهیم که کابر بتواند از سیستم خارج شود، و همچنین می‌خواهیم تمام داده‌های ساخته شده در طی آخرین session احراز هویت را نابود کنیم. در همان آبجکت actions، این کد را اضافه کنید:

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

حال، وقتی که کاربر بر روی دکمه خروج کلیک می‌کند، ما نشانه jwt را که به همراه header مربوط به axios ذخیره کردیم را حذف می‌کنیم. زیرا دیگر کاری نیست که بخواهیم انجام دهیم و نیازمند یک نشانه باشد.

جهش‌ها را بسازید

همانطور که پیش‌تر اشاره کردم، جهش دهنده‌ها برای تغییر وضعیت یک مخزن Vuex استفاده می‌شوند. حال بیایید جهش‌دهنده‌های استفاده شده در برنامه را تعریف کنیم. در آبجکت جهش‌دهنده‌ها، این کد را اضافه کنید:

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 = ''
  },
},

Getterها را بسازید

ما از getter برای دریافت مقادیر صفات مخزن Vuex استفاده می‌کنیم. نقش getter در این وضعیت، این است که داده‌های برنامه را از منطق برنامه جدا کند و تضمین کند که ما داده‌های حساس را از دست نمی‌دهیم.

این کد را به آبجکت getters اضافه کنید:

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

این روش ساده‌تری برای دسترسی به داده‌های موجود در مخزن است.

صفحات را پشت احراز هویت مخفی کنید

هدف کلی این مقاله، این است که احراز هویت را پیاده سازی کرده، و صفحاتی خاص را دور از دسترس کاربری که احراز هویت نشده است نگه داریم. برای رسیدن به این هدف، باید بدانیم که کاربر چه صفحه‌ای را می‌خواهد ببیند، و این که باید راهی برای بررسی وضعیت احراز هویت شدن کاربر داشته باشیم. همچنین باید راهی داشته باشیم تا بررسی کنیم و ببینیم که صفحه مورد نظر فقط برای کاربران احراز هویت شده، احراز هویت نشده و یا هر دو است. این‌ها موارد مهمی هستند که خوشبختانه می‌توانیم با استفاده از Vue-router به دست بیاوریم.

تعریف routeها برای صفحات مجاز و غیر مجاز

فایل ./src/router.js را باز کرده و مواردی که برای راه‌اندازی این موارد نیاز داریم را وارد کنید:

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)

همانطور که می‌توانید ببینید، ما Vue، Vue-router و راه‌اندازی Vuex‌ خود را وارد کرده‌ایم. همچنین تمام کامپوننت‌هایی که تعریف کردیم را وارد کرده، و به Vue گفتیم که از Router ما استفاده کند.

حال بیایید routeها را تعریف کنیم:

[...]
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

تعریف route ما ساده است. برای routeهایی که نیازمند احراز هویت هستند، داده‌های اضافی‌ای وارد می‌کنیم تا بتوانیم وقتی که کاربر می‌خواهد به آن دسترسی داشته باشد، آن را تشخیص دهیم. این ماهیت صفت meta است که به تعریف route‌ اضافه کردیم.

مدیریت موارد دسترسی غیر مجاز

حال ما routeهای خود را تعریف کرده‌ایم. بیایید دسترسی‌های غیز مجاز را بررسی کره، و کاری در قبال آن‌ها انجام دهیم. در فایل router.js، این کد را قبل از 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() 
  }
})

اگر مقاله مربوط به استفاده از Vue-router برای احراز هویت را به یاد داشته باشید، یک مکانیزم بسیار پیچیده در اینجا داشتیم که بسیار بزرگ و گیج‌کننده شد. Vuex به ما کمک می‌کند که آن را کاملا ساده‌ کنیم، و همچنین می‌توانیم هر شرطی را به route خود اضافه کنیم. در مخزن Vuex خود، می‌توانیم actionهایی تعریف کنیم که این شرایط را بررسی کنند، و همچنین getterهایی تعریف کنیم که آن‌ها را بر گردانند.

مدیریت موارد نشانه منقضی شده

از آنجایی که ما نشانه خود را در localStorage ذخیره می‌کنیم، می‌تواند دائما در آنجا باقی بماند. این به این معنی است که هر زمان که ما برنامه خود را باز می‌کنیم، برنامه به طور خودکار کاربر را احراز هویت می‌کند؛ حتی اگر نشانه او منقضی شده باشد. در این صورت، بدترین اتفاقی که می‌تواند بیفتد این است که به علت نشانه منقضی شده، درخواست ما با شکست مواجه می‌شود. این اتفاق، برای تجربه کاربری بد است.

حال، فایل ./src/App.js را باز کرده و در داخل اسکریپت، این کد را اضافه کنید:

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

در اینجا ما فراخوانی axios را رهگیری می‌کنیم تا ببینیم که پاسخ 401 Unauthorized را دریافت می‌کنیم یا نه. اگر آن را دریافت کنیم، اکشن خروج را اعزام می‌کنیم و کاربر از برنامه خارج می‌شود. همانطور که پیش‌تر تعیین کردیم، این کار کاربر را به صفحه ورود می‌برد و او می‌تواند دوباره وارد شود.

نتیجه گیری

می‌توانید تغییرات قابل توجهی که نسبت به مقاله قبلی به برنامه ما اعمال می‌شوند را ببینید. حال، دیگر به بررسی نشانه و دستکاری شروط وابسته نیستیم. می‌توانیم به سادگی از مخزن Vuex برای مدیریت state احراز هویت استفاده کرده، و state را با چند خط کد بررسی کنیم.

منبع

مقالات پیشنهادی

احراز هویت Vue و مدیریت Route با استفاده از Vue-router

Vue یک فریم‌وورک JavaScript پیش‌رونده است که ساخت برنامه‌های Frontend را آسان‌تر می‌کند. اگر آن را با Vue-Router ترکیب کنید، می‌توانید برنامه‌هایی با...

ایجاد یک اپلیکیشن Vue.js  پیچیده و بزرگ با استفاده از Vuex

یادگیری و استفاده از ویوجی‌اس بسیار ساده است، در حدی که هر فردی می‌تواند با آن اپلیکیشن ساده خود را ایجاد کند. حتی افرادی که تازه‌کار هستند با مراجعه...

مدیریت ارتباط با مشتری یا CRM چیست ؟

در فضای  کسب وکار هر شرکت و کسب و کاری که وارد بشوید مشاهده می کنید که در آنجا نرم افزار هایی متناسب با فعالیت آن کسب و کار در حال استفاده است. معمولا...

مدیریت مجوزهای کاربر در Vue، با استفاده از CASL

یک مسئله وجود دارد که همگی می‌توانیم تایید کنیم: مهم نیست که چه زبان یا پلتفرمی را برای ساخت برنامه‌ها ترجیح بدهیم؛ باید نوعی کنترل و سطح دسترسی در بر...