OTP-аутентификация c Devise

Что такое «OTP-аутентификация»?

Это отправка уникального одноразового OTP-пароля на зарегистрированный адрес электронной почты или номер телефона пользователя, который после ввода этого OTP получает доступ к своей учетной записи.

Так к традиционной комбинации имени пользователя и пароля добавляется дополнительный уровень безопасности, поскольку OTP скоротечен и применяется однократно.

Настройка «Devise» с «Confirmable»

Внедрим OTP-верификацию только через почту, для верификации по номеру телефона нужны сервисы вроде Twilio  —  ее мы не рассматриваем.

Сначала интегрируем Devise в приложение Ruby on Rails, следуя официальной документации.

Включаем в Gemfile библиотеку Devise.

В модель User добавим Confirmable, то есть модуль Devise для подтверждения пользователями адреса почты или номера телефона, в нашем случае  —  для подтверждения OTP.

1. Генерируем модель «Devise»

rails generate devise User

2. Добавляем «Confirmable» в модель «User»

Открываем файл app/models/user.rb и добавляем в конфигурацию Devise параметр :confirmable:

class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:lockable, :timeoutable, :confirmable
end

3. Выполняем миграцию БД

Чтобы обновить схему базы данных, генерируем и запускаем миграцию БД:

rails db:migrate

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

4. Переопределяем метод Devise «send_confirmation_instructions»

class User
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:lockable, :timeoutable, :confirmable

OTP_LENGTH = 6

def send_confirmation_instructions
token = SecureRandom.random_number(10**OTP_LENGTH).to_s.rjust(OTP_LENGTH, "0")
self.confirmation_token = token
self.confirmation_sent_at = Time.now.utc
save(validate: false)
UserMailer.confirmation_instructions(self, self.confirmation_token).deliver_now
end
end

Так мы переопределили send_confirmation_instructions для обработки подтверждения электронной почты: сгенерировали случайный 6-значный номер OTP, связали его с пользователем, отметили временну́ю метку отправления инструкций для подтверждения и отправили пользователю письмо подтверждения.

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

def confirmation_instructions(user, otp)
@user = user
@otp = otp
email = @user.email.presence || @user.unconfirmed_email
mail(to: email, subject: "Your Otp")
end

Помещаем представление электронной почты в файл app/views/user_mailer/users_instructions.html.erb.

После регистрации инструкции для подтверждения автоматически отправляются Devise на почту пользователя.

Напишем какой-нибудь HTML-код:

<div class="form-container mb-4">
<p class="heading">Confirm your email</p>
<p class="content">
We have sent OTP to registered email, please confirm your email
</p>

<%= form_with model: [:invitations, @user], url: validate_otp_invitations_users_path, method: :post do |form| %>
<%= form.hidden_field :id, value: @user.id %>

<%= form.text_field :otp, placeholder: "Type OTP" %>

<%= form.submit "Validate OTP" %>
<% end %>

<%= form_with model: [:invitations, @user], url: resend_otp_invitations_users_path, method: :post do |form| %>
<%= form.hidden_field :id, value: @user.id %>

<p class="text-center p-2 d-flex">
<%= form.submit "Resend OTP", style: "color: #0073ff;background: none;" %>
</p>
<% end %>
</div>

Сюда мы включили две формы: одну для валидации OTP, другую для повторной его отправки.

Чтобы аналогично обработать отправку этих форм на серверной стороне, напишем обе функции.

5. Обрабатываем подтверждение «Devise» на стороне контроллера

Для проверки ввода OTP создаем confirmations_controller:

class InvitationsController < ApplicationController
OTP_EXPIRATION_TIME = 5.minutes.ago

def validate_otp
user = User.find_by_confirmation_token(permitted_params[:otp])

if user && user.confirmation_sent_at >= OTP_EXPIRATION_TIME
user.confirm
user.update(confirmation_token: nil)
redirect_to confirmations_success_invitations_users_path, notice: "Email confirmed successfully!"
else
flash[:alert] = "Invalid or expired OTP. Please try again."
redirect_to success_invitations_users_path(id: params[:user][:id])
end
end

def resend_otp
user = User.find_by(id: params[:user][:id])
user.send_confirmation_instructions
redirect_to success_invitations_users_path(id: params[:user][:id])
end

private

def permitted_params
params.require(:user).permit(:otp)
end
end

Функцией validate_otp  —  для подтверждения конкретного пользователя  —  в таблице пользователей обнаруживается confirmation_token, а также задается время действия  —  обычно пять минут.

А функцией resend_otp инструкции подтверждения отправляются повторно.

Заключение

  • Это простая реализация OTP-аутентификации с функционалом Confirmable от Devise без сторонних библиотек или обширных конфигураций.
  • Что касается OTP-верификации по номеру телефона, для обработки этого процесса понадобятся сервисы вроде Twilio.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Sandesh Bodake: OTP-Based Authentication with Devise 🔐

Предыдущая статьяКак тестировать приложения Gofr?
Следующая статьяМиграции баз данных с Golang