I. General introduction
As you all know, 2-layer security is essential in all projects today. Regarding 2-layer security, you have seen using OTP of sms, email, or use google authentication app to authenticate. For using SMS, I have to use a third service, and have to pay a fee not to mention the very erratic network. Today I will introduce you how to use the Google Authentication app to perform two-factor security authentication in the Rails project. In the Rails project, you very often use devise to perform login / logout, I will combine with devise for you to apply more easily.
II. Setup
2.1 Principle of operation
First, I will talk a little bit about the operating principle. As you all know with gem devise, after the user login successfully will create session and allow the user to access your website. For services using 2FA as you know for example, Github, you will need to go to user settings to enable 2FA, here for simplicity, I will handle the following:
- User enter name / password
- Display QR code to perform QR scan
- After using the Google Authenticate app to scan the QR, the app side will display 6 numbers and I will input these 6 numbers to complete the login.
- Successful authentication is considered that you are active 2FA, since the next time you log back in, you just need to go to the app to see what the 6 numbers are, not need to scan the QR code anymore.
- Ok! essentially the flow is like that. Now I will go into details
2.2 Necessary Libraries
- Gem devise
- Gem devise-two-factor
- Gem rqrcode
2.3 Installation
- bundle install the gem just installed
- In the rails project directory, run the command rake secret to generate a base64 code and add the generated code to the .env file with the key TWO_FACTOR_SECRET
- Setup gem devise (in this section you can google more)
- After setting up gem devise and determining which model will apply the other devise gem, it will usually be the User model, in the User model, add the line below:
devise :two_factor_authenticatable, :otp_secret_encryption_key => ENV['TWO_FACTOR_SECRET']
TWO_FACTOR_SECRET env variable I just created in step 2
- Next, run the command
rails generate devise_two_factor MODEL TWO_FACTOR_SECRET
. That command will do the following:- Edit the app / models / MODEL.rb file
- Delete : database_authenticatable if this isn’t automatically deleted then you have to delete it manually
- Adjust the devise configuration file
- Create a migrate file to add the following columns:
- encrypted_otp_secret (string)
- encrypted_otp_secret_iv (string)
- encrypted_otp_secret_salt (string)
- consumed_timestep (integer)
- otp_required_for_login (boolean)
- Add otp_attempt to the devise params as shown below123456789class ApplicationController < ActionController::Basebefore_action :configure_permitted_parameters, if: :devise_controller?.....privatedef configure_permitted_parametersdevise_parameter_sanitizer.permit(:sign_up, keys: [:otp_attempt])endend
So we have completed the initial steps to set up 2FA. Next will go into more detail
III. Handle controller and view
- As you know, for gem devise, after I login successfully it will redirect to the page I requested, complete a login session. Now when applying 2FA, I will have to check my email / password. We add the code below:12345678910111213class SessionsController < Devise::SessionsControllerprepend_before_action :authenticate_with_two_factor, only: :createprivatedef authenticate_with_two_factoruser = User.find_by(email: params[:user][:email])return if user.blank?return unless user.valid_password?(params[:user][:password])session[:user_id] = user.idredirect_to new_two_factor_auth_pathendend
- Next we will create the TwoFactorAuthsController controller. In this controller I will handle the following123456789101112131415161718192021222324252627282930class TwoFactorAuthsController < ApplicationController# Sau khi vào hàm new, sẽ thực hiện update otp_secret.# Các bạn sẽ thắc mắc otp_secret ở đâu ra đúng không.# Đó chính là do thằng gem devise-two-factor đã xử lí và đưa các field# mình đã migrate ở bước trên về 1 field otp_secret.def new@user = User.find(session[:user_id]).decoratereturn if @user.otp_secret && @admin.otp_required_for_login@user.update!(otp_secret: User.generate_otp_secret)end# Hàm create thì không có gì khó cả. kiểm tra xem cái 6 số mình nhập vào có đúng hay ko.# đúng thì sẽ update field otp_required_for_login về true,# cái này để xác nhận là user đã hoàn thành xác thực 2FA,# lần sau login vào sẽ ko cần quét QR code nữa, sau đó thực hiện login như bình thườngdef create@user = User.find(session[:user_id]).decorateif @user.validate_and_consume_otp!(params[:otp_attempt])unless @user.otp_required_for_login@user.update!(otp_required_for_login: true)endsession.delete(:user_id)sign_in(@user)redirect_to root_path, notice: 'Login successfully'else@error = 'Login failed'render :newendendend
At this point, you will ask yourself a question, why on the Google Authenticate app every 30 seconds it generates a new code, and why a code on the app can be mapped to its value on the web. I will briefly explain like this, in the line otp_secret: User.generate_otp_secret it will generate a random code base32, this code is compatible with Google’s algorithm is to generate a new code every 30 seconds. on the original code I scanned the QR code. You can refer to this mechanism in the document below to see how it works ( https://www.rubydoc.info/gems/rotp/3.3.0 ) - Next in the controller twofactor view we add the code below123456789101112<% unless @user.otp_required_for_login %><%= image_tag @user.build_qr_code.to_data_url, class: "img_qrcode" %><% end %><%= form_tag two_factor_auths_path do %><div class="form-group"><%= label_tag :otp_attempt, "認証コード" %></br><%= text_field_tag(:otp_attempt, nil, class: 'form-control', required: true, pattern: '^d{6}$', maxlength: 6) %><%= submit_tag '認証', class: 'btn btn-primary', 'data-disable-with' => '送信中...' %></div><% end %>
Here will check if the user has enabled 2FA or not through otp_required_for_login if the Qrcode is not displayed, then display the text box to enter 6 numbers. - Next we need to create QRcode using decorate and rqrcode gem123456789101112131415161718class UserDecorator < Draper::Decoratordelegate_alldef build_qr_codelabel = "your_name"issuer = "LiBz Tech Blog"uri = otp_provisioning_uri(label, issuer: issuer)qrcode = RQRCode::QRCode.new(uri)qrcode.as_png(offset: 0,color: '000',shape_rendering: 'crispEdges',module_size: 2)endend
After completing the above steps, you can login to see if it is correct. The result will be as follows:
Also in App will be as follows
So I introduced an overview of how 2FA works in conjunction with Devise. In this article, I just go into detail how to operate and install 2FA, and other side issues such as setting up devise, routes so that the web can run smoothly, you can learn and apply for yourself.