Обработка аутентификации в вашем приложении 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

Вы должны увидеть, что ваше приложение работает на после запуска команды подачи пряжи.

vue работающее приложение

Создание пользовательского интерфейса

Откройте свой 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

vue установить плагин

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

Наше готовое приложение должно выглядеть примерно так:

приложение vue завершено

Вывод

В этом посте мы увидели, как обрабатывать аутентификацию наших API-интерфейсов GraphQL с помощью vue-router, vue-apollo и Vuex. Вы можете узнать больше об Apollo GraphQL здесьвы также можете узнать больше о GraphQL на ЛогРокет блог. Проверьте хранилище для этого руководства на GitHub его можно использовать в качестве шаблона для создания шаблона вашего приложения. Вы также можете проверить GraphQL API хранилище и развернутая версия нашего приложение.

Впервые опубликовано в блоге Logrocket:

Похожие записи

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *