こんにちは、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コードが表示されるので、アプリで読み込むと、下記のようにアプリでコードが表示されるようになります。
最後に
アクトインディでは、エンジニアを募集しています。 actindi.net
働きやすい環境で世の中に価値を残すことができるサービスを一緒に作りましょう!