Это краткое руководство, предназначенное для помощи начинающим разработчикам в создании VueJS SPA с аутентифицированными вызовами API.

Мы начнем с создания REST-полнофункциональной серверной части Rails, ориентированной на API. API-first означает, что одни и те же конечные точки API могут использоваться разными клиентами Web / JS, мобильными приложениями, сторонними API, и в идеале все они должны использовать унифицированный поток аутентификации, а JWT хорошо подходит для этой цели. Хотя в этой статье мы рассмотрим создание единого клиента VueJS, сам API можно легко адаптировать для повторного использования другими потребителями API.

Инструменты для бэкэнда:
Rails 5.2.0,
Ruby 2.4.4,
gem bcrypt 1.3.12,
gem jwt_sessions 2.1.0,
Драгоценный камень redis 4.0.1,
драгоценный камень rack-cors 1.0.2

Мы не привязаны к этим точным версиям, я просто перечисляю те, которые я установил в своей локальной среде. А теперь давайте создадим классическое приложение для задач.

Бэкэнд

  1. Создайте приложение rails rails new silver-octo-invention --api -T. Причудливое имя проекта генерируется GitHub автоматически. Параметр-T исключает Minitest, среду тестирования по умолчанию. Мы будем использовать RSpec.
  2. Отрегулируйте Gemfile, чтобы он выглядел примерно так

3. Запустите bundle install

4. Запустите rails generate rspec:install. Эта команда генерирует

.rspec
spec/spec_helper.rb
spec/rails_helper.rb

5. Теперь давайте создадим модель User. Начнем с минимума обязательных полей модели rails g model user email:string password_digest:string

6. Добавьте null: false настройки в строки миграции.

7. Запустите rails db:migrate

8. Добавьте вызов метода has_secure_password в модель User.

9. Теперь давайте создадим задачи. Беги rails g model todo title:string user:references && rails db:migrate

10. Построим контроллеры. Во-первых, нам нужно включить JWTSessions::RailsAuthorization в ApplicationController. Модуль обеспечивает действия авторизации, которые защищают безопасные конечные точки.

11. Нам понадобится обработка исключений для неавторизованных запросов, так что давайте сразу добавим ее.

12. Кстати, чтобы использовать JWT, нам нужно указать начальную конфигурацию. Гем JWTSessions по умолчанию использует HS256 алгоритм, и ему нужен ключ шифрования.
Кроме того, по умолчанию гем использует redis в качестве хранилища токенов, поэтому вам понадобится рабочий экземпляр сервера Redis. Однако есть возможность выбрать память в качестве хранилища токенов (может быть полезно в тестовом env). Более подробную информацию о конкретных настройках можно найти в README.

13. Теперь давайте создадим конечную точку регистрации. С сеансами на основе токенов и SPA у нас есть 2 наиболее распространенных варианта хранения токенов на клиенте - файлы cookie и localStorage. Разработчик должен решить, где они будут хранить токены. Принимая решение, имейте в виду - файлы cookie уязвимы для CSRF, а localStorage уязвим для атак XSS. Уязвимость CSRF решаема - я обычно предпочитаю файлы cookie только для http как наиболее безопасное хранилище токенов.
jwt_sessions gem сам предоставляет набор токенов - доступ, обновление и CSRF для случаев, когда cookies выбрано в качестве хранилища токенов. С учетом сказанного, давайте использовать файлы cookie вместе с токеном CSRF, предоставленным гемом (гем автоматически управляет проверками CSRF, когда JWT передается с помощью файлов cookie запроса).
Сеанс внутри драгоценного камня представлен в виде пары токенов - доступ и обновление. Маркер доступа имеет короткий срок службы (по умолчанию 1 час), а обновление имеет относительно долгий срок службы (2 недели). Срок действия настраивается. Токен обновления используется для продления доступа по истечении срока его действия.
Хотя есть смысл передавать токен обновления внешним службам API или мобильным приложениям, клиенты JS обычно недостаточно безопасны для хранения драгоценного токена обновления. Разработчик должен решить, какую информацию передавать или не передавать в JS. Гем jwt_sessions предоставляет возможность выдать новый токен доступа, передав старый просроченный, поэтому мы можем избежать передачи токена обновления клиенту JS. Поскольку и обновления, и токены доступа связаны друг с другом, будет легко определить, был ли украден доступ у JS-клиента, и сбросить утечку сеанса (2 пользователя - исходный пользователь и злоумышленник в конечном итоге будут иметь 2 разных токена доступа, указывающих к тому же токену обновления).
А теперь давайте создадим конечную точку регистрации. Конечная точка должна создать пользователей, собрать полезную нагрузку JWT и передать ее через файлы cookie с ответом, а также токен CSRF через тело ответа.

Спецификации для обеспечения работы регистрации.

14. Теперь мы можем создать контроллер входа в систему.

И спецификации для входа.

15. Вот и конечная точка обновления. Поскольку мы создаем конечную точку для веб-клиента, мы будем продлевать новый доступ со старым, срок действия которого истек. Позже мы можем создать отдельный набор конечных точек для использования другими API-потребителями (мобильными и т. Д.), Которые будут работать через токены обновления, но в этом случае мы не собираемся рисковать и показывать токен обновления жестоким внешний мир.
Мы ожидаем, что для обновления будут использоваться только просроченные токены доступа, поэтому в refresh_by_access_payload методе передается блок с unauth исключением. При желании внутри блока мы можем уведомить группу поддержки, очистить сеанс или пропустить блок и игнорировать этот вид деятельности.
Библиотека JWT автоматически проверяет утверждения об истечении срока действия, и, чтобы избежать исключения для токена доступа с истекшим сроком действия, мы использовать метод claimless_payload.

Обновить спецификации:

16. Почти готово. Пришло время создать контроллер задач.

Чтобы он заработал, нам также понадобится current_user, так что давайте добавим его.
После авторизации токена мы можем копаться в полезной нагрузке и извлекать все, что мы решили сохранить внутри. В нашем случае это user_id.

Общие спецификации задач:

17. Мы почти закончили работу с API, теперь можно зарегистрироваться, войти в систему, обновить access_token и управлять задачами. Но пока мы здесь, давайте также добавим возможность выхода.

18. Чтобы клиент JS мог отправлять запросы к API, нам нужно настроить CORS.

Конфигурация маршрутов:

Фронтенд

В этом руководстве мы разработаем автономное интерфейсное приложение.

Инструменты для фронтенда:

Node.js
Диспетчер версий Node.js - NWM
Диспетчер пакетов Node.js - NPM
VueJS CLI
Axios

$ node -v
v10.4.1
$ npm -v
6.1.0
  1. Установите Vue CLI.
$ npm install --global vue-cli

2. Инициализировать приложение.

$ vue init webpack todos-vue

Эта команда задаст нам пару вопросов.

? Project name todos-vue
? Project description Todos Vue.js application
? Author Yulia Oletskaya <[email protected]>
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? Yes
? Pick an ESLint preset Standard
? Set up unit tests Yes
? Pick a test runner karma
? Setup e2e tests with Nightwatch? No
? Should we run `npm install` for you after the project has been created? (recommended) npm

3. Запустите приложение.

$ cd todos-vue
$ npm run dev

И теперь он транслируется http://localhost:8080

4. Удалите автоматически созданный компонент HelloWorld и создайте новый Signin.vue. Каталог приложения должен выглядеть примерно так.

$ tree . -I "node_modules"
.
├── README.md
├── build
│   ├── build.js
│   ├── check-versions.js
│   ├── logo.png
│   ├── utils.js
│   ├── vue-loader.conf.js
│   ├── webpack.base.conf.js
│   ├── webpack.dev.conf.js
│   ├── webpack.prod.conf.js
│   └── webpack.test.conf.js
├── config
│   ├── dev.env.js
│   ├── index.js
│   ├── prod.env.js
│   └── test.env.js
├── index.html
├── package-lock.json
├── package.json
├── src
│   ├── App.vue
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   └── Signin.vue
│   ├── main.js
│   └── router
│       └── index.js
├── static
└── test
   └── unit
      ├── index.js
      ├── karma.conf.js
      └── specs
         └── Signin.spec.js

5. Давайте добавим ссылки на таблицы стилей в /index.html

6. Установите axios в каталог todos-vue.

$ npm install --save axios vue-axios

Подготовьте файлы конфигурации.

$ tree src/backend
src/backend
└── axios
   └── index.js

Определите конфигурацию и заставьте Vue использовать конфигурацию axios.

7. Создайте компонент Signin. Мы получаем файлы cookie с токеном доступа из ответа сервера, и они будут обрабатываться браузером. В теле ответа мы получим CSRF. Токен CSRF может храниться прямо в localStorage, так как нам все равно, даже если он будет украден в результате внезапной атаки XSS.

Визуализация:

8. Чтобы управлять всей авторизацией доступа / обновления, нам нужно создать специальную оболочку для axios. Желаемый поток следующий:

  • JWT хранится в файле cookie только для HTTP;
  • Для всех запросов, кроме OPTIONS и GET, мы должны добавить токен CSRF в заголовки запроса;
  • По истечении срока действия JWT следующий запрос API с этим файлом cookie возвращает 401;
  • На этом этапе мы должны обработать неавторизованный код ответа и сделать запрос обновления с истекшим токеном доступа для нового файла cookie;
  • Если запрос успешен, мы должны повторить исходный запрос, в противном случае выбросить 401;

Собственно, нам нужно будет построить даже 2 обертки axios. Первый будет использовать перехватчики для обработки ошибок 401 (экземпляр secure axios), а второй не будет иметь специальных прослушивателей повторов (экземпляр plain axios), но будет выполнять запросы на обновление и повторные попытки - это необходимо для предотвращения бесконечных циклов.
Как вы могли заметить, мы уже используем простой вариант для входа в систему, поскольку нам не нужно повторять запросы при неудачных входах / подписках:

this.$http.plain.post(‘/signin’, { email: this.email, password: this.password })

Конфигурация роутера:

Поскольку файл cookie предназначен только для HTTP, JS нелегко определить, есть он или нет. Вместо этого мы будем использовать простой флаг входа в систему. Некоторые люди могут сказать, что флаг можно легко изменить в консоли браузера, и поэтому незарегистрированный пользователь будет иметь доступ к «безопасным» URL-адресам. Действительно, флаг можно изменить вручную. Но поскольку он используется только для предотвращения перенаправления на страницу входа, он ни в коем случае не нарушает безопасность, так как в случае попытки получить / обновить защищенный ресурс без действительного файла cookie, пользователь все равно будет перенаправлен на страницу входа.

9. Теперь давайте создадим компонент регистрации. Это очень похоже на Signin.

Немного наглядного представления

10. Наконец, мы можем создать компонент todos. Реализация довольно наивна, основная цель здесь - продемонстрировать поток аутентификации с помощью базового CRUD.

$ tree src/components
src/components
├── Signin.vue
├── Signup.vue
└── todos
   └── List.vue

Маршруты.

Визуализация.

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

11. И последнее - кнопка выхода. Давай сделаем это.

Визуализация

Wohoo, вот и все, теперь у нас есть полностью функциональное безопасное приложение CRUD VueJS SPA.

Спасибо за чтение!
Само приложение доступно на GitHub.
Я планирую продолжить серию, добавить панель администратора, роли пользователей, расширить использование утверждений JWT, добавить разрешения для полезной нагрузки - следите за обновлениями .
UPD:
2-я часть серии.
3-я часть серии.

📝 Прочтите этот рассказ позже в Журнале.

🗞 Просыпайтесь каждое воскресное утро и слышите самые интересные истории, мнения и новости недели, ожидающие в вашем почтовом ящике: Получите примечательный информационный бюллетень›