Giter VIP home page Giter VIP logo

two_factor_authentication's Introduction

Two factor authentication for Devise

Gitter

Build Status Code Climate

Features

  • Support for 2 types of OTP codes
    1. Codes delivered directly to the user
    2. TOTP (Google Authenticator) codes based on a shared secret (HMAC)
  • Configurable OTP code digit length
  • Configurable max login attempts
  • Customizable logic to determine if a user needs two factor authentication
  • Configurable period where users won't be asked for 2FA again
  • Option to encrypt the TOTP secret in the database, with iv and salt

Configuration

Initial Setup

In a Rails environment, require the gem in your Gemfile:

gem 'two_factor_authentication'

Once that's done, run:

bundle install

Note that Ruby 2.1 or greater is required.

Installation

Automatic initial setup

To set up the model and database migration file automatically, run the following command:

bundle exec rails g two_factor_authentication MODEL

Where MODEL is your model name (e.g. User or Admin). This generator will add :two_factor_authenticatable to your model's Devise options and create a migration in db/migrate/, which will add the following columns to your table:

  • :second_factor_attempts_count
  • :encrypted_otp_secret_key
  • :encrypted_otp_secret_key_iv
  • :encrypted_otp_secret_key_salt
  • :direct_otp
  • :direct_otp_sent_at
  • :totp_timestamp

Manual initial setup

If you prefer to set up the model and migration manually, add the :two_factor_authenticatable option to your existing devise options, such as:

devise :database_authenticatable, :registerable, :recoverable, :rememberable,
       :trackable, :validatable, :two_factor_authenticatable

Then create your migration file using the Rails generator, such as:

rails g migration AddTwoFactorFieldsToUsers second_factor_attempts_count:integer encrypted_otp_secret_key:string:index encrypted_otp_secret_key_iv:string encrypted_otp_secret_key_salt:string direct_otp:string direct_otp_sent_at:datetime totp_timestamp:timestamp

Open your migration file (it will be in the db/migrate directory and will be named something like 20151230163930_add_two_factor_fields_to_users.rb), and add unique: true to the add_index line so that it looks like this:

add_index :users, :encrypted_otp_secret_key, unique: true

Save the file.

Complete the setup

Run the migration with:

bundle exec rake db:migrate

Add the following line to your model to fully enable two-factor auth:

has_one_time_password(encrypted: true)

Set config values in config/initializers/devise.rb:

config.max_login_attempts = 3  # Maximum second factor attempts count.
config.allowed_otp_drift_seconds = 30  # Allowed TOTP time drift between client and server.
config.otp_length = 6  # TOTP code length
config.direct_otp_valid_for = 5.minutes  # Time before direct OTP becomes invalid
config.direct_otp_length = 6  # Direct OTP code length
config.remember_otp_session_for_seconds = 30.days  # Time before browser has to perform 2fA again. Default is 0.
config.otp_secret_encryption_key = ENV['OTP_SECRET_ENCRYPTION_KEY']
config.second_factor_resource_id = 'id' # Field or method name used to set value for 2fA remember cookie
config.delete_cookie_on_logout = false # Delete cookie when user signs out, to force 2fA again on login

The otp_secret_encryption_key must be a random key that is not stored in the DB, and is not checked in to your repo. It is recommended to store it in an environment variable, and you can generate it with bundle exec rake secret.

Override the method in your model in order to send direct OTP codes. This is automatically called when a user logs in unless they have TOTP enabled (see below):

def send_two_factor_authentication_code(code)
  # Send code via SMS, etc.
end

Customisation and Usage

By default, second factor authentication is required for each user. You can change that by overriding the following method in your model:

def need_two_factor_authentication?(request)
  request.ip != '127.0.0.1'
end

In the example above, two factor authentication will not be required for local users.

This gem is compatible with Google Authenticator. To enable this a shared secret must be generated by invoking the following method on your model:

user.generate_totp_secret

This must then be shared via a provisioning uri:

user.provisioning_uri # This assumes a user model with an email attribute

This provisioning uri can then be turned in to a QR code if desired so that users may add the app to Google Authenticator easily. Once this is done, they may retrieve a one-time password directly from the Google Authenticator app.

Overriding the view

The default view that shows the form can be overridden by adding a file named show.html.erb (or show.html.haml if you prefer HAML) inside app/views/devise/two_factor_authentication/ and customizing it. Below is an example using ERB:

<h2>Hi, you received a code by email, please enter it below, thanks!</h2>

<%= form_tag([resource_name, :two_factor_authentication], :method => :put) do %>
  <%= text_field_tag :code %>
  <%= submit_tag "Log in!" %>
<% end %>

<%= link_to "Sign out", destroy_user_session_path, :method => :delete %>

Upgrading from version 1.X to 2.X

The following database fields are new in version 2.

  • direct_otp
  • direct_otp_sent_at
  • totp_timestamp

To add them, generate a migration such as:

$ rails g migration AddTwoFactorFieldsToUsers direct_otp:string direct_otp_sent_at:datetime totp_timestamp:timestamp

The otp_secret_key is only required for users who use TOTP (Google Authenticator) codes, so unless it has been shared with the user it should be set to nil. The following pseudo-code is an example of how this might be done:

User.find_each do |user| do
  if !uses_authenticator_app(user)
    user.otp_secret_key = nil
    user.save!
  end
end

Adding the TOTP encryption option to an existing app

If you've already been using this gem, and want to start encrypting the OTP secret key in the database (recommended), you'll need to perform the following steps:

  1. Generate a migration to add the necessary columns to your model's table:

    rails g migration AddEncryptionFieldsToUsers encrypted_otp_secret_key:string:index encrypted_otp_secret_key_iv:string encrypted_otp_secret_key_salt:string
    

    Open your migration file (it will be in the db/migrate directory and will be named something like 20151230163930_add_encryption_fields_to_users.rb), and add unique: true to the add_index line so that it looks like this:

    add_index :users, :encrypted_otp_secret_key, unique: true

    Save the file.

  2. Run the migration: bundle exec rake db:migrate

  3. Update the gem: bundle update two_factor_authentication

  4. Add encrypted: true to has_one_time_password in your model. For example: has_one_time_password(encrypted: true)

  5. Generate a migration to populate the new encryption fields:

    rails g migration PopulateEncryptedOtpFields
    

    Open the generated file, and replace its contents with the following:

    class PopulateEncryptedOtpFields < ActiveRecord::Migration
      def up
        User.reset_column_information
    
        User.find_each do |user|
          user.otp_secret_key = user.read_attribute('otp_secret_key')
          user.save!
        end
      end
    
      def down
        User.reset_column_information
    
        User.find_each do |user|
          user.otp_secret_key = ROTP::Base32.random_base32
          user.save!
        end
      end
    end
  6. Generate a migration to remove the :otp_secret_key column:

    rails g migration RemoveOtpSecretKeyFromUsers otp_secret_key:string
    
  7. Run the migrations: bundle exec rake db:migrate

If, for some reason, you want to switch back to the old non-encrypted version, use these steps:

  1. Remove (encrypted: true) from has_one_time_password

  2. Roll back the last 3 migrations (assuming you haven't added any new ones after them):

    bundle exec rake db:rollback STEP=3
    

Critical Security Note! Add before_action to your user registration controllers

You should have a file registrations_controller.rb in your controllers folder to overwrite/customize user registrations. It should include the lines below, for 2FA protection of user model updates, meaning that users can only access the users/edit page after confirming 2FA fully, not simply by logging in. Otherwise the entire 2FA system can be bypassed!

class RegistrationsController < Devise::RegistrationsController
  before_action :confirm_two_factor_authenticated, except: [:new, :create, :cancel]

  protected

  def confirm_two_factor_authenticated
    return if is_fully_authenticated?

    flash[:error] = t('devise.errors.messages.user_not_authenticated')
    redirect_to user_two_factor_authentication_url
  end
end

Critical Security Note! Add 2FA validation to your custom user actions

Make sure you are passing the 2FA secret codes securely and checking for them upon critical user actions, such as API key updates, user email or pgp pubkey updates, or any other changess to private/secure account-related details. Validate the secret during the initial 2FA key/secret verification by the user also, of course.

For example, a simple account_controller.rb may look something like this:

require 'json'

class AccountController < ApplicationController
  before_action :require_signed_in!
  before_action :authenticate_user!
  respond_to :html, :json
  
  def account_API
    resp = {}
    begin       
      if(account_params["twoFAKey"] && account_params["twoFASecret"])
        current_user.otp_secret_key = account_params["twoFAKey"]
        if(current_user.authenticate_totp(account_params["twoFASecret"]))
          # user has validated their temporary 2FA code, save it to their account, enable 2FA on this account
          current_user.save!
          resp['success'] = "passed 2FA validation!"
        else
          resp['error'] = "failed 2FA validation!"
        end
      elsif(param[:userAccountStuff] && param[:userAccountWidget])
        #before updating important user account stuff and widgets,
        #check to see that the 2FA secret has also been passed in, and verify it...
        if(account_params["twoFASecret"] && current_user.totp_enabled? && current_user.authenticate_totp(account_params["twoFASecret"]))
          # user has passed 2FA checks, do cool user account stuff here
          ...
        else 
          # user failed 2FA check! No cool user stuff happens!             
           resp[error] = 'You failed 2FA validation!'
        end
        
          ...
        end
      else
        resp['error'] = 'unknown format error, not saved!'  
      end
    rescue Exception => e
      puts "WARNING: account api threw error : '#{e}' for user #{current_user.username}"
      #print "error trace: #{e.backtrace}\n"
      resp['error'] = "unanticipated server response"
    end
    render json: resp.to_json
  end

  def account_params
    params.require(:twoFA).permit(:userAccountStuff, :userAcountWidget, :twoFAKey, :twoFASecret)
  end
end   

Example App

TwoFactorAuthenticationExample

Example user actions

to use an ENV VAR for the 2FA encryption key:

config.otp_secret_encryption_key = ENV['OTP_SECRET_ENCRYPTION_KEY']

to set up TOTP for Google Authenticator for user:

current_user.otp_secret_key =  current_user.generate_totp_secret
current_user.save!

( encrypted db fields are set upon user model save action, rails c access relies on setting env var: OTP_SECRET_ENCRYPTION_KEY )

to check if user has input the correct code (from the QR display page) before saving the user model:

current_user.authenticate_totp('123456')

additional note:

current_user.otp_secret_key

This returns the OTP secret key in plaintext for the user (if you have set the env var) in the console the string used for generating the QR given to the user for their Google Auth is something like:

otpauth://totp/LABEL?secret=p6wwetjnkjnrcmpd (example secret used here)

where LABEL should be something like "example.com (Username)", which shows up in their GA app to remind them the code is for example.com

this returns true or false with an allowed_otp_drift_seconds 'grace period'

to set TOTP to DISABLED for a user account:

current_user.second_factor_attempts_count=nil
current_user.encrypted_otp_secret_key=nil
current_user.encrypted_otp_secret_key_iv=nil
current_user.encrypted_otp_secret_key_salt=nil
current_user.direct_otp=nil
current_user.direct_otp_sent_at=nil
current_user.totp_timestamp=nil
current_user.direct_otp=nil
current_user.otp_secret_key=nil
current_user.save! (if in ruby code instead of console)
current_user.direct_otp? => false
current_user.totp_enabled? => false

two_factor_authentication's People

Contributors

amoose avatar apoyan avatar boffbowsh avatar carvil avatar daveriess avatar edg3r avatar gaurish avatar gitter-badger avatar gkopylov avatar houdini avatar johnmichaelbradley avatar jskirst avatar kevinrob avatar laykou avatar leanucci avatar marclennox avatar markfchavez avatar mattmueller avatar monfresh avatar msx2 avatar poctek avatar rmm5t avatar rossta avatar rud avatar sbc100 avatar shaunakpp avatar surzhko avatar tectract avatar wkrsz avatar znow avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

two_factor_authentication's Issues

Versioning

It would appear that the changes added in 1.1.5 are a little more significant than a patch level change. Support for ruby 1.9.3 is dropped and the addition of the Encryptor gem will break functionality for any anyone who has a custom Encryptor lib, such as myself. What was the reasoning for not making this a 1.2 release? It seems like it might have been better suited for a 1.2 as existing functionality breaks with the changes 1.1.5, which should not be the case with patch level changes.

NoMethodError in Devise::TwoFactorAuthenticationController#update

Hi,

I'm receiving this error message when I try to test entering a wrong code, any thoughts?

NoMethodError in Devise::TwoFactorAuthenticationController#update
undefined method `find_message' for #Devise::TwoFactorAuthenticationController:0x007f8f0e051eb0

Or is it possible to generate the controller or views and modify them like Devise does?

Thanks,

rails g two_factor_authentication MODEL does not append .rb to end of migration

Hi there,

I noticed today that the command:

bundle exec rails g two_factor_authentication MODEL

Does not seem to end the migtation in the .rb extension meaning that I had to add this manually before rake db:migrate would run the migration.

This is on Rails 4.1.5 and Devise 3.3.0

Not a huge issue I wouldn't suspect but you can replicate this in a new rails project by running the following commands:

rails new twofa
cd twofa
# Add 'devise' and 'two_factor_authentication' to Gemfile
bundle install
rails generate devise:install
rails generate devise User
rails g two_factor_authentication User

=>

โžœ  twofa  bundle exec rails g two_factor_authentication User
      insert  app/models/user.rb
      invoke  active_record
      create    db/migrate/20140922160755_two_factor_authentication_add_to_users

How to allow users to turn two-factor on and off?

In the docs, there's a method:

   def need_two_factor_authentication?(request)
      request.ip != '127.0.0.1'
   end

However, I can't seem to access the current user from here. How do I check for a boolean (two_factor_auth) on the user model to see if they have two-factor auth enabled? Do I just use .self? I inspected the request variable and it's massive, but maybe there's something in there?

Thanks for your time!

gem?

has this been pushed as a gem?

Backport Rails 5 compatiblity

Is there any way you could backport this commit: fd7e18c to 1.x? 2.x seems to have been in progress for a while and it'd be nice to be Rails 5 compatible in the meantime.

provisioning_uri called with no otp_secret_key set

I used this post as a guide for implementing this gem into an existing app that uses Devise and I'm getting the error from the title when I update a user to enable two factor auth, specifically on the user.provisioning_uri line of the following helper:

def google_authenticator_qrcode(user)
    data = user.provisioning_uri
    data = Rack::Utils.escape(data)
    url = "https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=#{data}"
    return image_tag(url, :alt => 'Google Authenticator QRCode')
  end

Which is called from /users/registrations/confirm_two_factor_authentication.html.erb:
<%= google_authenticator_qrcode(resource) %>

Which the user is redirected to via my overridden after_update_path_for(resource).

Do I need to do anything specific to set the encrypted_otp_secret_x columns for the existing users?

TIA

support for Mongoid 5

Logs

rails g two_factor_authentication User --trace
insert app/models/user.rb
error mongoid [not found]

How to handle when users are recoverable

When I add recoverable to my user model and try to have a user go through the Devise password recovery workflow, the after_authentication callback is never executed. It seems like this is how Devise decides to handle the login callbacks. But that means that when I recover a user password, the user does not get the code.

This is very similar to my situation, but in more generic terms:

https://groups.google.com/forum/#!topic/plataformatec-devise/30oxTAWHdSs

Any thoughts?

Old OTP can be used after a new one has been generated

Steps to reproduce:

  1. User signs in, and is prompted to enter the OTP they were sent
  2. User requests a new OTP to be sent
  3. User receives new OTP that is different from the one in step 1
  4. User enters OTP from step 1

Expected Result: OTP should no longer be valid since a new one was generated. There should only ever be one valid OTP at any given time

Actual Result: The old OTP is considered valid and the user is able to fully authenticate

Google Auth Not Working For You Too?

I'm having difficulty with Google Auth for some users. SMS codes work fine, but integrating the gem with Google Auth, not so much. I've attempted it with the bar code and manual entry, but neither work. Has anyone else had this issue. I've also tried syncing the time. Still no go.

2FA for specific Controllers / Actions

I am trying to improve my user experience by only asking for the 2nd factor for specific controlls / actions. Could someone provide me an idea on how to get this done?

devise - wrong number of arguments (1 for 0)

I followed the read me of this gem. Installed gem, generated it and made sure :two_factor_authenticatable is added in my user model. Not sure why I am getting the devise wrong number of arguments error only when :two_factor_authenticatable is added to user model. Could you please guide me on this.

send_two_factor_authentication_code is never called

I'm using Twilio to send the OTP code to the user's phone. So I wrote my own send_two_factor_authentication_code method, but it doesn't get called. This is what I have in my user.rb file:

  def send_two_factor_authentication_code(code)

    # put your own credentials here
    account_sid = ENV['TWILIO_SID']
    auth_token = ENV['TWILIO_TOKEN']
    number = "+1" + current_user.profile.phone
    # set up a client to talk to the Twilio REST API
    @client = Twilio::REST::Client.new account_sid, auth_token

    @client.messages.create(
    from: '+1xxx3607217',
    to: number,
    body: code
    )
    logger.info "Code sent."
  end

Everything else seems to be working. I get to the page where you're asked to enter the code, but this one method simply is not called, so, no code is sent. I have Twilio working in other parts of my app, so I know that's not the problem.

Configuration

Hello,

These configurations, should they go to rails application.rb environment.rb or devise.rb

config.max_login_attempts = 3  # Maximum second factor attempts count
config.allowed_otp_drift_seconds = 30  # Allowed time drift
config.otp_length = 6  # OTP code length
config.remember_otp_session_for_seconds = 30.days  # Time before browser has to enter OTP code again

Wordlists

Hi

Great plugin, thanks.

Just one comment, it might be worth including in your readme that you need a relevant wordlist on your computer.

Otherwise the code generation fails with no error, no emails etc.

Just had this issue going from dev to pro.

Finally, working through things in the console, I found

 User.first.generate_two_factor_code
 RuntimeError: Words file not found. Check if it is installed in (/usr/share/dict/words or /usr/dict/words) 

Which was easily fixed installing the list via apt:

 apt-get install wbritish (or whichever you require)

send_two_factor_authentication_code "wrong number of arguments (0 for 1)"

I'm getting this error as if the code isn't being passed to the method:

Completed 500 Internal Server Error in 193ms (ActiveRecord: 5.9ms)
ArgumentError (wrong number of arguments (0 for 1)):

->  def send_two_factor_authentication_code(code)

app/models/user.rb:124:in `send_two_factor_authentication_code'

Any ideas what might cause this?

attr_encrypted gem is not working with two_factor_authentication gem

Hi

I am already using attr_encrypted gem. I want to use two_factor_authentication gem.

But I get the following error whenever user is saved:

ArgumentError - wrong number of arguments (2 for 1):
attr_encrypted (1.3.4) lib/attr_encrypted.rb:143:in `block (2 levels) in attr_encrypted'

Is two_factor_authentication gem supposed to work well with 'attr_encrypted', '~> 1.3.4' gem?

Override views

Hi

Is it possible to override the views that this gem provides?

I want to change the text to another language, since the site it is used at is not in english.

Regards

Security enhancements

Just to mention that there are two good practices that are missing in this library. Worth considering their implementation to improve security.

First one is required by RFC 6238 - authentication code once accepted must not be accepted twice (i.e. again). This requires additional field in DB.

Second one is a best practice, also recommended by RFC 6238 - encrypt security key in DB. The encryption key must not be stored in DB. One-way hash functions like scrypt cannot be used for TOTP, but simple encryption would suffice.

(Also note there is by mistake used term "authorisation" in README.)

Having trouble with non-devise controllers

Hi, thanks for the awesome lib. This strikes me as a very noob question, but after diving into your code and checking many options I'm filing an issue as a last resource.

The problem I'm having is that some non-devise controllers are passing through the two-factor flow even though they shouldn't. Here's my setup:

head -1 app/controllers/api/base_controller.rb:
class Api::BaseController < ActionController::Base

head -1 app/controllers/application_controller.rb 
class ApplicationController < ActionController::Base

When a test is ran through RSpec it fails with the following stack trace:

ERROR -- : undefined method `authenticate?' for nil:NilClass (NoMethodError)
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/devise-3.4.1/lib/devise/controllers/sign_in_out.rb:10:in `block in signed_in?'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/devise-3.4.1/lib/devise/controllers/sign_in_out.rb:9:in `each'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/devise-3.4.1/lib/devise/controllers/sign_in_out.rb:9:in `any?'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/devise-3.4.1/lib/devise/controllers/sign_in_out.rb:9:in `signed_in?'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/two_factor_authentication-1.1.5/lib/two_factor_authentication/controllers/helpers.rb:15:in `block in handle_two_factor_authentication'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/two_factor_authentication-1.1.5/lib/two_factor_authentication/controllers/helpers.rb:14:in `each'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/two_factor_authentication-1.1.5/lib/two_factor_authentication/controllers/helpers.rb:14:in `any?'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/two_factor_authentication-1.1.5/lib/two_factor_authentication/controllers/helpers.rb:14:in `handle_two_factor_authentication'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/activesupport-4.1.12/lib/active_support/callbacks.rb:429:in `block in make_lambda'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/activesupport-4.1.12/lib/active_support/callbacks.rb:161:in `call'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/activesupport-4.1.12/lib/active_support/callbacks.rb:161:in `block in halting'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/activesupport-4.1.12/lib/active_support/callbacks.rb:501:in `call'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/activesupport-4.1.12/lib/active_support/callbacks.rb:501:in `block in call'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/activesupport-4.1.12/lib/active_support/callbacks.rb:501:in `each'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/activesupport-4.1.12/lib/active_support/callbacks.rb:501:in `call'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/activesupport-4.1.12/lib/active_support/callbacks.rb:86:in `run_callbacks'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/actionpack-4.1.12/lib/abstract_controller/callbacks.rb:19:in `process_action'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/actionpack-4.1.12/lib/action_controller/metal/rescue.rb:29:in `process_action'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/actionpack-4.1.12/lib/action_controller/metal/instrumentation.rb:32:in `block in process_action'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/activesupport-4.1.12/lib/active_support/notifications.rb:159:in `block in instrument'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/activesupport-4.1.12/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/activesupport-4.1.12/lib/active_support/notifications.rb:159:in `instrument'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/actionpack-4.1.12/lib/action_controller/metal/instrumentation.rb:30:in `process_action'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/actionpack-4.1.12/lib/action_controller/metal/params_wrapper.rb:250:in `process_action'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/activerecord-4.1.12/lib/active_record/railties/controller_runtime.rb:18:in `process_action'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/actionpack-4.1.12/lib/abstract_controller/base.rb:136:in `process'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/actionview-4.1.12/lib/action_view/rendering.rb:30:in `process'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/actionpack-4.1.12/lib/action_controller/test_case.rb:595:in `process'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/actionpack-4.1.12/lib/action_controller/test_case.rb:64:in `process'
.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/actionpack-4.1.12/lib/action_controller/test_case.rb:495:in `get'
dev/spec/controllers/api_controller_spec.rb:98:in `block (3 levels) in <top (required)>'

The test goes something like this:

require 'rails_helper'
require 'factory_girl_rails'
require 'securerandom'

RSpec.describe Api::LoginController, :type => :controller do
  context "login" do
    it "logs in" do
      post :authenticate, {username: "...", password: "..."}
      expect(response.status).to eq(200)
    end
  end
end

The reason is that the condition unless devise_controller? in handle_two_factor_authentication method is positive, so it enters the flow.

Am I doing something wrong?
How can I bypass the two-factor validation for non-devise controllers?

Thanks in advance.

Options specific to resource

Hello. Is a way to apply two_factor_authentication configurations specific to resource type?

I'm trying to give different values to remember_otp_session_for_seconds for admin resource and user.

Enable Option

Maybe there should be an option in the accounts view where users can enable two factor IF they want to.

Basically I would function something like the authy gem. https://github.com/authy/authy-devise There are some views create, verify, disable and 2fa. Basically if the user wants to enable 2fa to their account, they go to the create view. There they can ask for a SMS or (google auth) QR code. Then they press a button: Send SMS or GA, which takes them to the next page, verify. There, they can input the SMS code or the GA code. That enables 2fa to the account. The next time the person logs in, they are forwarded to the 2fa view, where they are able to put a GA code or ask for a SMS. Then finally, if they go to disable, they can ask for a SMS or put in a GA code to disable 2fa for the account.

The names of views should be changeable.

This would be a REALLY great addition to the plugin!

Skip 2FA on specific scenario

Hi,

Is there a way, to skip the 2FA if a user, in example has a specific role or other stuff?

So, I dont want to force 2FA when logging in, when the user is "Admin".

Thanks in advance

Regards

Update

Hey! Does this work with the most recent version of Devise and rails?

Allowing only one page without 2fa authentication

Hi,
I need to allow my users to update a setting even without entering 2fa code ....

i need to allow my users to view or perform certain actions without providing 2fa code ... Could you please provide a possible solution ?

uninitialized constant Devise::VERSION

Hi,

first thanks for making this great gem :)

After resetting cookies.signed[TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME] the Devise::TwoFactorAuthenticationController#update produces the exception uninitialized constant Devise::VERSION in line 33 if Devise::VERSION.to_f >= 4.2. I'm using Rails 5 and Devise 4.2.0.

Trace:

/bundle/bundler/gems/two_factor_authentication-40fb11b069ddcontrollers/devise/two_factor_authentication_controller.rb:33:in `after_two_factor_success_for'
/bundle/bundler/gems/two_factor_authentication-40fb11b069ddcontrollers/devise/two_factor_authentication_controller.rb:14:in `update'
actionpack (5.0.0) lib/action_controller/metal/basic_implicit_render.rb:4:in `send_action'
actionpack (5.0.0) lib/abstract_controller/base.rb:188:in `process_action'
actionpack (5.0.0) lib/action_controller/metal/rendering.rb:30:in `process_action'
actionpack (5.0.0) lib/abstract_controller/callbacks.rb:20:in `block in process_action'
...

PR with fix: #115

Application previously using this gem has several issues trying to upgrade

  • The migration barfed when a migration already existed with the same class name.
    -- Suggest adding Otp to the migration class name.
  • The migration assumed second_factor_attempts_count does not exist. This is an issue for apps that already have this column name from previous use of this gem.
  • Ability to define "login_code_random_pattern" seems to have gone away even though docs still state ability to control login pattern.
  • Table "valid_mfa_tokens" no longer is necessary (from 0.2 code).
  • Putting has_one_time_password before "devise :two_factor_authenticatable" causes start up errors
    -- Using Thin 1.6.2; Rails 4.0.4
  • Documentation still mentions ":second_factor_pass_code" (README.md)
  • When trying to get access to the otp_code in the model's send_two_factor_authentication_code implementation method, the following gets thrown.
    -- undefined method `scan' for nil:NilClass

Timestamp naming conventions: totp_timestamp vs last_otp_at

I'd like to suggest a more consistent naming convention to replace the new totp_timestamp column in preparation for v2.0. A better name would be something like last_otp_at. This would better match Rails naming conventions, it would better coincide with direct_otp_sent_at, and this would match rotp's recommendation.

I'm happy to provide a pull-request for this, but I'd like to get some sense that it's likely to be accepted first and/or if it interests others that use this library.

Do we have any two_factor_method like authenticate_user!

I need to to authenticate a user is two-step authenticated or not.
As in simple devise we had a method like
authenticate_user!

I need to set this in before_action filter.
Can we have similar method for two-factor-authentication?

Rails 5, Devise 4.2.0 "undefined method" errors from config in devise.rb

I'm having an issue with 3 config options in my app. The following 3 config options cause "undefined method..." errors:

config.direct_otp_valid_for
config.direct_otp_length
config.second_factor_resource_id

Commenting out those three lines in my devise.rb initializer file allows the app to start, but I'm not sure if this breaks anything. Will look into it further.

Here's the full error message I get when starting the server:

=> Booting Puma
=> Rails 5.0.0.1 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
Exiting
/Users/dude/Work/myapp/config/initializers/devise.rb:338:in `block in <top (required)>': undefined method `second_factor_resource_id=' for Devise:Module (NoMethodError)
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/devise-4.2.0/lib/devise.rb:292:in `setup'
    from /Users/dude/Work/myapp/config/initializers/devise.rb:3:in `<top (required)>'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/activesupport-5.0.0.1/lib/active_support/dependencies.rb:287:in `load'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/activesupport-5.0.0.1/lib/active_support/dependencies.rb:287:in `block in load'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/activesupport-5.0.0.1/lib/active_support/dependencies.rb:259:in `load_dependency'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/activesupport-5.0.0.1/lib/active_support/dependencies.rb:287:in `load'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/engine.rb:648:in `block in load_config_initializer'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/activesupport-5.0.0.1/lib/active_support/notifications.rb:166:in `instrument'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/engine.rb:647:in `load_config_initializer'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/engine.rb:612:in `block (2 levels) in <class:Engine>'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/engine.rb:611:in `each'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/engine.rb:611:in `block in <class:Engine>'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/initializable.rb:30:in `instance_exec'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/initializable.rb:30:in `run'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/initializable.rb:55:in `block in run_initializers'
    from /Users/dude/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:226:in `block in tsort_each'
    from /Users/dude/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:348:in `block (2 levels) in each_strongly_connected_component'
    from /Users/dude/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:420:in `block (2 levels) in each_strongly_connected_component_from'
    from /Users/dude/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:429:in `each_strongly_connected_component_from'
    from /Users/dude/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:419:in `block in each_strongly_connected_component_from'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/initializable.rb:44:in `each'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/initializable.rb:44:in `tsort_each_child'
    from /Users/dude/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:413:in `call'
    from /Users/dude/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:413:in `each_strongly_connected_component_from'
    from /Users/dude/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:347:in `block in each_strongly_connected_component'
    from /Users/dude/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:345:in `each'
    from /Users/dude/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:345:in `call'
    from /Users/dude/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:345:in `each_strongly_connected_component'
    from /Users/dude/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:224:in `tsort_each'
    from /Users/dude/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:203:in `tsort_each'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/initializable.rb:54:in `run_initializers'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/application.rb:352:in `initialize!'
    from /Users/dude/Work/myapp/config/environment.rb:5:in `<top (required)>'
    from /Users/dude/Work/myapp/config.ru:3:in `require_relative'
    from /Users/dude/Work/myapp/config.ru:3:in `block in <main>'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/rack-2.0.1/lib/rack/builder.rb:55:in `instance_eval'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/rack-2.0.1/lib/rack/builder.rb:55:in `initialize'
    from /Users/dude/Work/myapp/config.ru:in `new'
    from /Users/dude/Work/myapp/config.ru:in `<main>'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/rack-2.0.1/lib/rack/builder.rb:49:in `eval'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/rack-2.0.1/lib/rack/builder.rb:49:in `new_from_string'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/rack-2.0.1/lib/rack/builder.rb:40:in `parse_file'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/rack-2.0.1/lib/rack/server.rb:318:in `build_app_and_options_from_config'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/rack-2.0.1/lib/rack/server.rb:218:in `app'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/commands/server.rb:59:in `app'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/rack-2.0.1/lib/rack/server.rb:353:in `wrapped_app'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/commands/server.rb:124:in `log_to_stdout'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/commands/server.rb:77:in `start'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/commands/commands_tasks.rb:90:in `block in server'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/commands/commands_tasks.rb:85:in `tap'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/commands/commands_tasks.rb:85:in `server'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/commands/commands_tasks.rb:49:in `run_command!'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/railties-5.0.0.1/lib/rails/commands.rb:18:in `<top (required)>'
    from /Users/dude/Work/myapp/bin/rails:9:in `require'
    from /Users/dude/Work/myapp/bin/rails:9:in `<top (required)>'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/spring-1.7.2/lib/spring/client/rails.rb:28:in `load'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/spring-1.7.2/lib/spring/client/rails.rb:28:in `call'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/spring-1.7.2/lib/spring/client/command.rb:7:in `call'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/spring-1.7.2/lib/spring/client.rb:30:in `run'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/spring-1.7.2/bin/spring:49:in `<top (required)>'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/spring-1.7.2/lib/spring/binstub.rb:11:in `load'
    from /Users/dude/.rvm/gems/ruby-2.2.2/gems/spring-1.7.2/lib/spring/binstub.rb:11:in `<top (required)>'
    from /Users/dude/Work/myapp/bin/spring:13:in `require'
    from /Users/dude/Work/myapp/bin/spring:13:in `<top (required)>'
    from bin/rails:3:in `load'
    from bin/rails:3:in `<main>'

Any ideas? Or advice?

Thanks for your time!

How should I integrate Devise two factor authentication with custom sessions controller?

I integrated the Houdini two factor authentication with existing rails application.
First I doesn't have any custom sessions_controller then the two_factor_authentication is working fine.
But whereas I written custom create action in Sessions Controller then it doesn't authenticated by two_factor_authentication.

Here is the custom code for create action of Sessions Controller.

  if status_response.nil?
    render :file => 'public/api_not_found.html', :status => :not_found, :layout => false
  else
    if status_response['code'].to_i == 1
      signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
      flash[:alert] = 'Invalid Email ID or password.'
      yield if block_given?
      respond_to_on_destroy
    else
      self.resource = warden.authenticate!(auth_options)
      set_flash_message(:notice, :signed_in) if is_flashing_format?
      sign_in(resource_name, resource)
      yield resource if block_given?
      respond_with resource, location: after_sign_in_path_for(resource)
    end
  end

Model code of user.rb:

  devise :two_factor_authenticatable, :database_authenticatable, :registerable,
     :recoverable, :rememberable, :trackable, :validatable,
     :confirmable, password_length: 8..30
  has_one_time_password


  def send_two_factor_authentication_code
    puts ">>>>>>>>>>>>>>> otp_secret_key: #{otp_secret_key}, otp_code: #{otp_code}"
  end

I know if we are customizing the create action of Sessions Controller then we should call the two_factor_authentication. I tried to invoke this in controller but it throws an error.

So my question how should i integrate the two_factor_authentication with custom create action of Session Controller?

encryptor 3.0.0 release with security fix

Right now the gemspec does not lock encryptor down to 2.0.0 and the encryptor gem recently had a release to 3.0.0 because of a security issue. The fix in 3.0.0 seems to break this gem. Here is the text that the encryptor gem has up on their README:

A bug was discovered in Encryptor 2.0.0 wherein the IV was not being used when using an AES--GCM algorithm. Unfornately fixing this major security issue results in the inability to decrypt records encrypted using an AES--GCM algorithm from Encryptor v2.0.0. While the behavior change is minimal between v2.0.0 and v3.0.0, the change has a significant impact on users that used v2.0.0 and encrypted data using an AES--GCM algorithm, which is the default algorithm for v2.0.0. Consequently, we decided to increment the version with a major bump to help people avoid a confusing situation where some of their data will not decrypt. A new option is available in Encryptor 3.0.0 that allows decryption of data encrypted using an AES--GCM algorithm from Encryptor v2.0.0.

It appears this gem is affected by this issue, because even though it specifies 'aes-256-cbc' when getting an iv, it does not specify an algorithm when calling encrypt or decrypt.

Any ideas on the best way around this at this point?

Code is sent to locked users

Hello and thank you for this gem!

If a user makes too many login attempts, goes back to the login page and logs in again, send_two_factor_authentication_code is still called, so the user gets a code although he can't enter it.
This can be easily avoided in send_two_factor_authentication_code but I think it shouldn't be called at all.

I created a test case and offer a solution here.
I sadly can't make a PR as I already forked a fork of this repository (...), and that means I can't fork the root repository.

ActiveAdmin Integration

Just wondering what would be the best way to hook this into activeadmin for an admin user?

NoMethodError (undefined method `scan' for nil:NilClass)

Hi

Im running my app in production, and getting this error:

NoMethodError (undefined method `scan' for nil:NilClass):
  app/models/user.rb:39:in `send_two_factor_authentication_code'

My method in the user model is:

def send_two_factor_authentication_code
    # use Model#otp_code and send via SMS, etc.
    #puts ">>>>>>>>>>>>>>>>>>>>>>>> otp_secret_key: #{otp_secret_key}, otp_code: #{otp_code}"
    UserMailer.one_time_password(self).deliver
  end
def one_time_password(user)
    @user = user
    mail :to => user.email, :subject => "Engangskodeord"
  end

This only occurs on my production server, not on my Macbook in production or anywhere else.

Update gem

Looks like the changes introduced here, to allow scoped views, was not released with the gem update.

Allow `Warden::Manager.after_authentication` to be customizable

Thanks for this gem! In our app, we need to be able to perform certain actions after sign in, but before the OTP is sent to the User. But because the Warden::Manager.after_authentication block is hardcoded in the gem, we can't customize it.

I propose moving that block into a separate initializer that can be placed in the app's config/initializers directory via the generator. That way, it can easily be customized if desired.

Would you be open to a pull request that does this? Or do you have thoughts about other ways to solve this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.