Skip to content

Instantly share code, notes, and snippets.

@kevinhq
Last active August 11, 2022 15:20
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kevinhq/691a08575d132b161c8eef037a0991a6 to your computer and use it in GitHub Desktop.
Save kevinhq/691a08575d132b161c8eef037a0991a6 to your computer and use it in GitHub Desktop.
How to implement two-factor authentication for Rails app by using Devise gem, Google authenticator, and ActiveModel::Otp gem
<# /app/views/users/activate_2fa.html.erb %>
<%= @svg.html_safe %>
<%= form_for(@user, url: activate_2fa_update_path) do |f| %>
<%= f.text_field :otp_response_code %>
<%= f.submit %>
<% end %>
gem 'devise', '~> 4.7.1'
gem 'active_model_otp', '~> 2.0.1'
gem 'rqrcode', '~> 1.1.2'
# /app/controllers/users/sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
def create
self.resource = resource_class.find_for_authentication(sign_in_params.except(:password, :otp_response_code))
if resource
if resource.active_for_authentication?
if resource && resource.otp_module_disabled?
continue_sign_in(resource, resource_name)
elsif resource && resource.otp_module_enabled?
if params[:user][:otp_response_code].present?
if resource.authenticate_otp(params[:user][:otp_response_code])
sign_in(resource, scope: :user)
continue_sign_in(resource, resource_name)
else
if resource.otp_counter < 5
resource.increment!(:otp_counter)
render 'two_factors_authentication' and sign_out(resource)
end
end
else
# wrong password or failed login should get back to login page
if resource.valid_password?(params[:user][:password])
render 'two_factors_authentication' and sign_out(resource)
else
sign_out(resource)
respond_with resource, location: new_user_session_path
end
end
end
end
end
end
private
def continue_sign_in(resource, resource_name)
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in)
sign_in(resource_name, resource)
resource.update_column(:last_action_at,Time.now)
yield resource if block_given?
if params[:redirect_to_path].present?
respond_with resource, location: params[:redirect_to_path]
else
respond_with resource, location: after_sign_in_path_for(resource)
end
end
end
<%# /app/views/users/sessions/two_factors_authentication.html.erb %>
<%= form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :role => 'form', :method => 'POST' }) do |f| %>
<%= f.hidden_field :email, value: params[:user][:email] %>
<%= f.text_field :otp_response_code %>
<%= f.submit %>
<% end %>
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def activate_2fa
qrcode = RQRCode::QRCode.new(current_user.provisioning_uri(nil, issuer: 'your-app-url.com'), :size => 12, :level => :h)
@svg = qrcode.as_svg(offset: 0, color: '000',
shape_rendering: 'crispEdges',
module_size: 4)
respond_to :html
end
def activate_2fa_update
if secure_params.key?(:otp_response_code)
if @user.authenticate_otp(secure_params[:otp_response_code])
@user.otp_module_enabled!
# do something here
else
# do something here
end
else
#turn off 2FA
@user.otp_module_disabled!
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment