Adding Two-Factor Authentication(2FA) to ActiveAdmin auth in a Ruby on Rails we...
source link: https://blog.kiprosh.com/adding-two-factor-authentication-2fa-for-activeadmin-auth-in-a-ruby-on-rails-web-application/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Adding Two-Factor Authentication(2FA) to ActiveAdmin auth in a Ruby on Rails web application
To enhance the security of a web application having a user authentication workflow, we use a security method called 2FA. It is also known as Two Factor Authentication(type of Multi-Factor Authentication). In this blog post, we will see how to implement email-based 2FA in ActiveAdmin auth of a Ruby on Rails application.
In the email-based 2FA approach, when logging in with an email and password, an OTP will be sent on a registered email address. Upon entering the OTP, it will successfully authenticate and the session will be started.
Also, we will see the following additional functionality and customizations in this article:
- OTP to auto-expire in 60 seconds.
- OTP to be reset automatically after 60 seconds.
- OTP authentication expires after 24 hours.
Steps to implement the 2FA
- Setup active_model_otp
gem 'active_model_otp', '~> 2.0', '>= 2.0.1'
2. Create a migration
otp_secret_key
otp_auth_at
$ rails g migration AddOtpSecretKeyAndOtpAuthAtToUser otp_secret_key:string otp_auth_at:timestamp
3. Add routes
# config/routes.rb
devise_scope :user do
get '/admin/otp', to: 'users/sessions/otp_authentications#new', as: :admin_otp_page
post '/admin/otp', to: 'users/sessions/otp_authentications#create', as: :admin_verify_otp
end
4. Configure and add a "before" action
# config/initializers/active_admin.rb
config.before_action :authenticate_current_user_with_otp!
# app/controllers/application_controller.rb
def authenticate_current_user_with_otp!
return if devise_controller? || current_user.otp_authenticated?
redirect_to(admin_otp_page_path)
end
5. Add Controller
# app/controllers/users/sessions/otp_authentications_controller.rb
# frozen_string_literal: true
module Users
module Sessions
class OtpAuthenticationsController < ActiveAdmin::Devise::SessionsController
prepend_before_action -> { authenticate_user!(force: true) }
skip_before_action :require_no_authentication
def new
return unless otp_sent?
current_user.send_otp_mail
session[:otp_invalid_after] = Time.zone.now.advance(minutes: 1)
end
def create
if current_user.authenticate_otp(params[:otp], drift: 1.minutes)
current_user.touch(:otp_auth_at)
redirect_to admin_dashboard_path
else
# set invalid OTP flash message
render :new
end
end
private
def otp_sent?
return true if session[:otp_invalid_after].nil?
session[:otp_invalid_after] < Time.zone.now
end
end
end
end
6. Add OTP page
# app/views/users/sessions/otp_authentications/new.html.erb
<%= form_tag admin_verify_otp_path do %>
<h2>Enter OTP</h2>
<h2>Please check your email for the OTP</h2>
<%= text_field_tag 'otp', nil, options = { placeholder: 'One Time Password', size: '6', maxlength: '6' } %>
<%= hidden_field_tag(:email, current_user.email) %>
<%= submit_tag 'Submit OTP' %>
<%= link_to 'Resend OTP', admin_otp_page_path %>
<%= link_to 'Logout', destroy_user_session_path %>
<% end %>
7. Add methods to verify auth and for the mailer
# app/models/user.rb
class User < ApplicationRecord
OTP_AUTH_EXPIRES_IN = 24.hours
has_one_time_password
def send_otp_mail
AdminMailer.user_otp(email, otp_code).deliver_now
end
def otp_authenticated?
return unless otp_auth_at?
otp_auth_at + OTP_AUTH_EXPIRES_IN > Time.zone.now
end
end
8. Add AdminMailer
# app/mailers/admin_mailer.rb
class AdminMailer < ActionMailer::Base
default from: DEFAULT_EMAIL_ADDRESS
def user_otp(email, otp_code)
@otp_code = otp_code
mail(
to: email,
subject: 'Sign-in: Email verification',
bcc: BCC_EMAIL_ADDRESS_FOR_OTP
)
end
end
# app/views/admin_mailer/user_otp.html.erb
<h3>Verify your login</h3>
<h2><%= "Your OTP is #{@otp_code}" %></h2>
9. If you're adding this to an existing User model you'll need to generate otp_secret_key with a migration like:
User.find_each { |user| user.update_attribute(:otp_secret_key, User.otp_random_secret) }
If you face any issue, drop a comment 📝 in the comments section below 👇 mentioning the issue and error-details if any.
We would ❤️ to hear from you. Thank you.
References
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK