アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

Railsで2段階認証を試してみました

こんにちは、nakamuraです。 securityって大事ですよね。少し前からRailsで2要素認証ってどうやるんだろうと気になっていたので、google-authenticator-railsを使ってどんな風に実装できるのか試してみました。基本的には google-authenticator-rails Readmeに記載してある通りに実装していけばOKでした。

Gemをインストール

はじめに、Gemfileにgoogle-authenticator-railsを追記し、bundle installを実行します。

gem 'google-authenticator-rails'

Userモデルを作成

次に、Userモデルにgoogle-authenticator-railsで必要なカラムを追加します。 通常は、passwordカラムなど他にもカラムがあると思いますが、今回はお試しなので、割愛します。

$ rails g model user email:string google_secret:string

Userクラスにacts_as_google_authenticatedを設定します。 これで、google-authenticator-railsのメソッドが使えるようになります。

class User < ApplicationRecord
  acts_as_google_authenticated
end

Readme によるとGoogleAuthenticatorRailsはアカウント区別するためにlabelが必要とのことで、デフォルトではemailカラムを使用しますが、他にもcolumn_nameオプションで別カラムを指定したり、procでカスタマイズも可能とのこと。とりあえず、今回はデフォルトを使用します。

そうすると、下記のように、User#set_google_secret でUserモデルのgoogle_secretカラムに値が設定され、User#google_qr_uri メソッドでQRコードが表示できるようになります。

Loading development environment (Rails 5.2.3)
irb(main):001:0> user = User.new
irb(main):002:0> user.email = "test@example.com"
irb(main):003:0> user.set_google_secret
irb(main):004:0> user.google_qr_uri
=> "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2FTEST%2520MY%2520APP%3Atest%40example.com%3Fsecret%3Dedotpifgivmj2lmf%26issuer%3DTEST%2BMY%2BAPP&chs=200x200"
irb(main):005:0>

サンプルアプリの作成

それでは、以降で、簡単なQRコードを表示して、スマホアプリのGoogleAuthenticatorでコードが表示されるところまで実装して行きたいと思います。 まず、Userモデルを下記のように書き換えます。本来であれば、 validationなども定義しないとですが、今回は割愛します。

class User < ApplicationRecord
  acts_as_google_authenticated drift: 10, issuer: 'TEST MY APP'
  after_create { |record| record.set_google_secret }
end

driftはアプリで表示されたコードを何秒間遅延して許容するかどうかの設定とのことで、デフォルトは5秒とのこと。issuerはアプリで表示される発行元の名称です。

次に、UserMfaSessionモデルを作成します。 Readmeにある通り、GoogleAuthenticatorRails::Session::Base継承し、コードの記載は不要です。

# app/models/user_mfa_session.rb

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

次に、コントローラーを作成します。 User#google_authentic? でフォームに設定されたコードが正しいかを確認できるようになります。

# app/controllers/user_mfa_session_controller.rb

class UserMfaSessionsController < ApplicationController
  skip_before_action :check_mfa

  def new
    @user = current_user
  end

  def create
    @user = current_user # grab your currently logged in @user
    if @user.google_authentic?(params[:mfa_code])
      UserMfaSession.create(@user)
      redirect_to root_path
    else
      flash[:error] = "Wrong code"
      render :new
    end
  end
end

そして、#check_mfaメソッドを記載し、認証が済んでいない場合は、認証ページにリダイレクトさせます。

# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  before_action :check_mfa

  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_path
    end
  end
end

認証ページは以下のように設定します。

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

img src="#{@user.google_qr_uri}"
br
= form_tag user_mfa_session_path, method: :post do
  .actions
    = text_field_tag :mfa_code
    = submit_tag 'authenticate'

Welcomeコントローラーを作成し、ルーティングを設定します。

# app/controllers/welcome_controller.rb

class WelcomeController < ApplicationController
  def index
  end

  def logout
    UserMfaSession.destroy
    redirect_to :root
  end
end
# config/routes.rb

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

これで、localhost:3000にアクセスするとQRコードが表示されるので、アプリで読み込むと、下記のようにアプリでコードが表示されるようになります。

f:id:iam-nakamura:20190414115031j:plain

最後に

アクトインディでは、エンジニアを募集しています。 actindi.net

働きやすい環境で世の中に価値を残すことができるサービスを一緒に作りましょう!