【Rails】Vonage Verify APIを利用した二要素認証(2FA)チュートリアル

Ruby

Vonage Verify APIを利用して2FAが組み込まれたRailsアプリケーションを実装します。
今回紹介するサンプルアプリケーションは公式のチュートリアルをベースとしているので、あわせてご覧になってください。

事前準備

アカウント作成

Vonage Verify APIをはじめとしたVonageのコミュニケーションAPIを利用するには事前にアカウントを作成する必要があります。
Vonageのアカウント作成はこちらから行います。

Gemのインストール

必要となるGemをインストールします。

Gemfile

gem 'devise' # ユーザー認証で利用
gem 'vonage' # Vonage APIを利用する際に利用
gem 'dotenv-rails' # 環境変数ファイル作成で利用

2FAを行うユーザーをアプリケーションに登録する

Deviseのセットアップ

Deviseのセットアップを行います。Deviseの利用方法の詳細解説はdeviseのインストール手順をシンプルなログイン機能の実装で理解するで紹介しています。

Deviseのジェネレータを実行します。

$ rails generate devise:install

ルートURLを作成します。

routes.rb

root to: 'kittens#index'

コントローラーを作成します。

$ rails g controller Kittens index

ユーザーモデルを作成します。

$ rails g devise User

マイグレーションを実行します。

$ rails db:migrate

以下のようなユーザーテーブルが作成できていればOKです。

> describe users;

+------------------------+--------------+------+-----+---------+----------------+
| Field                  | Type         | Null | Key | Default | Extra          |
+------------------------+--------------+------+-----+---------+----------------+
| id                     | bigint       | NO   | PRI | NULL    | auto_increment |
| email                  | varchar(255) | NO   | UNI |         |                |
| encrypted_password     | varchar(255) | NO   |     |         |                |
| reset_password_token   | varchar(255) | YES  | UNI | NULL    |                |
| reset_password_sent_at | datetime     | YES  |     | NULL    |                |
| remember_created_at    | datetime     | YES  |     | NULL    |                |
| created_at             | datetime(6)  | NO   |     | NULL    |                |
| updated_at             | datetime(6)  | NO   |     | NULL    |                |
+------------------------+--------------+------+-----+---------+----------------+

ユーザーを作成する

localhost:3000/users/sign_upにアクセスし、ユーザーを作成します。

sign_up

ユーザー情報に電話番号を追加する

2FAで必要になる電話番号をユーザー情報に追加します。

マイグレーションファイルを作成します。

$ rails generate migration add_phone_number_to_users

マイグレーションファイルは以下の通りです。

class AddPhoneNumberToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :phone_number, :string
  end
end

マイグレーションを実行します。

$ rails db:migrate

phone_numberカラムが追加されていればOKです。

> describe users;

+------------------------+--------------+------+-----+---------+----------------+
| Field                  | Type         | Null | Key | Default | Extra          |
+------------------------+--------------+------+-----+---------+----------------+
| id                     | bigint       | NO   | PRI | NULL    | auto_increment |
| email                  | varchar(255) | NO   | UNI |         |                |
| encrypted_password     | varchar(255) | NO   |     |         |                |
| reset_password_token   | varchar(255) | YES  | UNI | NULL    |                |
| reset_password_sent_at | datetime     | YES  |     | NULL    |                |
| remember_created_at    | datetime     | YES  |     | NULL    |                |
| created_at             | datetime(6)  | NO   |     | NULL    |                |
| updated_at             | datetime(6)  | NO   |     | NULL    |                |
| phone_number           | varchar(255) | YES  |     | NULL    |                |
+------------------------+--------------+------+-----+---------+----------------+

電話番号をユーザー編集画面から更新できるようにする

ユーザー編集画面に電話番号を登録するフィールドを追加します。

app/views/devise/registrations/edit.html.erb

<div>
  <%= f.label :phone_number %> <i>(Leave blank to disable two factor authentication)</i><br />
  <%= f.number_field :phone_number, class: "form-control", placeholder: "e.g. 447555555555 or 1234234234234"  %>
</div>

phone_numberが更新できるようにapplication_controller.rbを編集します。

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  # アプリケーションにアクセスする際は認証を行う
  before_action :authenticate_user!

  # deviseに関するリクエストの場合、phone_numberの更新を許可
  before_action :configure_permitted_parameters, if: :devise_controller?

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:account_update, keys: [:phone_number])
  end
end

ログインユーザーの電話番号を更新する

http://localhost:3000/users/editにアクセスし、ログインユーザーに電話番号の情報を追加します。

電話番号のフォーマットはE.164にする必要があります。
E.164の場合、例えば日本の09012345678という携帯電話番号は、先頭の0を取り国番号の+81をつけた+819012345678となります。

edit_user

Vonage APIを組み込み、2FAを実装する

RailsアプリケーションにVonage APIを組み込んでいきます。

APIキーとAPIシークレットの設定

Vonage Verify APIを利用する際に必要となるAPIキーとAPIシークレットを確認します。
APIキーとAPIシークレットはダッシュボード画面で確認できます。

スクリーンショット_2021-12-05_16_17_56.png

環境変数の設定

APIキーとAPIシークレットを.envファイルに登録します。

.env

VONAGE_API_KEY=your_api_key
VONAGE_API_SECRET=your_api_secret

間違ってリモートリポジトリに環境変数を公開しないように.gitignore.envを追加しておくとよいでしょう。

.gitignore

.env

以下のようにして確認して値が表示されればOKです。

$ rails c

> ENV['VONAGE_API_KEY']
# APIキーが表示されればOK

> ENV['VONAGE_API_SECRET']
# APIシークレットが表示されればOK

2FA実施前のチェック処理

今回のアプリケーションでは、2FAを実行する条件は「認証画面(deviseに関連する画面)以外にアクセスする場合 かつ 2FAが完了していない場合 かつ 電話番号が登録されている場合」とします。

devise(認証)に関する画面以外にアクセスした場合のみ、2FAを実行するようにします。

app/controllers/application_controller.rb

before_action :verify_user!, unless: :devise_controller?

def verify_user!
  start_verification if requires_verification?
end

今回、2FAが完了しているかどうかの判定はセッションを利用します。
session[:verified]が存在していない(2FAが完了していない) かつ 電話番号が存在している場合は2FAを実施するようにします。

app/controllers/application_controller.rb

def requires_verification?
  session[:verified].nil? && !current_user.phone_number.blank?
end

2FAの実装: 認証コードの送信

2FAのプロセスには「認証コードの送信」と「入力された認証コードの検証」の2ステップがあります。
Vonage Verify APIを利用した「認証コードの送信」の実装は以下のようになります。

app/controllers/application_controller.rb

def start_verification
  result = Vonage::Client.new.verify.request(
    number: current_user.phone_number,
    brand: "Kittens and Co",
    sender_id: 'Kittens'
  )
  if result['status'] == '0'
    redirect_to edit_verification_path(id: result['request_id'])
  else
    sign_out current_user
    redirect_to :new_user_session, flash: {
      error: 'Could not verify your number. Please contact support.'
    }
  end
end

2FAの実装: 入力された認証コードの検証

認証コードを入力するためのルーティングを追加します。

routes.rb

resources :verifications, only: [:edit, :update]

認証コードの入力画面を作成します。

$ rails g controller Verifications edit update

app/views/verifications/edit.html.erb

<div class="panel panel-default devise-bs">
  <div class="panel-heading">
    <h4>Verify code</h4>
  </div>
  <div class="panel-body">
    <%= form_tag verification_path(id: params[:id]), method: :put do %>
      <div class="form-group">
        <%= label_tag :code %><br />
        <%= number_field_tag :code, class: "form-control"  %>
      </div>
      <%= submit_tag 'Verify', class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

<%= link_to 'Send me a new code', :root %>

画面から入力されたパラメータを受け取り、Vonage Verify APIによる検証をします。

def update
  confirmation = Vonage::Client.new.verify.check(
    request_id: params[:id],
    code: params[:code]
  )

  if confirmation['status'] == '0'
    session[:verified] = true
    redirect_to :root, flash: { success: 'Welcome back.' }
  else
    redirect_to edit_verification_path(id: params[:id]), flash[:error] = confirmation['error_text']
  end
end

動作確認

電話番号の登録後、ホーム画面にアクセスするとデバイスに認証コードが送られます。

verify

デバイスに送られてきた認証コードを入力します。

verify

http://localhost:3000/にリダイレクトされればOKです。

root_page

参考: Rails コンソールで2FAを確認する方法

Railsコンソール上でVonage Verify APIの2FAを確認する場合は以下のようになります。

$ rails c

### Vonage API Clientの作成
> client = Vonage::Client.new(api_key: 'YOUR--KEY', api_secret: 'YOUR-API-SECRET')

### 認証コードの送信
> result = client.verify.request(
  number: '+81xxxx', # E.164フォーマットで記載した電話番号
  brand: "Kittens and Co",
  sender_id: 'Kittens'
)

> result["status"]
=> "0" # OK

### 認証コードの検証
> confirmation = client.verify.check(
  request_id: result["request_id"],
  code: 'xxx' # SMSで送られたコード
)

> confirmation["status"]
=> "0" # OK

さいごに

今回紹介したサンプルアプリケーションはGitHubのnishina555/vonage-rails-devise-2fa-demoに公開しました。

Dockerを導入しているので、docker-compose upをするだけでアプリケーションが立ち上がり、2FAの確認ができるようになっています。
docker-compose upのみでRailsアプリケーションを起動できるDocker環境の構築手順はチームで共有するための『Rails 6 x MySQL 8』Docker環境構築手順で紹介しています。

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!