こんにちは!kossyです!
最近はオリジナルアプリの実装に熱を入れ過ぎてブログの更新が疎かになっておりました、、、
アウトプットが大事だと頭でわかってはいるものの、
実行に移すのは大変ですね、、、
さて、今回はRailsアプリに2段階認証を導入する手順について、
ブログに残してみたいと思います。
環境
Rails 5.2.2
Ruby 2.5.1
devise 4.6.1
devise_two_factor 3.0.3
rqrcode 0.10.1
dotenv-rails 2.6.0
google_authenticator
macOs Mojave
[注意]
この解説記事内でgoogleの2段階認証アプリを使うので、実際に導入してみたい方は
お手持ちのスマートフォンにGoogle Authenticatorを入れて下さい。
iOS: 「Google Authenticator」をApp Storeで
Android: Google 認証システムのインストール - Android - Google アカウント ヘルプ
Gemの導入
まずはGemの導入からです。
./Gemfile
gem 'devise'
gem 'devise-two-factor'
gem 'rqrcode'
gem 'dotenv-rails'
$ bundle install
それぞれ端的に解説すると、
deviseは言わずと知れた、認証の仕組みを数コマンドで提供してくれるGemです。
devise_two_factorを使うと、簡単に2段階認証の仕組みをRailsアプリに導入することができます。
rqrcodeは、QRコードの生成を行うgemです。
dotenv-railsは環境変数の管理をしてくれるGemです。
deviseの導入
deviseの導入は細かい説明は省きます。
詳しく知りたい方は公式ドキュメントや、
[*Rails*] deviseの使い方(rails5版) - Qiita
こちらを参考にしてみてください。
$ rails g devise:install
$ rails g devise:views
$ rails g devise User
$ rails g devise:controllers users
$ rails db:migrate
上記のコマンドを順に実行してください。
次に、ルーティングを追加します。
config/routes.rb
Rails.application.routes.draw do
devise_for :users, controllers: {
registrations: 'users/registrations',
sessions: 'users/sessions'
}
end
これでdeviseの導入は終了です。
次にdevise_two_factorの設定を行います。
devise-two-factorを導入
ターミナルで以下のコマンドを実行します。
$ rails g devise_two_factor User TWO_FACTOR_ENCRYPTION_KEY
$ rails db:migrate
このコマンドを実行することで、以下の処理が実行されます。
・app/models/user.rbにdevise_two_factorの設定を追加
・:database_authenticatableを削除しようとする。 ※削除されない場合は手動で削除してください。
・config/initializers/devise.rbにwardenの設定を追加
・db/migrate下に二段階認証関連のカラムを追加するためのマイグレーションファイルを生成
追加されるカラムは以下の5種類です。
- encrypted_otp_secret(string)
- encrypted_otp_secret_iv(string)
- encrypted_otp_secret_salt(string)
- consumed_timestep(integer)
- otp_required_for_login(boolean)
ワンタイムパスワードのパラメータを入れるカラムと、
ユーザーが2段階認証を有効にしているかどうかを判断するためのカラムを追加しています。
次に、環境変数の設定を追加します。
.envファイルの設定
アプリケーションフォルダ直下に.envファイルを作成し、
環境変数を設定します。
./ .env
# 例です
TWO_FACTOR_ENCRYPTION_KEY=hogehogehogehogehogehogehogehogehogehogehoge
このファイルは.gitignoreに追加して、gitの管理から外れるようにしましょう。
.gitignore
.env
次に、application_controller.rbにStrongParametersでotp_attempt(認証コード)を許可するように設定を加えます。
StrongParameterの設定
StrongParameterについては以下の記事が詳しかったです。
Rails初学者がつまずきやすい「ストロングパラメータの仕組み」
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_in, keys: [:otp_attempt])
end
end
次項から、2段階認証の仕組みを実装していきます。
2段階認証を実装
Deviseを使っているRailsアプリに2段階認証を導入する - Qiita
こちらの記事のコードを参考に実装させていただきました。
app/controllers/users/sessions_controller.rbを編集し、通常のdeviseの認証の前に処理を挟むようにします。
class Users::SessionsController < Devise::SessionsController
prepend_before_action :authenticate_with_two_factor, only: [:create]
private
def authenticate_with_two_factor
user_params = params.require(:user).permit(:email, :password, :remember_me, :otp_attempt)
if user_params[:email]
user = User.find_by(email: user_params[:email])
elsif user_params[:otp_attempt].present? && session[:otp_user_id]
user = User.find(session[:otp_user_id])
end
self.resource = user
return unless user && user.otp_required_for_login
if user_params[:email]
if user.valid_password?(user_params[:password])
session[:otp_user_id] = user.id
render 'devise/sessions/two_factor' and return
end
elsif user_params[:otp_attempt].present? && session[:otp_user_id]
if user.validate_and_consume_otp!(user_params[:otp_attempt])
session.delete(:otp_user_id)
sign_in(user) and return
else
flash.now[:alert] = 'Invalid two-factor code.'
render :two_factor and return
end
end
end
end
次に、認証コードの入力画面を作成します。
app/views/devise/sessions/two_factor.html.erb
<h2>Log in</h2>
<%= form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f| %>
<div class="field">
<%= f.label :otp_attempt %><br />
<%= f.text_field :otp_attempt %>
</div>
<div class="actions">
<%= f.submit "Verify" %>
</div>
<% end %>
ルーティングは下記のように実装します。
resource :two_factor_auth, only: [:new, :create, :destroy]
次に、2段階認証周りの機能を提供するtwo_factor_auths_controller.rbを作成します。
app/controllers/two_factor_auths_controller.rb
class TwoFactorAuthsController < ApplicationController
def new
unless current_user.otp_secret
current_user.otp_secret = User.generate_otp_secret(32)
current_user.save!
end
@qr_code = build_qr_code
end
def create
if current_user.validate_and_consume_otp!(params[:otp_attempt])
current_user.otp_required_for_login = true
current_user.save!
redirect_to root_path
else
@error = 'Invalid pin code'
@qr_code = build_qr_code
render 'new'
end
end
def destroy
current_user.update_attributes(
otp_required_for_login: false,
encrypted_otp_secret: nil,
encrypted_otp_secret_iv: nil,
encrypted_otp_secret_salt: nil,
)
redirect_to root_path
end
private
def build_qr_code
label = "service_name"
issuer ="company_name"
uri = current_user.otp_provisioning_uri(label, issuer: issuer)
qrcode = RQRCode::QRCode.new(uri)
qrcode.as_svg(
offset: 0,
color: '000',
shape_rendering: 'crispEdges',
module_size: 2
)
end
end
QRコード表示画面を作成
app/views/two_factor_auths/new.html.erb
<h1>Enable 2FA</h1>
<% if @error %>
<div><%= @error %></div>
<% end %>
<div><%= raw @qr_code %></div>
<%= form_tag two_factor_auth_path do %>
<div class="field">
<%= label_tag :otp_attempt %><br />
<%= text_field_tag :otp_attempt %>
</div>
<%= submit_tag :submit %>
<% end %>
認証機能の有効化・無効化画面の例
app/views/home/index.html.erb(ユーザープロフィール画面があればそこに表示するのがベスト?)
<div>
<% if user_signed_in? %>
<% if current_user.otp_required_for_login %>
<%= button_to "Disable 2FA", two_factor_auth_path, method: :delete %>
<% else %>
<%= button_to "Enable 2FA", new_two_factor_auth_path, method: :get %>
<% end %>
<% end %>
</div>
これで一通りの実装は完了です。
それでは、ローカルで試してみます。(本ブログでは解説していないcontrollerとrootの設定をしています。)
localhost:3000/users/sign_upにアクセスし、必要情報を入力して、submitを押下します。
ユーザー登録に成功すると、enable 2FAのボタンが表示されるので、クリックします。
すると、QRコードが表示されるので、google認証のアプリでQRコードを読み取り、6桁の認証コードを入力します。
正しく認証をパスすると、Enable 2FAだったボタンが、Disable 2FAの表示に変わっていると思います。
これで2段階認証の導入は終了です。
思った通り動作するのはやっぱり嬉しいですね。
参考にさせていただいた記事
Deviseを使っているRailsアプリに2段階認証を導入する - Qiita
deviseとGoogle Authenticatorを用いてRailsシステムに「二段階認証」を導入した話 - LiBz Tech Blog
Railsの二段階認証Gem Devise-Two-FactorのDemoを触ってみた - Qiita
GitHub - tinfoil/devise-two-factor: Barebones two-factor authentication with Devise