Обработка аутентификации в вашем приложении Vue на основе GraphQL
Основной целью аутентификации является идентификация лица, запрашивающего ресурс. Это очень сложный вопрос при разработке приложений, поскольку не существует универсального правила для обработки аутентификации в наших приложениях.
Обработка аутентификации на клиенте в веб-приложении еще более сложна, поскольку существуют проблемы безопасности, такие как межсайтовый скриптинг (XSS), когда злоумышленник получает доступ к информации, хранящейся в браузере, и использует ее, чтобы маскироваться под пользователя. Большинство SPA реализуют аутентификацию на основе токенов, потому что токены не имеют состояния и легко масштабируются, поскольку это снимает нагрузку с вашего сервера, отслеживающего состояние сеанса.
Процесс аутентификации пользователей в современных приложениях обычно имеет следующий формат:
- Клиент отправляет запрос на маршрут аутентификации с информацией о пользователе, такой как адрес электронной почты и пароль.
- Сервер проверяет личность пользователя, создает веб-токен JSON (JWT) и отправляет его обратно в браузер.
- Клиент сохраняет токен на одном из носителей данных браузера (API).
- Клиент добавляет токен к заголовку авторизации, чтобы делать последующие запросы к серверу.
Для сохранения токена на клиенте доступны три варианта хранения, в том числе:
- Локальное хранилище
- Хранилище сеансов
- Печенье
В этом руководстве мы рассмотрим, как обрабатывать аутентификацию в приложении Vue, подключенном к GraphQL API, мы сделаем это, создав мини-приложение. Мы будем использовать localStorage для хранения нашего токена.
API, к которому мы будем подключаться, можно найти здесь.
Предпосылки
В этом руководстве предполагается, что у читателя есть следующее:
Вы можете установить Vue CLI с помощью следующей команды, используя Yarn:
yarn global add @vue/cli
Инструменты, которые мы будем использовать для создания нашего приложения, включают:
Нажмите здесь, чтобы увидеть полную демонстрацию с сетевыми запросами
Вью-Аполлон — Это интеграция клиента Apollo для Vue.js, она помогает интегрировать GraphQL в наши приложения Vue.js!
Вьюекс — Vuex — это библиотека шаблонов управления состоянием для приложений Vue.js, она служит централизованным хранилищем для всех компонентов приложения. На него сильно влияет Архитектурный паттерн Flux сделано Фейсбук.
Просмотр маршрутизатора — Это официальная библиотека маршрутизации для Vue.jsэто упрощает маршрутизацию в наших приложениях Vue.js.
Начиная
Мы будем использовать инструмент Vue CLI для начальной загрузки нового проекта Vue. Этот инструмент поможет нам не беспокоиться о конфигурациях, чтобы начать работу с нашим приложением, поскольку мы можем вручную выбрать необходимые пакеты для нашего приложения.
Сначала мы создаем новый проект, используя create
команда:
vue create blogr
Переместите клавишу со стрелкой вниз на «выбрать функции вручную», нажмите клавишу ввода и выберите следующие функции:
Затем измените каталог на папку проекта с помощью этой команды:
cd blogr
Запустите свой проект командой:
yarn serve
Вы должны увидеть, что ваше приложение работает на после запуска команды подачи пряжи.
Создание пользовательского интерфейса
Откройте свой App.vue
файл, расположенный в вашем src
папку и удалите следующие строки кода:
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
Замените удаленный контент следующим:
<header class="header">
<div class="app-name">Blogr</div>
<div v-if="authStatus" id="nav">
<div>{{user.name}}</div>
<button class="auth-button" @click="logOut" > Log Out</button>
</div>
</header>
Мы получаем имя аутентифицированного пользователя и создали кнопку выхода, которая запускает logOut
метод.
Далее перейдите к src/views
и создать Register.vue
файл и включите в него следующие строки кода:
<template>
<div class="auth">
<h3>Sign Up</h3>
<form action="POST" @submit.prevent="registerUser">
<label for="name"> Name</label>
<input type="text" name="name" placeholder="John Doe" v-model="authDetails.name" />
<label for="email">Email Address</label>
<input type="email" name="email" placeholder="yourdopeemail@something.com" v-model="authDetails.email" />
<label for="password">Password</label>
<input type="password" name="password" placeholder="password" v-model="authDetails.password" />
<button class="auth-submit">submit</button>
<p class="auth-text"> Already have an account? <router-link to="/login"> Login </router-link> </p>
</form>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
name: 'Register',
data () {
return {
authDetails: {
name: '',
email: '',
password: ''
}
}
},
methods: {
registerUser: function () {
}
}
}
</script>
В этом блоке кода мы создали страницу регистрации без какой-либо функциональности, нажатие кнопки отправки запускает registerUser
метод, который пока ничего не делает.
Мы используем v-модель для создания двусторонней привязки данных к нашим полям ввода для authDetails
если значение нашей формы изменится, значение в authDetails
меняется вместе с ним.
Давайте добавим немного стиля в наше приложение, создадим styles
папка в /src/assets
. Внутри src
папка создать index.css
файл и включите следующее:
.header {
display: flex;
justify-content: space-between;
background-color: fuchsia;
height: 25%;
padding: 1rem;
}
.app-name {
font-weight: 900;
font-size: 3rem;
}
.auth {
display: flex;
flex-direction: column;
align-items: center;
}
.auth h3 {
margin-top: 2rem;
}
form {
max-width: 50%;
margin-top: 1rem;
padding: 4rem;
border: 1px solid #c4c4ce;
}
form input {
display: block;
margin-bottom: 1.2rem;
padding: 0.4rem 1.2rem;
background-color: white;
}
.auth-submit {
margin-top: .5rem;
padding: .5rem 1rem;
border: none;
background-color: fuchsia;
color: white;
font-weight: bold;
text-transform: capitalize;
border-radius: 0.3rem;
}
.auth-text a {
color: black;
text-decoration: none;
}
.auth-text a:visited {
color: inherit;
}
.auth-text a:hover {
text-decoration: underline;
}
.auth-text {
margin-top: .5rem;
}
.auth-button{
margin: .7rem 2rem 0 0;
padding: .5rem 2rem;
background-color: white;
border: none;
border-radius: .3rem;
}
main{
margin-top: 5rem;
display: flex;
justify-content: center;
}
Далее давайте создадим страницу входа, создадим Login.vue
файл в src/views
и включить в него следующее:
<template>
<div class="auth">
<h3>Log In</h3>
<form action="POST" @submit.prevent="loginUser">
<label for="email">Email Address</label>
<input type="email" name="email" placeholder="yourdopeemail@something.com" v-model="authDetails.email" />
<label for="password">Password</label>
<input type="password" name="password" placeholder="password" v-model="authDetails.password" />
<button class="auth-submit">submit</button>
<p class="auth-text"> Don't have an account? <router-link to="/"> Register </router-link> </p>
</form>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
name: 'Login',
data () {
return {
authDetails: {
email: '',
password: ''
}
}
},
methods: {
loginUser: function () {
}
}
}
</script>
Эта страница похожа на нашу Register.vue
странице, нажатие на кнопку отправки запускает loginUser
метод, который пока ничего не делает.
Далее замените содержимое Home.vue
со следующим:
<template>
<div class="home">
<main>
Yaay! User authenticated!
</main>
</div>
</template>
<script>
// @ is an alias to /src
export default {
name: 'Home',
components: {
},
computed: {
}
}
</script>
Эта страница будет служить нашей страницей панели инструментов, которая будет отображаться для нашего пользователя после аутентификации:
Настройка маршрутов
Далее давайте включим маршруты для входа, регистрации и страницы панели инструментов в наш файл маршрутизатора, расположенный в src/router/
.
Удалите содержимое в routes
массив и добавьте следующее в index.js
файл:
{
path: '/dashboard',
name: 'Home',
component: () => import('@/views/Home.vue'),
},
{
path: '/login',
name: 'Login',
// route level code-splitting
// this generates a separate chunk (login.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "login" */ '@/views/Login.vue')
},
{
path: '/',
name: 'Register',
// route level code-splitting
// this generates a separate chunk (register.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "register" */ '@/views/Register.vue')
},
{
path: '*',
redirect: 'login'
}
Эти маршруты используют разделение кода Webpack и загружаются отложенно, что по своей сути повышает производительность нашего приложения.
Мы также добавили *
– это известно как маршрутизатор с подстановочными знаками. Маршрутизатор выберет этот маршрут, если запрошенный URL-адрес не соответствует ни одному из заданных маршрутов, и пользователь будет перенаправлен на страницу входа.
Наше приложение теперь должно выглядеть примерно так, когда вы посещаете localhost:8080
:
Установка клиента Apollo с помощью Vue-Apollo
Apollo Client — это полноценный клиент GraphQL для вашей среды пользовательского интерфейса, он помогает вам подключаться, извлекать данные и изменять данные на сервере GraphQL.
Чтобы интегрировать Apollo в наше приложение Vue, нам нужно установить плагин vue-apollo для vue-cli:
vue add apollo
Этот плагин создает два файла, apollo.config.js
в корневом каталоге проекта и vue-apollo.js
в src
папку, он также внедряет провайдера Apollo в экземпляр Vue в main.js
.
Этот провайдер позволяет использовать экземпляры клиента Apollo в наших компонентах Vue. Далее, давайте сделаем некоторые настройки для нашего vue-apollo.js
файл, расположенный в нашем /src
папка.
Включите следующее в начало содержимого файла:
import { setContext } from 'apollo-link-context'
Это помогает нам использовать setContext
метод при добавлении authorization
заголовок к нашим HTTP-запросам.
Далее мы меняем httpEndpoint
мы будем подключаться к. Замените значение вашего httpEndpoint
переменная с этим:
const httpEndpoint = process.env.VUE_APP_GRAPHQL_HTTP || '
Добавьте следующее сразу после httpEndpoint
определено:
const authLink = setContext(async (_, { headers }) => {
// get the authentication token from local storage if it exists
const token = JSON.parse(localStorage.getItem('apollo-token'))
// Return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token || ''
}
}
})
Затем мы переопределяем ссылку Apollo по умолчанию на нашу authLink
поместите следующее в defaultOptions
объект:
link: authLink
defaultOptions
объект устанавливает значения по умолчанию для всего приложения для apolloClient
.
Приступим к созданию нашего apolloClient
экземпляр с нашим defaultOptions
объект как значение, мы экспортируем его с export
чтобы мы могли получить доступ apolloClient
в нашем vuex
хранить:
export const { apolloClient, wsClient } = createApolloClient({
...defaultOptions
// ...options
})
Далее замените createProvider
функция со следующим:
export function createProvider () {
// Create vue apollo provider
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
defaultOptions: {
$query: {
fetchPolicy: 'cache-and-network'
}
},
errorHandler (error) {
// eslint-disable-next-line no-console
console.log('%cError', 'background: red; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;', error.message)
}
})
return apolloProvider
}
createProvider
функция вызывается в main.js
файл, как только наше приложение инициализируется, он внедряет экземпляры клиента Apollo в наше приложение Vue и позволяет использовать Apollo в наших компонентах.
Запросы и мутации
Создайте папку с именем graphql
в твоей /src
папку, внутри нее создайте два файла с помощью следующей команды:
touch queries.js mutations.js
queries.js
файл будет содержать запросы к нашему серверу GraphQL, Query
— это запрос к API для получения данных. Запросы похожи на HTTP GET
запросы в REST API.
mutations.js
файл будет содержать мутации, внесенные в сервер GraphQL, Mutations
— это запросы, которые изменяют состояние данных на вашем сервере Apollo. Мутации похожи на HTTP PUT
, POST
или же DELETE
запрос в REST API.
Затем добавьте следующие строки кода в наш mutations.js
файл:
import gql from 'graphql-tag'
export const LOGIN_USER = gql`
mutation login ($email: String! $password: String! ){
login(email: $email password: $password ){
token
}
}
`
export const REGISTER_USER = gql`
mutation createUser($name: String! $email: String! $password: String! ) {
createUser( name: $name, email: $email, password: $password) {
token
}
}
`
gql
помогает нам писать наши запросы GraphQL, мы создали мутации для входа в систему и создания нового пользователя, содержимое нашей формы служит переменными для наших мутаций.
В нашем queries.js
файл, включите следующий запрос, запрос получает текущего аутентифицированного пользователя:
import gql from 'graphql-tag'
export const LOGGED_IN_USER = gql`
query {
me {
id
name
email
}
}
`
Настройка Vuex
Во-первых, давайте импортируем наш Mutations
, Queries
и apolloClient
пример:
import { apolloClient } from '@/vue-apollo'
import { LOGGED_IN_USER } from '@/graphql/queries'
import { LOGIN_USER, REGISTER_USER } from '@/graphql/mutations'
Импорт apolloClient
instance позволяет нам выполнять операции Apollo в нашем магазине.
Затем установите данные, которые нам понадобятся, в нашем state
введите следующее в state
объект:
token: null,
user: {},
authStatus: false
государственный объект является центральным хранилищем данных, которые будут использоваться во всем приложении. Он представляет собой «единый источник правды».
authStatus
является логическим значением, которое сообщает, аутентифицирован ли пользователь или нет, объект пользователя будет содержать сведения о аутентифицированном пользователе.
Далее настраиваем нашу getters
включите следующее в getters
объект:
isAuthenticated: state => !!state.token,
authStatus: state => state.authStatus,
user: state => state.user
Добытчики помочь с получением элементов в нашем объекте состояния, результат геттера кэшируется на основе его зависимостей и будет переоцениваться только тогда, когда некоторые из его зависимостей изменились.
Приступайте к созданию новых мутациив mutations
объект:
SET_TOKEN (state, token) {
state.token = token
},
LOGIN_USER (state, user) {
state.authStatus = true
state.user = { ...user }
},
LOGOUT_USER (state) {
state.authStatus=""
state.token = '' && localStorage.removeItem('apollo-token')
}
Мы создали мутации для изменения состояния в хранилище Vuex, функции мутации синхронны и обычно принимают два параметра — объект состояния и полезную нагрузку, которая может быть переменной или объектом.
Наконец, давайте настроим наш actions
, действия являются асинхронными функциями, используемыми для фиксации мутаций. Действия запускаются с помощью store.dispatch
метод:
async register ({ commit, dispatch }, authDetails) {
try {
const { data } = await apolloClient.mutate({ mutation: REGISTER_USER, variables: { ...authDetails } })
const token = JSON.stringify(data.createUser.token)
commit('SET_TOKEN', token)
localStorage.setItem('apollo-token', token)
dispatch('setUser')
} catch (e) {
console.log(e)
}
},
async login ({ commit, dispatch }, authDetails) {
try {
const { data } = await apolloClient.mutate({ mutation: LOGIN_USER, variables: { ...authDetails } })
const token = JSON.stringify(data.login.token)
commit('SET_TOKEN', token)
localStorage.setItem('apollo-token', token)
dispatch('setUser')
} catch (e) {
console.log(e)
}
},
async setUser ({ commit }) {
const { data } = await apolloClient.query({ query: LOGGED_IN_USER })
commit('LOGIN_USER', data.me)
},
async logOut ({ commit, dispatch }) {
commit('LOGOUT_USER')
}
Теперь, когда наш магазин настроен, давайте добавим функциональность в наши формы входа и регистрации, включим следующее в script
раздел вашего Register.vue
файл:
<script>
import { mapActions } from 'vuex'
....
methods: {
...mapActions(['register']),
registerUser: function () {
this.register(this.authDetails)
.then(() => this.$router.push('/dashboard'))
}
}
...
Для отправки действий в нашем компоненте мы используем метод mapActions
помощник, который сопоставляет методы компонента с this.$store.dispatch
.
Приведенный выше код отправляет данные формы в качестве полезной нагрузки в register
действие в нашем магазине Vuex, а затем меняет маршрут на /dashboard
.
Включите в свой Login.vue
файл:
<script>
import { mapActions } from 'vuex'
....
methods: {
...mapActions(['login']),
loginUser: function () {
this.login(this.authDetails)
.then(() => this.$router.push('/dashboard'))
}
}
...
Включите следующее в script
раздел вашего Home.vue
файл для получения сведений о пользователе:
<script>
import { mapGetters } from 'vuex'
....
computed: {
...mapGetters(['user'])
}
....
</script>
mapGetters
helper просто сопоставляет геттеры хранилища с локальными вычисляемыми свойствами.
Охрана маршрутов
Импортировать vuex
хранить в верхней части вашего router
файл:
import store from '../store'
Добавить meta
поле к нашему /dashboard
маршрут, это meta
помогает нам при определении нашего промежуточного программного обеспечения для навигации по маршрутам. Наша запись маршрута на приборной панели будет выглядеть примерно так:
{
path: '/dashboard',
name: 'Home',
component: () => import('@/views/Home.vue'),
meta: { requiresAuth: true }
},
Включите следующее непосредственно перед export default router
:
router.beforeEach((to, from, next) => {
// Check if the user is logged i
const isUserLoggedIn = store.getters.isAuthenticated
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!isUserLoggedIn) {
store.dispatch('logOut')
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next()
}
})
Это определяет нашу навигационную защиту для наших записей маршрута. Когда мы переходим к любому маршруту с requiresAuth
мета-поле проверяет, аутентифицирован ли пользователь и авторизован ли он для доступа к этому маршруту, и перенаправляет пользователя на страницу входа, если пользователь не авторизован.
Наше готовое приложение должно выглядеть примерно так:
Вывод
В этом посте мы увидели, как обрабатывать аутентификацию наших API-интерфейсов GraphQL с помощью vue-router, vue-apollo и Vuex. Вы можете узнать больше об Apollo GraphQL здесьвы также можете узнать больше о GraphQL на ЛогРокет блог. Проверьте хранилище для этого руководства на GitHub его можно использовать в качестве шаблона для создания шаблона вашего приложения. Вы также можете проверить GraphQL API хранилище и развернутая версия нашего приложение.