Money Forward Developers Blog

株式会社マネーフォワード公式開発者向けブログです。技術や開発手法、イベント登壇などを発信します。サービスに関するご質問は、各サービス窓口までご連絡ください。

20230215130734

google-authenticator-railsを試してみた

こんにちは。 マネーフォワードで開発推進活動をしています木村です。

今日は、google-authenticator-railsというgemを使ってみたので紹介したいと思います。  

google-authenticator-rails とは?

google-authenticator-rails とは、Googleが提供するAuthenticatorアプリ(iPhone, Android)を用いて二段階認証をするためのgemです。

Two-Factor Authentication とか Multi-Factor Authentication と呼ばれている認証です。

認証に使われているアルゴリズムは TOTP (Time-based One-time Password Algorithm) で、いくつかの条件(秘密鍵の共有等)と時刻を用いて認証コードを生成しています。

Google検索すると色々情報はみつかるので、興味のある方は検索してみてください。

では、さっそく簡単なアプリを作り、MFA認証するところまでを説明していきます。  

Installation

Railsアプリを作りgemをインストールします。

$ mkdir google-authenticator-rails-example
$ cd google-authenticator-rails-example
$ bundle init
$ echo "gem 'rails'" >> Gemfile
$ bundle install --path vendor/bundle
$ bundle exec rails new . --skip-bundle
$ echo "gem 'google-authenticator-rails'" >> Gemfile
$ bundle

インストール時のバージョンは、Rails v4.2.3, google-authenticator-rails v1.2.1 です。  

Setup

User

Userモデルを作成し、google-authenticator-railsに必要なカラムを用意します。 cf. GoogleAuthenticatorRails::ActiveRecord::ActsAsGoogleAuthenticated

$ bundle exec rails g model user name:string email:string salt:string google_secret:string

Authlogic gemを使用している際はpersistence_tokenというカラム名が被ってしまうため、READMEではsaltというカラム名を使っているようです。

$ bundle exec rake db:create
$ bundle exec rake db:migrate

Userモデルを作成したら、acts_as_google_authenticatedを適用します。

# app/models/user.rb

class User < ActiveRecord::Base
  acts_as_google_authenticated lookup_token: :salt, drift: 30, issuer: 'Money Forward'
  before_save {|record| record.salt = SecureRandom.hex unless record.salt }
  after_create {|record| record.set_google_secret }
end

acts_as_google_authenticatedでモデルに認証時に必要な機能を追加します。 lookup_tokenは前述の別名にしたカラムの設定です。 driftは認証時に遅延を許容するため秒数です。30を指定すると、30秒前の認証コードは有効になります。 issuerは認証コードを取得する際の表示で使用されます。  

UserMfaSession

UserMfaSessionを作成します。MFAのsessionを扱うクラスです。GoogleAuthenticatorRails::Session::Baseを継承したクラスを作るだけです。

# app/models/user_mfa_session.rb

class UserMfaSession < GoogleAuthenticatorRails::Session::Base
  # no real code needed here
end

 

UserMfaSessionController

$ bundle exec rails g controller user_mfa_sessions

UserMfaSessionを処理するControllerクラスです。

# app/controllers/user_mfa_sessions_controller.rb

class UserMfaSessiosnController < ApplicationController
  skip_before_filter :check_mfa

  def new
    @user = current_user
  end

  def create
    @user = current_user
    if @user.google_authentic?(params[:auth][:mfa_code])
      UserMfaSession.create(@user)
      redirect_to root_url
    else
      flash[:error] = "Wrong code"
      render :new
    end
  end
end

Userモデルにacts_as_google_authenticatedを適用した際にGoogleAuthenticatorRails::ActiveRecord::Helpersがincludeされるので、 User#google_authentic?で認証コードが正しいか判定できるようになります。

# app/views/user_mfa_sessions/new.html.erb

<% if flash[:error] %>
  <%= flash[:error] %>
  <br />
<% end %>
<img src="<%= @user.google_qr_uri %>">
<br />
<%= form_tag user_mfa_session_path, method: :post do %>
  <div class="actions">
    <%= text_field :auth, :mfa_code %>
    <%= submit_tag 'authenticate' %>
  </div>
<% end %>

User#google_qr_uriでQRコードのイメージのURLが取得できます。 Googleが提供しているQRコードを生成するAPIを使用しています。 QRコードにはGoogle authenticatorで認証コードを生成するための情報が入っています。 Userモデルに追加されたgoogle_secretの値をもとにTOTPを使用して生成したProvisioning URIです。

# https://github.com/jaredonline/google-authenticator/blob/v1.2.1/lib/google-authenticator-rails/active_record/helpers.rb#L14

ROTP::TOTP.new(google_secret_value, :issuer => google_issuer).provisioning_uri(google_label)
# => "otpauth://totp/kimura@example.com?issuer=Money+Forward&secret=rnibc63l3ylprhpe"

 

ApplicationController

# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  before_filter :check_mfa

  def current_user
    @current_user = User.find_or_create_by(name: 'kimura', email: 'kimura@example.com')
  end

  private
  def check_mfa
     if !(user_mfa_session = UserMfaSession.find) && (user_mfa_session ? user_mfa_session.record == current_user : !user_mfa_session)
      redirect_to new_user_mfa_session_url
    end
  end
end

今回は便宜上current_userを固定にしていますが、よくある認証系gem等が生やしてくれるcurrent_userをそのまま使うと良いと思います。 #check_mfaでMFA認証が済んでいるかどうかをチェックしており、認証済みでなければMFA認証するようリダイレクトしています。  

TopController

# app/controllers/top_controller.rb

class TopController < ApplicationController
  def logout
    UserMfaSession.destroy
    redirect_to :root
  end
end

 

Routing

# config/routes.rb

Rails.application.routes.draw do
  root 'top#index'
  get 'logout' => 'top#logout'
  resource :user_mfa_session, only: %i(new create)
end

 

認証してみる

準備が整えばRailsアプリを起動して http://localhost:3000 にアクセスすると http://localhost:3000/user_mfa_session/new へリダイレクトされてQRコードと入力フォームが表示されます。

このQRコードをiPhoneのAuthenticatorアプリで読み込みます。 Authenticatorアプリを起動し、右下の「+」をタップします。 authenticator_1

「バーコードをスキャン」をタップします。 authenticator_2

QRコードリーダーが現れるのでQRコードを読み込みます。 正常に読み込みが完了すると認証コードが生成されるようになります。 authenticator_3

この認証コードを先ほどのフォームに入力すると認証できることが確認できます。  

まとめ

いかがだったでしょうか? 必要なコード量も少なく、比較的簡単にMFA認証を試すことができました。 MFA認証済みかどうかの判定も既存の認証を邪魔しないので、既に運用中のアプリに追加することも簡単にできるので、興味のある方は試してみてはいかがでしょうか。

マネーフォワードでは、思い立ったらすぐ行動できる、フットワークの軽いエンジニアを募集しています。 ご応募お待ちしております。

【採用サイト】 ■『マネーフォワード採用サイト』 https://recruit.moneyforward.com/『Wantedly』 https://www.wantedly.com/companies/moneyforward

【公開カレンダー】 ■マネーフォワード公開カレンダー

【プロダクト一覧】 ■家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 https://moneyforward.com/家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 iPhone,iPad家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 Androidクラウド型会計ソフト『MFクラウド会計』 https://biz.moneyforward.com/クラウド型請求書管理ソフト『MFクラウド請求書』 https://invoice.moneyforward.com/クラウド型給与計算ソフト『MFクラウド給与』 https://payroll.moneyforward.com/消込ソフト・システム『MFクラウド消込』 https://biz.moneyforward.com/reconciliation/マイナンバー管理『MFクラウドマイナンバー』 https://biz.moneyforward.com/mynumber