3
votes

I'm going to add two factor auth to my Ruby app. I need really simple implementation of it with simple use cases:

  • ability to generate and send codes by sms or email (means, I don't want to be attached to Google Authenticator with this);

  • ability to show login-password form first, and then a form for the code (just like github does now).

Does not sound like a rocket science yet. But somehow I'm stuck.

So my question is: did anybody try to implement this, and if so what strategy did you use to accomplish this?


I've tried using the devise-two-factor gem which is described as "Barebones two-factor authentication with Devise".

Under the hood it authenticates user with login+password+code (all at the same time). But in my use case I want the user to enter login+password first (with form posting), then send the code to the user before the user then enters the code afterwards, on the next screen.

The only solution I found is to store login and password in a session (sic!) and use to authenticate user after he entered a 2-factor code. I'm not really feeling too confident about this strategy.

3

3 Answers

1
votes

devise-two-factor is opinionated on how your login should work. I think you're better off using the ROTP gem directly (which devise-two-factor uses) and implement a custom solution.

0
votes

Ricky from Twilio here.

We built a Two-Factor Authentication application in Ruby you could check out if you're looking for inspiration. Our application is using Authy and there are a couple related bits in the code to getting this started.

First, when you create a new user you need to register them with Authy to enable 2FA:

    @user = User.new(user_params)
    if @user.save
      session[:user_id] = @user.id

      authy = Authy::API.register_user(
        email: @user.email,
        cellphone: @user.phone_number,
        country_code: @user.country_code
      )
      @user.update(authy_id: authy.id)

      redirect_to account_path
    else
      render :new
    end

Then when a user attempts to log in you can use Authy to send a token via SMS and then verify the token they enter:

  def send_token
    @user = User.find(session[:pre_2fa_auth_user_id])
    Authy::API.request_sms(id: @user.authy_id)
    render plain: 'sending token'
  end

  def verify
    @user = User.find(session[:pre_2fa_auth_user_id])
    token = Authy::API.verify(id: @user.authy_id, token: params[:token])
    if token.ok?
      session[:user_id] = @user.id
      session[:pre_2fa_auth_user_id] = nil
      redirect_to account_path
    else
      flash.now[:danger] = "Incorrect code, please try again"
      redirect_to new_session_path
    end
  end

Hope that helps!

0
votes

Feels like it's better to answer to myself here: after all I found that gitlab way of doing this 2FA fits my needs the most: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/474

So I use some ideas from this code.

Thank you guys from gitlab, you helped me a lot!