こんにちは。 マネーフォワードで開発推進活動をしています木村です。
今日は、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アプリを起動し、右下の「+」をタップします。
QRコードリーダーが現れるのでQRコードを読み込みます。
正常に読み込みが完了すると認証コードが生成されるようになります。
この認証コードを先ほどのフォームに入力すると認証できることが確認できます。
まとめ
いかがだったでしょうか? 必要なコード量も少なく、比較的簡単に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