Skip to content
Jon Phenow edited this page Nov 16, 2015 · 3 revisions

General Flow

Duo's overview is a good place to start. Here's how I'd explain it in words:

  • Login your user as you normally would and redirect to a new endpoint (like <your-app>/auth/duo)
  • In this new endpoint:
    • Using Duo-Api, sign an identifier, like a username, into what Duo calls the sig_request
    • Render a page using their iframe and js framework and fill in the sig_request param as well as any other options you need to
  • The User goes through a 2 factor handshake, upon success:
  • Handle a POST back to this same endpoint from above (can also be configured to be a different endpoint when initializing the js library)
    • In this post, use Duo-Api gem to unpack the response and verify that the identifier matches the current-user's identifier in your system

Example (Rails)

Note that this gem doesn't discriminate against things like Sinatra or vanilla services, this is just an example and should be somewhat applicable to other frameworks. Find the necessary js framework here. The bundled version doesn't require jQuery, we'll use that for this example.

# config/initializers/duo-api.rb
DuoApi.config do |config|
  config.hostname = "xxxx-duosecurity.com"
  config.integration_key = "asdf"
  config.secret_key = "asdf"
  config.app_secret = Rails.application.secret_token
end

# in routes
get "/auth/duo", :to => "DuoController#duo"
post "/auth/duo", :to => "DuoController#duo_callback"

# Controller
class DuoController < ApplicationController
  before_filter :authenticate!

  def duo
    @signed_request = DuoApi.sign(current_user.login)
  end

  def duo_callback
    if current_user.login == DuoApi.verify(params[:sig_response])
      current_user.update_attributes(:duo_verified_at => Time.now)
      redirect_to "/"
    else
      flash[:error] = "Could not authenticate you with Duo"
      redirect_to login_path
    end
  end
end
<%# duo.html.erb %>
<html>
  <head>
    <script src="/javascripts/Duo-Web-v1.bundled.min.js"></script>
    <script>
      Duo.init({
        'host': '<%= DuoApi.config.hostname %>',
        'sig_request': '<%= @signed_request %> '
      });
    </script>
  </head>
  <body>
    <iframe id="duo_iframe" frameborder="0"></iframe>
  </body>
</html>

Some of the controller code makes assumptions about what an app might have available to it. Also note that it stores a duo-verified timestamp. One could imagine using this to force-re-duo-auth after a window of time. Likely, in practice, you wouldn't want this in your regular database, and instead in a session or something.

Clone this wiki locally