Аутентификации в Vue с использованием Vuex

Spread the love

Традиционно многие люди используют local storage для управления токенами, генерируемыми для аутентификации. Всегда вызывает много вопросов поиск лучшего способа управления этими токенами авторизации, таким образом что бы можно было бы хранить как можно больше информации о пользователях и при этом код был бы достаточно структурированным.

В этой статье мы расскажем как можно используя Vuex настроить авторизацию в приложение. Vuex управляет состоянием всего приложения Vue.js. Он служит централизованным хранилищем для всех компонентов приложения с заданными правилами, гарантирующими, что состояние может быть изменено только предсказуемым образом.

Содержание

Предварительные требования

  1. Знание JavaScript
  2. Установленный Node
  3. Знания Vue
  4. Установленный Vue CLI

Настройка модулей приложения

Для этого проекта мы хотим создать приложение vue с vuex и vue-router. Мы будем использовать vue cli 3.0, чтобы создать новый проект vue и выбрать опции router и vuex.

Запустите следующую команду, чтобы создать проект:

$ vue create vue-auth

Следуйте появившемуся диалоговому окну, введите необходимую информацию, выберите нужные параметры (vuex, vue-router) и завершите установку.

Далее, установите 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, чтобы наши запросы могли быть обработаны, со стороны сервера если токен потребуется. Таким образом, нам не нужно устанавливать токен каждый раз, когда мы хотим сделать запрос.

Далее, нужно настроить сервер для обработки аутентификации.

Настройка сервера для аутентификации

Как настроить Node сервер для обработки аутентификации я уже писал в другой статье Vue Authentication And Route Handling Using Vue-router в разделе Setup Node.js Server

Настройка Компонентов

Компонент Login

Создайте файл 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>

Далее добавьте атрибут data, которые будут привязаны к форме 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>

Компонент Register

Как и компонент для входа в систему, давайте сделаем такой же для регистрации пользователей. Начните с создания файла 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>

Далее определим атрибут data, который будем связан с формой:

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

Далее создадим простой компонент, который будет отображаться только в случае аутентификации нашего пользователя. Создайте файл компонента Secure.vue и добавьте в него следующее:

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

Обновим компонент App

Откройте файл ./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>

Тут мы делаем две вещи – вычисляем состояние аутентификации пользователя и запускаем действие logout, когда пользователь нажимает кнопку logout. После выхода из системы мы отправляем пользователя на страницу входа с помощью this.$router.push(‘/login’).

Теперь давайте создадим модуль авторизации, используя vuex.

Vuex модуль авторизации

Во-первых, давайте настроим наш файл 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, далее определили атрибуты state. Теперь состояние vuex будет содержать наш статус аутентификации, токен jwt и информацию о пользователе.

Создание действия Vuex login

Действия Vuex используются для фиксации мутаций в хранилище Vuex. Создадим действие login, которое аутентифицирует пользователя на сервере и передает учетные данные пользователя в хранилище vuex. Откройте файл ./src/store.js и добавьте следующее к объекту действий:

...
actions: {
  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)
      })
    })
  }
}
...

Действие login передает хелпер vuex commit, который мы будем использовать для запуска мутаций.

Мы обращается к серверному API http://localhost:3000/login и возвращаем необходимые данные. Далее сохраняем токен в localStorage, и передаем токен и информацию о пользователе в auth_success для обновления атрибутов store. Также тут мы установили заголовок ‘Authorization’ для axios.

Мы могли бы хранить токен в хранилище vuex, но если пользователь покинет наше приложение, все данные в хранилище vuex исчезнут. Чтобы позволить пользователю вернуться в приложение в течение срока действия токена и не предлагать пользователю залогиниться в систему снова, мы храним токен в localStorage.

Создания действия Vuex register

Как и действие login, действие register будет работать почти так же. В том же файле добавьте следующее в объект действия:

...
actions: {
...
 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)
    })
  })
 }
}
...

Это работает аналогично действию login, вызывая те же мутаторы и преследует одну и ту же простую цель – получить пользователя в систему.

Создание действия logout

Мы хотим, чтобы у пользователя была возможность выйти из системы, и при этом уничтожались бы все данные, созданные во время последней аутентифицированной сессии. В этот же объект действий добавьте следующее:

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

Теперь, когда пользователь щелкает, чтобы выйти, мы удаляем токен jwt, который мы сохранили вместе с установленным заголовком axios.

Создание мутаторов (mutations)

Мутаторы используются для изменения состояния store 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 = ''
  },
},

Создание геттеров (getters)

Мы используем getter, чтобы получить значение атрибутов состояния vuex. Роль нашего getter в этой ситуации состоит в том, чтобы отделить данные приложения от логики приложения и обеспечить, того чтобы мы не выдавали внутреннюю информацию.

Добавьте следующие getters к объекту:

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

Сокрытие страницы за аутентификацией

Целью этой статьи является реализация аутентификации и защита определенных страниц от пользователя, который не является аутентификацией. Чтобы достичь этого, нам нужно знать страницу, которую пользователь хочет посетить, и в равной степени иметь возможность проверить, прошел ли пользователь аутентификацию. Нам также нужен способ сказать, зарезервирована ли страница только для аутентифицированного пользователя или не прошедшего аутентификацию пользователя, одного или обоих. Это важные соображения, которых, к счастью, мы можем достичь с помощью vue-router.

Определение маршрутов для аутентифицированных и неаутентифицированных страниц

Откройте файл ./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 store. Мы также импортировали все определенные нами компоненты и установили vue для использования нашего маршрутизатора.

Давайте определим маршруты:

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

Для маршрутов, требующих аутентификации, мы добавляем к ним дополнительные данные, чтобы мы могли идентифицировать их, когда пользователь пытается получить к ним доступ. В этом суть атрибута meta, добавленного в определение маршрута. Вы так же можете добавить еще и другие данных к атрибуту meta.

Обработка случаев несанкционированного доступа

Мы определили наши маршруты. Теперь давайте проверим несанкционированный ли доступ и примем меры. В файле 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 помог нам полностью упростить это, и мы можем добавить любое условие к нашему маршруту. В нашем хранилище vuex мы можем определить действия для проверки этих условий.

Обработка просроченных токенов

Поскольку мы храним наш токен в localStorage, он может оставаться там постоянно. Это означает, что всякий раз, когда мы открываем наше приложение, оно автоматически аутентифицирует пользователя, даже если срок действия токена истек. Самое худшее, что может в этом случае произойти, это то, что наши запросы продолжали бы приводит к сбою из-за неверного токена. Это плохо с точки зрения пользователя. Нужно это исправить.

Откройте файл ./src/App.vue и добавьте в него следующее:

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 для управления состоянием аутентификации в приложении, используя всего несколько строк кода.

Я надеюсь, что это поможет вам создавать лучшие приложения.

Оригинальная статья: Chris Nwamba Handling Authentication In Vue Using Vuex


Spread the love

Аутентификации в Vue с использованием Vuex: 2 комментария

  • 06.08.2019 в 17:59
    Permalink

    в последнем коде должно быть this.$store.dispatch(“logout”), а не this.$store.dispatch(logout) (кавычек не хватает)

    Ответ
    • 06.08.2019 в 19:23
      Permalink

      Спасибо, за комментарий!

      Ответ

Добавить комментарий

Ваш e-mail не будет опубликован.