Implementing 2FA with Google Authenticator in Rails app
Explore the 2FA concept with Google Authenticator in a Rails app.
Today, security is a top concern for both users and developers of web applications. Passwords alone often do not provide sufficient protection against account takeovers and fraudulent access. This is where two-factor authentication (2FA) comes in — adding an extra layer of identity verification for logins beyond just a username and password.
In this post, we’ll explore how to implement 2FA in a Ruby on Rails application using Google Authenticator, one of the most popular and user-friendly 2FA tools.
What is 2FA?
2FA or two-factor authentication is when a system requires two steps to log a user in.
2FA or MFA is mostly used in login screens to verify and double-check a user’s identity for additional security.
Rather than depending solely on a user’s email address and their password, 2FA attempts to decrease the risk of imposters acquiring access to personal data by properly establishing a user’s identity first. This is done by using an additional piece of information to verify the identity.
Because there are lots of ways to perform 2FA, there are also lots of different implementations. Some use email, others use SMS, etc. One of the implementations is Google Authenticator, which we’ll be using in our project.
Let’s implement it
Add the following line to your Gemfile and save it:
gem 'google-authenticator-rails'
Go to terminal and run:
bundle install
Add the required columns to your model.
We need to add the columns used for storing the ‘google_secret’ and ‘mfa_secret’ to our User model.
The first column is google_secret. This is where we’ll store the encrypted code (which can be reset) used to connect the model (User) with Google Authenticator.
The second column is mfa_secret. This is where we’ll store a 6-digit code each time the user tries to log into their account. Every time the user logs out, the mfa_secret is reset to nil.
Create a migration for it:
rails generate migration add_columns_to_users google_secret:string mfa_secret:integer
In terminal, run the command:
rails db:migrate
Connect Google authenticator with your model (User)
To achieve this, you’ll add ‘acts_as_google_authenticated’ to your model (User).
‘encrypt_secrets: true.’ This setting will encrypt ‘google_secret’ inside of the database.
Class User < ApplicationRecord
Acts_as_google_authenticated lookup_token: :mfa_secret, encrypt_secrets: true
end
Create a session object to be able to authenticate
Now you need to create an empty model to store User data for authentication inside of it.
#app/models/user_mfa_session.rb
class UserMfaSession < GoogleAuthenticatorRails::Session::Base
end
Create the controller
We’ll need to create a controller to check if the 6-digit code the user entered and stored into our ‘mfa_secret’ column is indeed correct. Depending on that, the controller will either save the correct session data into our session object(UserMfaSession) or alternatively prevent the user from accessing the resources they do not have access to until they’ve established their identity.
Our controller will have two methods, “new” and “create”.
The “new” method is the one that’ll load your view after you’ve finished authorization with your email (username) and password. Inside of this view, the user will need to input their 6 digit code which was created by Google Authenticator.
The “create” method is the actual backbone of it all and is where we’ll check if the code is valid. If it’s invalid, the view will redirect the user and prevent access until they verify their identity with a correct code. Here is the code we ended up with:
class UserMfaSessionsController < ApplicationController
skip_before_action :check_mfa, only: [:new, :create]
def new
end
def create
user = current_user # grab your currently logged in user
user.mfa_secret = params[:mfa_code]
user.save!
if user.google_authentic?(params[:mfa_code])
UserMfaSession.create(user)
redirect_to root_path
else
flash.now[:danger] = "Invalid code"
render :new
end
end
end
Let’s now move on to modifying our ApplicationController, our next step in integrating Google Authenticator.
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_filter :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
if "/logout" == request.path
current_user.mfa_secret = nil
current_user.save!
UserMfaSession.destroy
end
end
end
After the User fills in their correct credentials (email and password), they will be redirected to the view where they get asked to enter a 6-digit code from Google Authenticator.
Here is the relevant code:
<div class="mfa-container">
<%= form_tag user_mfa_session_path, method: :post, class: "search-form", role: "search" do %>
<div class="form-group">
<%= label_tag "Token", 'Google Authenticator code'%>
<%= text_field_tag 'mfa_code', params[:mfa_code], class: "form-control form-control-line" %>
</div>
<%= submit_tag 'Log In', class: "btn btn-block btn-lg btn-primary text-uppercase fs-12 fw-600" %>
<% end %>
</div>
we’ll be putting our view in the following place: app/views/user_mfa_sessions/new.html.erb
So, as you can see, when the User tries to log in, they get redirected by ApplicationController to “new_user_mfa_session_path“. This path leads to “user_mfa_session_controller“ and, more specifically, its method “new“.
After the User enters their code, they need to click on the “Log In“ button. On click, “:mfa_code“ containing the 6-digit code entered by the User gets sent through to the “create_user_mfa_session_path“ path, which ends up in the “create” method inside of our “user_mfa_session_controller“ file.
One more tip about “google_secret“. You need to send the “google_secret“ at least once to the User via email so only they can see it. You can do so either by sending a QR code or a key. The method for creating the QR code is “current_user.google_qr_uri“ and for the key, it is “current_user.google_secret_value“.
Using Google Authenticator
Now I’d like to show you how Google Authenticator actually works in practice.
@user = User.new
@user.set_google_secret # => true
@user.google_secret_value # => 16-character plain-text secret, whatever the name of the secret column
@user.google_qr_uri # => http://path.to.google/qr?with=params
@user.google_authentic?(123456) # => true
@user.clear_google_secret! # => true
@user.google_secret_value # => nil
The User must have a “google_secret“ value set in the database using "user.set_google_secret”. The reason is that “google_secret“ contains the encrypted key connecting the User to their Google Authenticator. This key allows the User to connect their profile with the mobile Google Authenticator application. This code can be displayed either as a regular key or as a scannable QR code.
Now after our User has a “google_secret“ created inside the database, they need to connect their mobile application with their account so it can serve as a verification of identity in the future.
They can do so by either scanning the QR code (“current_user.google_qr_uri “) or by typing in the key manually (“current_user.google_secret_value“).
After our User connects their account to the mobile app, they should get to the view rendering their own 6-digit code. The below code is what the User enters on login:
Now our User is ready to use Google Secret. The procedure for authentication is now fairly straightforward:
- First, the User needs to do a standard authentication with their credentials (email/username and password).
- Then the User will be redirected to the view with the 6-digit input field.
- They’ll receive this in their Google Authenticator application.
- Then they’ll enter the code in the input fields.
- By clicking on “Log In,” they will now be able to make use of the rest of the site after passing Google Authenticator verification (or whatever resources you might have been hiding from them behind 2FA).
Conclusion
2FA affords a better stage of protection than authentication techniques that rely on single-thing authentication. Rather than depending solely on a user’s email address and password, 2FA attempts to decrease the risk of imposters acquiring access to personal data by properly establishing a user’s identity first.
Thanks for tuning in, and we hope this was helpful for you!
For more updates on the latest tools and technologies, follow the Simform Engineering blog.