Обработка аутентификации в Vue с использованием Vuex
Код
Мы всегда стремимся получить лучший способ управления токенами авторизации, чтобы хранить еще больше информации о пользователях.
Вот тут и вступает в игру Vuex. Он управляет состояниями приложений, созданными с помощью фреймворка Vue.js. Инструмент служит централизованным хранилищем для всех компонентов приложения с правилами.
Похоже, лучше всегда проверять localStorage? Давайте выясним это.
Необходимый базовый уровень
- Знание JavaScript.
- js, установленный локально.
- Знание Vue.
- Установленный Vue CLI
Настройка модулей приложения
В рамках этого проекта мы создадим vue-приложение с помощью vuex и vue-router. Мы будем использовать vue cli 3.0.
Выполните следующую команду:
$ 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
для токена. Таким образом, нам не нужно устанавливать токен каждый раз, когда мы хотим выполнить запрос.
Настройка компонентов
Компонент аутентификации
Создайте файл 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>
Теперь добавим логику для выхода из системы.
<script>
export default {
computed : {
isLoggedIn : function(){ return this.$store.getters.isLoggedIn}
},
methods: {
logout: function () {
this.$store.dispatch('logout')
.then(() => {
this.$router.push('/login')
})
}
},
}
</script>
Когда пользователь кликает по ссылке для выхода из системы, мы проверяем состояние аутентификации пользователя и отправляем действие выхода из системы в хранилище vuex.
После выхода мы отправляем пользователя на страницу login
с помощью this.$router.push('/login')
. Вы можете изменить адрес, на который будет перенаправляться пользователь.
Модуль Vuex Auth
Мы реализуем аутентификацию с использованием 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, а затем указали vue использовать vuex. Это потому, что здесь будут выполняться сложные действия.
Мы определили атрибуты состояния. Теперь состояние vuex будет содержать статус аутентификации, токен jwt
и информацию о пользователе.
Создание Vuex-действия login
Мы создадим действие login
, которое аутентифицирует пользователя на сервере и передает его учетные данные в хранилище vuex.
Откройте файл ./src/store.js
и добавьте к объекту действия приведенный ниже код:
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
, который мы будем использовать для запуска мутаций. Мутации вносят изменения в Vuex Store.
Мы совершаем вызов маршрута входа на сервер и возвращаем необходимые данные. Мы храним токен в localStorage, затем передаем токен и информацию о пользователе auth_success
для обновления атрибутов хранилища. В этот момент также устанавливаем заголовок для axios
.
Токен можно было разместить в хранилище vuex. Но если пользователь покидает приложение, все данные в хранилище vuex стираются.
Чтобы пользователь мог вернуться в приложение в течение срока действия токена, нужно хранить токен в localStorage. Мы возвращаем promise, чтобы вернуть ответ пользователю после завершения авторизации.
Создание Vuex- действия register
Действие register
будет работать почти так же, как действие login
. В том же файле добавьте приведенный ниже код в объект действия:
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
. Действие register
также предназначено для обработки авторизации.
Создание Vuex-действия logout
Пользователь должен иметь возможность выйти из системы. Для этого нужно удалить все данные, созданные во время последней аутентифицированной сессии. В этот же объект actions
добавьте приведенный ниже код:
logout({commit}){
return new Promise((resolve, reject) => {
commit('logout')
localStorage.removeItem('token')
delete axios.defaults.headers.common['Authorization']
resolve()
})
}
Теперь, когда пользователь кликает по ссылке выхода из системы, удаляется токен jwt
, который хранился вместе с установленным заголовком 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 = ''
},
},
Создание геттеров
Мы используем геттеры, чтобы получить значение атрибутов состояния vuex. Геттеры должны отделить данные приложения от его логики.
Добавьте приведенный ниже код к объекту 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, добавленного в определение маршрута.
Обработка случаев несанкционированного доступа
Реализуем проверку несанкционированного доступа и соответствующие меры. В файле 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()
}
})
В хранилище 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
. Если это так, то отправляем действие logout
, и пользователь выйдет из приложения. Он будет перенаправлен на страницу login
, на которой сможет авторизоваться повторно.
Заключение
Мы внесли значительные изменения в приложение, созданное на основе vuex. Теперь мы не полагаемся на постоянную проверку токена. Можно использовать хранилище vuex для управления состоянием аутентификации и перейти к проверке состояния в приложении, используя всего несколько строк кода.
Я надеюсь, что это руководство поможет вам создавать лучшие приложения.