---
title: "المصادقة الثنائية بكلمات المرور لمرة واحدة (OTP) في Ruby on Rails باستخدام ROTP"
description: "تطبيق المصادقة الثنائية بكلمات مرور لمرة واحدة طريقة ممتازة لإضافة طبقة أمان إضافية لمستخدميكم."
canonical: https://oozou.com/ar/blog/otp-2fa-in-ruby-on-rails-with-rotp-42
locale: ar
date: 2020-06-08
author: "OOZOU Team"
---

# المصادقة الثنائية بكلمات المرور لمرة واحدة (OTP) في Ruby on Rails باستخدام ROTP

> تطبيق المصادقة الثنائية بكلمات مرور لمرة واحدة طريقة ممتازة لإضافة طبقة أمان إضافية لمستخدميكم.

Published: 2020-06-08 · Author: OOZOU Team (Engineering) · 5 min read

# ما هي المصادقة الثنائية بـ OTP؟

المصادقة الثنائية (2FA) تعني أن النظام يطلب خطوتين لتسجيل دخول المستخدم، مثل اسم المستخدم وكلمة المرور، ثم رمز يُرسل عبر البريد الإلكتروني أو رسالة SMS.
أما المصادقة الثنائية بكلمة مرور لمرة واحدة (OTP 2FA) فهي أسهل على المستخدم، لأن الرمز يُولَّد على جهازه بفضل سرّ (secret) مشترك بين التطبيق والمستخدم. ولإعداد OTP، يمسح المستخدم أولًا رمز QR يحتوي على هذا السر، باستخدام تطبيق مثل Google Authenticator أو 1Password. وبعد الإعداد يستطيع جهاز المستخدم توليد رمز OTP يبقى صالحًا لفترة زمنية محددة، تتراوح عادة بين 15 و30 ثانية.

# الإعداد

مع [rtop gem](https://github.com/mdp/rotp) يصبح إعداد المصادقة الثنائية بـ OTP بسيطًا جدًا.
سأفترض أن لديكم نموذج مستخدم مع نظام مصادقة قائم، وغالبًا ما يكون Devise.
لنثبّت rotp وrqrcode حتى نتمكن من توليد رموز QR تمسحونها من مدير كلمات المرور المفضل لديكم.

```ruby
# Gemfile
gem 'rotp'
gem 'rqrcode'
```

# إعداد ROTP

لإعداد ROTP نحتاج إلى إضافة عمود `otp_secret` وعمود `last_otp_at` (من نوع integer، لا date) إلى جدول User، وسنستخدمهما لتوليد رمز OTP التالي والتحقق منه.

```shell
bin/rails generate migration add_otp_secret_to_users otp_secret:string last_otp_at:integer
bin/rails db:migrate
```

# توليد السر والتحقق من أول رمز OTP للمستخدم

نحتاج الآن إلى السماح للمستخدمين بإعداد OTP. أين تضعون ذلك في تطبيقكم قرار يعود إليكم، ومن منظور تجربة المستخدم هناك خياران:

1. حماية OTP إلزامية
    -   بعد التسجيل في التطبيق، أعيدوا توجيه المستخدم إلى صفحة إعداد OTP ليضبط سر OTP الخاص به
2. حماية OTP اختيارية
    -   اسمحوا للمستخدم بتفعيل حماية OTP أو إيقافها
        -   بعد التفعيل، أعيدوا توجيهه إلى صفحة إعداد OTP ليضبط سر OTP الخاص به
        -   بعد الإيقاف، احذفوا سر OTP

لإنشاء `otp_secret` نولّد سلسلة base32 باستخدام مكتبة ROTP، ثم نتحقق من أن المستخدم أعدّ تطبيق المصادقة لديه بشكل صحيح قبل حفظ السر، حتى لا نحرمه من الدخول إلى حسابه.
سأنفذ كل ذلك داخل الـ controller لتسهيل العرض، لكنني أنصح بنقله إلى مكان آخر، مثل [form objects](https://oozou.com/blog/ensuring-maintainability-in-large-rails-apps-fat-controllers-done-right-34) أو service objects أو interactors.

```ruby
# app/controllers/otp_secrets_controller.rb
class OtpSecretsController < ApplicationController
  def new
    @otp_secret = ROTP::Base32.random
    totp = ROTP::TOTP.new(
      @otp_secret, issuer: 'YourAppName'
    )
    @qr_code = RQRCode::QRCode
      .new(totp.provisioning_uri(current_user.email))
      .as_png(resize_exactly_to: 200)
      .to_data_url
  end

  def create
    @otp_secret = params[:otp_secret]
    totp = ROTP::TOTP.new(
      @otp_secret, issuer: 'YourAppName'
    )

    last_otp_at = totp.verify(
      params[:otp_attempt], drift_behind: 15
    )

    if last_otp_at
      current_user.update(
        otp_secret: @otp_secret, last_otp_at: last_otp_at
      )
      redirect_to(
        after_otp_configuration_path,
        notice: 'Successfully configured OTP protection for your account'
      )
    else
      flash.now[:alert] = 'The code you provided was invalid!'
      @qr_code = RQRCode::QRCode
        .new(totp.provisioning_uri(current_user.email))
        .as_png(resize_exactly_to: 200)
        .to_data_url
      render :new
    end
  end
end
```

# الـ view

نحتاج الآن إلى إنشاء نموذج يعرض الـ action المسمى new أعلاه

```ruby
# app/views/otp_secrets/new.html.erb
<%= form_tag otp_secrets_path, method: :post do %>
  <%= hidden_field_tag :otp_secret %>
  <%= image_tag @qr_code %>
  <%= text_field_tag :otp_attempt, label: "<strong>Verify (enter a one-time password)"</strong> %>
<% end %>
```

يُفترض أن يكون لديكم الآن شيء يشبه هذا
هذا كل ما يلزم لإعداد المستخدم. بقي فقط أن نُلزم المستخدم بإدخال رمز OTP عند تسجيل الدخول

# التحقق من رموز OTP عند تسجيل الدخول

إذا كنتم تُلزمون جميع المستخدمين بتأمين حساباتهم بـ OTP، فالأمر بسيط: أضيفوا حقل إدخال نصي إلى نموذج تسجيل الدخول وتحققوا منه. أما إذا كان OTP اختياريًا، فعليكم التحقق مما إذا كان مفعّلًا أثناء تسجيل الدخول، وتأجيل إتمام الدخول حتى يتحقق المستخدم من رمز OTP بنجاح.
منطق التحقق من OTP بسيط للغاية. لقد نفذناه مرة من قبل، وكل ما نحتاجه تعديل بسيط ليشمل last_otp_attempt

```ruby
last_otp_at = totp.verify(
  otp_attempt, after: user.last_otp_at, drift_behind: 15
)
if last_otp_at
  user.update(last_otp_at: last_otp_at)
else
	# Return an error to the user
end
```

# أفكار إضافية

هذا كل ما في الأمر. يكفيكم هذا المقال للبدء، لكن هناك أمور أخرى تستحق التفكير:

1. يجب تشفير `otp_secret` لحماية مستخدميكم في حال تسرّب قاعدة البيانات. أنا أستخدم [attr_encrypted](https://github.com/attr-encrypted/attr_encrypted).
2. عليكم إجراء بعض التحقق للتأكد من أن سر OTP لم يُعبث به. أفضّل استخدام ActiveSupport::MessageEncryptor لأتأكد من أن ما أرسلته إلى المستخدم هو نفسه ما يعود إليّ، ثم أفك ترميز السر وأعيد ترميزه للتأكد من أنه فعلًا سلسلة base32.
3. إذا كنتم تستخدمون عملية تسجيل دخول من خطوتين، فتأكدوا من أن مستخدميكم:
    -   لا يُعدّون مسجّلين فعليًا حتى تتحققوا من رمز OTP بنجاح.
    -   أو أنشئوا before_action يتحقق من أنهم أدخلوا رمز OTP صحيحًا بعد آخر تسجيل دخول. والخيار الثاني أعلى تكلفة بكثير لأنه يُنفَّذ مع كل طلب.

---

Contact OOZOU: hello@oozou.com · +66 2544 0000 · https://oozou.com/ar/contact

More for AI agents: https://oozou.com/llms.txt · every page has a markdown twin at its URL plus ".md"
