С одноразовым паролем на основе времени (TOTP)

На прошлой неделе наш владелец продукта спросил, что потребуется, чтобы добавить двухфакторную аутентификацию в нашу систему. Мы используем Azure Active Directory, что означает настройку дополнительной политики через портал Azure. Это все, что нам нужно было сделать, чтобы включить двухфакторную аутентификацию.

Под влиянием вопроса владельца продукта я подумал, что пришло время добавить двухфакторную аутентификацию в мой сторонний проект. Я хотел использовать распространенный способ добавления двухфакторной аутентификации, одноразовый пароль на основе времени.

В этой статье описывается, как я использовал Node.js и Vue.js для реализации двухфакторной аутентификации в своем стороннем проекте.

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

Двухфакторная аутентификация

Двухфакторная или многофакторная аутентификация (MFA) становится стандартом де-факто для ИТ-систем. Скорее всего, вы уже используете несколько учетных записей MFA, например, от Google, GitHub или Apple. Если нет, вы должны включить его!

Многофакторная проверка подлинности — это процесс, при котором пользователь должен предоставить множество форм идентификации во время входа в систему. Это может быть код мобильного телефона или отпечаток пальца.

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

Разница между двухфакторной и многофакторной состоит в том, что двухфакторная всегда использует две формы идентификации. Многофакторная аутентификация может включать два или три фактора. В этой статье мы реализуем двухфакторную аутентификацию.

Одноразовый пароль на основе времени (TOTP)

Одноразовый пароль на основе времени (TOTP) — это распространенный способ двухфакторной аутентификации. Он использует алгоритм, который генерирует токен на основе текущего времени. Недавнее исследование показало, что TOTP — одна из самых удобных для пользователя техник двухфакторной аутентификации. На изображении ниже показан процесс TOTP.

Он начинается с того, что сервер генерирует случайный секрет из 32 символов. Внешний интерфейс преобразует этот секрет в QR-код и отображает его на веб-странице. Пользователь использует приложение для проверки подлинности, такое как Authy или Google Authenticator, для добавления учетной записи путем сканирования QR-кода. В ответ сервер сохраняет секрет пользователя.

После того, как пользователь входит в систему, на веб-сайте отображается поле, в котором пользователь должен ввести токен, сгенерированный приложением Authenticator. Приложение аутентификатора использует сохраненный секрет и текущее время для создания токена. После отправки на сервер сервер генерирует токен, используя сохраненный секрет и настоящее время. Затем он проверяет, совпадают ли токен от пользователя и сгенерированный токен.

Мой сторонний проект

Мой дополнительный проект состоит из серверной части Node.js и внешней части Vue 3. Проект с закрытым исходным кодом, но демонстрационное приложение с этой статьей реализует то же решение.

Серверный REST API

Серверная часть содержит REST API, реализованный с использованием Node.js и Fastify. REST API содержит два контроллера, пользовательский и клиентский контроллер. Пользовательский контроллер общедоступен и отвечает за регистрацию и аутентификацию пользователей. Контроллер клиентов защищен и содержит действие для получения списка клиентов.

Интерфейсное веб-приложение Vue

Интерфейс состоит из пяти разных экранов. Он использует библиотеку управления состоянием Vuex для глобального хранения данных приложения. HTTP-клиент Axios на основе обещаний используется для связи с серверной частью.

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

Стандартная аутентификация с использованием JWT

Я реализовал первый уровень аутентификации с использованием электронной почты и пароля. Когда пользователь входит в систему и проходит проверку подлинности, пользовательский контроллер создает веб-маркер JSON (JWT). Серверная часть возвращает JWT во внешний интерфейс. Внешний интерфейс должен включать токен JWT в заголовок каждого последующего HTTP-запроса.

Мы храним пользовательские данные в файловой базе данных JSON под названием LocallyDB.

Серверная часть хэширует пароли перед их сохранением с помощью bcryptjs. В третьей строке действие входа извлекает пользователя из базы данных. Затем в строке 5, если пользователь существует, он использует bcrypt.compareSync для сравнения хэша с заданным паролем.

В случае успеха действие входа создает JWT, подписывая объект пользователя с настроенным секретом. Затем он возвращает новый пользовательский объект, который включает в себя сгенерированный токен JWT.

Добавление двухфакторной аутентификации, серверная часть

После аутентификации пользователя он может включить двухфакторную аутентификацию. Он состоит из двух отдельных шагов, каждый из которых реализуется с использованием другого действия контроллера.

Шаг 1, генерация секрета и QR-кода

Первый шаг делает две вещи. Во-первых, он создает общий секрет TOTP, а во-вторых, генерирует QR-код из секрета. Бэкэнд возвращает и QR-код, и секрет во внешний интерфейс.

Мы используем speakeasy для создания общего секрета, как вы можете видеть в строке 4. Он возвращает секретный объект со следующими четырьмя свойствами: secret.ascii, secret.hex, secret.base32 и secret.otpauth_url. Каждое свойство содержит одно и то же значение, но в другой форме.

Последнее свойство secret.otpauth_url используется для генерации QR-кода. Библиотека QRcode генерирует QR-код в строке 5. Она использует строку в кодировке base64, содержащую png-изображение QR-кода. Серверная часть возвращает QR-код и секретный объект во внешний интерфейс.

См. ниже пример.

Шаг 2, проверка токена и включение двухфакторной аутентификации

Может быть, вы спросите, почему есть шаг 2? Шага 1 недостаточно, чтобы включить двухфакторную аутентификацию? Ты прав.

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

Ниже вы видите реализацию шага 2. В строке 9 мы используем библиотеку Speakeasy для проверки сгенерированного токена из приложения-аутентификатора. Мы не хранили сгенерированный секрет из шага 1 в бэкенде. Внешний интерфейс предоставляет сгенерированный секрет, как вы можете видеть в строке 7.

Войдите с помощью двухфакторной аутентификации

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

Сначала метод validateToken проверяет токен JWT и извлекает адрес электронной почты. Затем мы используем этот адрес электронной почты для извлечения пользователя из базы данных. Это включает в себя секрет TOTP.

Затем данный токен и секрет передаются verify методу speakeasy для проверки токена. Мы возвращаем результат проверки во внешний интерфейс.

Давайте теперь посмотрим, как эти шаги вызываются из внешнего интерфейса.

Добавление двухфакторной аутентификации, интерфейс

Внешний интерфейс Vue 3 состоит из следующих пяти компонентов Vue. Мы рассмотрим каждый компонент, чтобы увидеть, как они реализованы.

  • Дом
  • Авторизоваться
  • регистр
  • Включить двухфакторную аутентификацию
  • Панель инструментов, которая показывает защищенные данные

Дом

Это главный экран приложения. Он показывает панель навигации с кнопкой входа. Сообщение в центре экрана содержит две ссылки: одну для экрана входа в систему, а другую для экрана регистрации нового пользователя.

Войти

На этом экране пользователь может войти на веб-сайт, а затем получить доступ к защищенной панели инструментов.

Если приложение успешно аутентифицировало пользователя и у пользователя включена двухфакторная аутентификация, то на экране отображается поле для ввода токена TOTP. См. ниже.

Компонент входа обрабатывает как стандартный вход, так и проверку токена. Он содержит две формы: одну для обычного входа в систему, а другую для ввода токена TOTP. См. ниже. Шаблон представления показывает форму входа или форму токена проверки в зависимости от логического поля showTwoFactorPanel.

Форма входа выполняет метод login, а форма токена проверки выполняет метод validateToken при нажатии кнопок. Оба метода отправляют сообщение в хранилище Vuex. Затем хранилище Vuex вызывает метод для связи с серверной частью. Ниже вы видите метод validateToken, который является частью компонента LoginUser.vue.

Во второй строке вы видите, что он отправляет сообщение validateToken в хранилище Vuex. Сохраняем результат вызова API в хранилище. Затем метод validateToken считывает результат из хранилища в строке 7. Если токен правильный, приложение переходит к панели управления.

Если мы рассмотрим реализацию метода validateToken в магазине, мы используем Axios для вызова серверной части. Вызов API маркера проверки фиксируется в хранилище с использованием мутации SET_TWOFACTOR_LOGIN.

Панель приборов

После того, как пользователь прошел аутентификацию, приложение отобразит панель инструментов. Панель инструментов — это защищенный ресурс, который показывает список клиентов.

Компонент панели мониторинга использует хранилище Vuex для извлечения и хранения клиентов. После того, как клиенты возвращаются из серверной части, они назначаются массиву клиентов компонента. Каждый клиент визуализируется с использованием компонента CustomerCard. См. ниже компонент Vue Dashboard.

Зарегистрировать пользователя

На экране RegisterUser пользователь может создать новую учетную запись для доступа к защищенному ресурсу на сайте.

TwoFactorРегистрация

После того, как пользователь войдет в систему, он или она может включить двухфакторную аутентификацию, нажав кнопку TwoFactor на панели навигации. На экране отображается QR-код, который можно отсканировать с помощью приложения для аутентификации, такого как Authy.

Когда вы нажимаете кнопку 2-Factor, внешний интерфейс дает указание внутреннему сгенерировать секрет TOTP. Серверная часть преобразует секрет в QR-код, и QR-код отправляется обратно во внешний интерфейс в виде изображения.

Компонент TwoFactorRegistration содержит шаблон Vue для отображения QR-кода. Это поле изображения в строке 4. Тег изображения привязан к полю qr компонента. Поле qr заполняется строкой изображения png, которую возвращает серверная часть.

Замечания о реализации

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

Решение хранит секрет TOTP в незашифрованном виде там же, где и учетные данные пользователя. Это не очень хорошая практика. Вы должны зашифровать секрет TOTP и хранить ключ на другом сервере.

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

Заключение

В этой статье описывается реализация двухфакторной аутентификации с использованием сервера Node.js и клиента Vue.js. Решение использует одноразовый пароль на основе времени для создания второго уровня аутентификации.

Серверная часть Node.js использует Fastify для создания REST API с easyspeak в качестве библиотеки, которая выполняет двухфакторную аутентификацию. Интерфейс использует Axios для связи с сервером. Он хранит глобальное состояние приложения, используя библиотеку управления состоянием Vuex.

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

Вы можете найти реализацию клиента и сервера на GitHub.

Спасибо за чтение, и помните, никогда не переставайте учиться!