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

Настройка Auth0

Есть много вариантов обеспечения безопасности в приложениях. Для меня разгрузка деталей хранения имени пользователя / пароля, рабочих процессов смены пароля, регистрации новых пользователей и т. Д. Делает разработку моего приложения менее сложной. Команда Auth0 проделала невероятную работу по созданию инструментов, которые позволят вам как разработчику с легкостью добавить лучшую в своем классе безопасность в свои приложения. Основываясь на предыдущем проекте, давайте настроим Auth0 в приложении и создадим базовую страницу входа.

Вам нужно будет создать учетную запись с Auth0. После того, как у вас будет учетная запись, создайте нового клиента, щелкнув меню клиентов, а затем кнопку нового клиента. Это даст вам экран для имени клиента и выбора типа проекта.

После того, как вы нажмете «Создать», вы попадете на страницу быстрого старта, где вы сможете выбрать свой фреймворк, и они предоставят руководство по установке. Это поможет вам добавить Auth0 в приложение с помощью виджета блокировки. Я собираюсь использовать базовый сценарий Auth0 и настраиваемую страницу входа в этот проект. Виджет блокировки - отличный инструмент для быстрого начала работы, а руководство - лучший ресурс для его использования. Основное различие будет заключаться в поле входа в систему, руководство будет использовать виджет блокировки, и я предоставлю настраиваемую форму… любой из них будет обрабатывать запрос аналогичным образом.

На панели управления клиента Auth0 выберите вкладку настроек. Здесь вы найдете свой домен и идентификатор клиента. Эти значения понадобятся позже при вызове API через скрипт Auth0. Вам также нужно будет установить разрешенный URL-адрес обратного вызова. Для разработки это будет http: // localhost: 8080 (если вы используете vue-cli и не меняли порт).

Обязательно нажмите кнопку сохранения настроек внизу. Вы также захотите добавить пользователя для тестирования. Нажмите на опцию меню пользователей слева, а затем на кнопку создания пользователя. Введите данные и нажмите кнопку «Сохранить».

Затем давайте добавим в приложение Auth0. Есть несколько способов установить пакет, я буду ссылаться на него через cdn. В index.html добавьте следующую строку:

<!-- Auth0 -->
    <script src="https://cdn.auth0.com/w2/auth0-7.4.min.js"></script>

Настроить auth.js

Создайте файл в корне / src с именем auth.js. Здесь будет размещена логика авторизации для всего приложения. Сначала создайте экземпляр Auth0.

/* eslint no-undef: "off" */
const auth0 = new Auth0({
  domain: 'YOUR DOMAIN',
  clientID: 'YOUR CLIENT ID',
  responseType: 'token',
  callbackURL: window.location.origin + '/'
})

Вам нужно будет ввести данные своего домена и идентификатора клиента на вкладке настроек на панели инструментов Auth0. Если вы использовали шаблон веб-пакета из vue-cli, есть файлы конфигурации, где вы можете установить эти данные и указать на них через process.env.variable_name, ознакомьтесь с документацией здесь.

Этот файл экспортирует 4 вспомогательные функции, которые мы можем использовать в нашем приложении:

войти / выйти / checkAuth / requireAuth

// login
let login = (username, password) => {
  auth0.login({
    connection: 'Username-Password-Authentication',
    responseType: 'token',
    email: username,
    password: password,
    scope: 'openid email'
  },
  function (err) {
    if (err) alert('something went wrong: ' + err.message)
  })
}

Функция входа в систему вызывает Auth0.login и передает ему объект конфигурации. Auth0 предоставляет несколько подключений для входа в социальные сети и предприятия, но здесь я использую подключение по имени пользователя и паролю. Тип ответа установлен на токен, что указывает на то, что мы хотим использовать JWT (веб-токен json) для нашей стратегии безопасности. Переменная области видимости сообщает auth0 детали, которые будут закодированы в jwt, в зависимости от вашего backend api вам может потребоваться добавить дополнительные данные для идентификации пользователя на стороне api.

// logout
let logout = () => {
  localStorage.removeItem('id_token')
  localStorage.removeItem('profile')
}

Функция выхода из системы просто удаляет id_token и элементы профиля из локального хранилища. Эти значения устанавливаются при успешном входе в систему (подробнее об этом чуть позже).

// checkAuth
let checkAuth = () => {
  if (localStorage.getItem('id_token')) {
    return true
  } else {
    return false
  }
}

Функция checkAuth просто возвращает true, если в локальном хранилище есть значение с идентификатором id_token. Для производственного приложения вы можете рассмотреть возможность декодирования токена и проверки, чтобы убедиться, что значение является токеном jwt и срок его действия не истек. Auth0 предлагает библиотеку jwt-decode, которая поможет в этом. Эта библиотека не будет проверять ваш токен (что происходит на стороне сервера), но ее можно использовать для подтверждения токена.

// requireAuth
let requireAuth = (to, from, next) => {
  if (!checkAuth()) {
    console.log('auth failed ...')
    let path = '/login'
    let result = auth0.parseHash(window.location.hash)
    if (result && result.idToken) {
      // set token in local storage
      localStorage.setItem('id_token', result.idToken)
      // redirect to home page
      path = '/'
      // get user profile data
      auth0.getProfile(result.idToken, function (err, profile) {
        if (err) {
          // handle error
          alert(err)
        }
        let user = JSON.stringify(profile)
        localStorage.setItem('profile', user)
      })
    }
    next({
      path: path
    })
  } else {
    next()
  }
}

Эта функция вызывается из хука маршрутизатора beforeEnter, она получает переменную to и from и переменную обратного вызова с именем next. Это дает вам возможность проверить маршрут до загрузки компонента. Сначала мы вызываем функцию checkAuth, если она проходит, мы просто вызываем обратный вызов next () и позволяем всему продолжаться.

Если аутентификация не удалась, мы проверяем URL-адрес на предмет хэша и токена. Служба Auth0 по умолчанию настроена для использования метода перенаправления, поэтому все приложение перенаправляется обратно на URL-адрес обратного вызова в конфигурации Auth0. Поскольку этот хук beforeEnter установлен на нашем домашнем маршруте, мы можем проверить его на наличие хэша и токена и установить их в наше локальное хранилище. Мы также берем профиль пользователя и помещаем его в локальное хранилище. И последнее, что следует отметить в этой функции: у меня есть переменная с именем path, которая по умолчанию имеет значение / login. Если в URL-адресе есть хэш и токен, я сбрасываю путь, по которому пользователь возвращается домой (вы также можете отправить redirectUrl в Auth0 и вернуть пользователя туда, где они были, для простоты я отправляю их обратно на домашнюю страницу) . Если хеша или токена не было и проверка подлинности завершилась неудачно, пользователь попадет на страницу входа.

Наконец, экспортируйте функции:

export default {
  checkAuth,
  login,
  logout,
  requireAuth
}

Создать контейнер для входа

Теперь, когда у нас настроена логика аутентификации, давайте создадим простую страницу входа. В папке с контейнерами добавьте новый файл с именем Login.vue.

<template>
  <div id="login">
    <div class="columns">
      <div class="column is-one-quarter">
        <form @submit.prevent="login">
          <p class="control">
            <input class="input" v-model="email" placeholder="email">
          </p>
          <p class="control">
            <input class="input" v-model="pass" placeholder="password" type="password">
          </p>
          <p>
            <button class="button" type="submit">Login</button>
          </p>
        </form>
      </div>
    </div>
  </div>
</template>
<script>
import auth from '../auth'
export default {
  name: 'login',
  data () {
    return {
      email: '',
      pass: ''
    }
  },
  methods: {
    login () {
      auth.login(this.email, this.pass)
    }
  }
}
</script>

Здесь я использую простую форму с входными данными, привязанными через v-модель. Когда форма отправляется, метод входа в систему просто передает входные данные в функцию входа в систему аутентификации.

Регулировка роутера

Давайте внесем некоторые изменения в экземпляр маршрутизатора, чтобы вызвать функцию requireAuth в обработчике beforeEnter.

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
import login from './containers/Login'
import home from './containers/Home'
import dashboard from './containers/dashboard'
import projects from './containers/projects'
import auth from './auth'
// application routes
const routes = [
  { path: '/login', component: login },
  { path: '/', component: home, beforeEnter: auth.requireAuth },
  { path: '/dashboard', component: dashboard, beforeEnter: auth.requireAuth },
  { path: '/projects', component: projects, beforeEnter: auth.requireAuth }
]
// export router instance
export default new Router({
  mode: 'history',
  routes,
  linkActiveClass: 'is-active'
})

Настройте панель навигации

Затем необходимо обновить панель навигации, подключив кнопку выхода из системы и отображая только то, что пользователь вошел в систему. В компоненте navbar.vue добавьте следующие методы:

methods: {
    logout () {
      auth.logout()
      this.$router.go('/login')
    },
    isLoggedIn () {
      return auth.checkAuth()
    }
  }

Обязательно импортируйте файл аутентификации:
импортируйте аутентификацию из "../auth"

Настройте событие нажатия на кнопку:

<a class="button" @click="logout()">Logout</a>

и, наконец, добавьте v-if на верхний уровень навигации:

<nav class="nav has-shadow" v-if="isLoggedIn()">

Заголовки авторизации в вызовах API

Теперь, когда наше приложение настроено для аутентификации наших пользователей, приложению необходимо отправить токен на наш api при выполнении защищенных вызовов. Чтобы это сработало, я установлю заголовок авторизации для объекта заголовка по умолчанию в axios.

Вернитесь в файл auth.js и в разделе, где он устанавливает токен в локальном хранилище, добавьте следующую строку:

// set auth headers
axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('id_token')

Это добавляет заголовок авторизации к каждому вызову API, выполненному с помощью axios. Помимо установки значения при установке токена аутентификации, его необходимо установить при загрузке приложения (если есть токен). Добавьте следующие строки в файл auth.js вне функций:

// set auth header on start up if token is present
if (localStorage.getItem('id_token')) {
  axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('id_token')
}

Ответный перехватчик

Последний элемент, который нужно добавить, чтобы сделать его безопасным, - это создать перехватчик ответа, который выйдет из системы, если будет возвращен авторизованный вызов. В файле main.js добавьте следующее:

import auth from './auth'
import axios from 'axios'
axios.interceptors.response.use((response) => {
  return response
}, function (error) {
  // Do something with response error
  if (error.response.status === 401) {
    console.log('unauthorized, logging out ...')
    auth.logout()
    router.replace('/login')
  }
  return Promise.reject(error)
})

Если ответ возвращается и ошибка 401, вызовите auth.logout (). Затем пользователь будет перенаправлен на страницу входа.

Проверка на стороне сервера

Чтобы убедиться, что наша аутентификация работает, давайте создадим простой экспресс-сервер для его проверки. Создайте папку с именем server и добавьте файл с именем app.js. Вам нужно будет установить express, express-jwt и cors. Корс необходим, поскольку мы собираемся запустить экспресс-сервер на другом порту.

$ yarn add express express-jwt cors --dev
OR 
$ npm install express express-jwt cors --save --dev

Я добавил флаг разработчика в приведенное выше утверждение, чтобы убедиться, что это только зависимости от разработки. Добавьте следующий код в файл server / app.js:

var express = require('express')
var app = express()
var cors = require('cors')
var jwt = require('express-jwt')
var jwtCheck = jwt({
  secret: 'YOUR_CLIENT_SECRECT',
  audience: 'YOUR_CLIENT_ID'
})
app.use(cors())
// check security for anything under the secured route
app.use('/secured', jwtCheck)
// open call
app.get('/ping', function(req, res) {
  res.send("All good. You don't need to be authenticated to call this");
})
// secured call
app.get('/secured/ping', function(req, res) {
  res.status(200).send("All good. You only get this message if you're authenticated");
})
app.listen(3000, function () {
  console.log('Example app listening on port 3000!')
})

Здесь я создал простой экспресс-сервер с двумя маршрутами. Маршрут / ping не защищен и может быть вызван кем угодно. Любой путь под / secure будет обработан функцией jwtCheck, и действительный токен должен быть отправлен в заголовке запроса. Поэтому, если я звоню / защищаю / пингую, мне нужно будет предоставить действующий токен, чтобы получить ответ.

Вам нужно будет предоставить функции jwtCheck 2 переменные: секрет и аудиторию. Аудитория - это ваш идентификатор клиента с панели управления клиента в Auth0, а секрет - это секрет клиента с того же экрана. Чтобы получить более полный пример серверной части, просмотрите репозиторий экспресс-примеров Auth0 здесь.

Вернувшись к клиентской части нашего приложения, давайте внесем последние изменения, чтобы протестировать наше приложение. В файле main.js добавьте следующую строку, чтобы установить baseUrl для axios.

axios.defaults.baseURL = 'http://localhost:3000'

Затем в home.vue добавьте кнопку для вызова защищенного API:

<button type="button" class="button" v-on:click="testSecured()">Test API - Secured</button>
methods: {
    testSecured: function () {
      console.log('sending secured test call to api ...')
      axios.get('/secured/ping').then((response) => {
        console.log(response)
      }, (response) => {
        console.log(response)
      })
    }
  }

Запустите приложение и запустите сервер узлов [node server / app.js], войдите в систему и протестируйте, чтобы убедиться, что вы можете выполнять аутентифицированные вызовы. Кнопка запишет ответ на консоль:

Заключение

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