Most rails applications have some sort of "user" to represent either customers who are consuming the service or administrators that are publishing content to the web application. It's important that these users are authenticated (ensuring that they are who they say they are). For user authentication in a Ruby on Rails application, Devise is one of the best solutions out there. It has a very active community and wide variety of options and extensions to fit your business model. Sometimes customers may ask for or the application requires an additional layer of authentication beyond the basic username/password combination.
Yubikey Hardware is a small usb "key" that is set up to generate unique one-time passwords that are validated against their server. Fortunately someone has written an extension to incorporate the yubikey as a second factor of authentication against devise users called yubikey_database_authenticatable.
In order to require users to log in with a yubikey there are a few large steps you have to take.
I have created a skeleton rails app with these steps and posted it to Github
1. Have a rails application
For this app I am using rails 3.2.10
$ rails new rails_with_yubikey
$ cd rails_with_yubikey/
At Ikayzo we have a fondness for lemurs so this application will maintain a list of lemurs species.
$rails g scaffold lemurs species:string description:text
Now that we have some actual pages in the applications we can set the root url of the application to the LemursController
$ rm public/index.html
root :to => 'lemurs#index'
2. Set up regular devise authentication
There is already some great documentation for this on the Devise readme but here are the steps I took to add it to this rails application.
Add devise to the global part of the Gemfile
$ bundle install
Next generate the devise files and user model
$ rails generate devise:install
$ rails generate devise User
$ rake db:migrate
The only thing left is to require users to log in when accessing the lemursController
class LemursController < ApplicationController
3. Add yubikey_database_authenticatable extension to devise
First add the yubikey_database_authenticatable gem to the applications Gemfile
$ bundle install
There are two new columns that the users table will need to authenticat witht he yubikey.
$ rails generate migration add_yubikey_to_users
class AddYubikeyToUsers < ActiveRecord::Migration
add_column :users, :useyubikey, :boolean
add_column :users, :registeredyubikey, :string
$ rake db:migrate
You will have to replace :database_authenticatable with :yubikey_database_authenticatable to the devise line of the user model.
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :yubikey_database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :timeoutable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me,
:useyubikey, :registeredyubikey, :yubiotp
There is one last change to make to the code of the project. The field for the yubikey one-time password is not in the default user session login screen for devise. You will need to generate the devise views and overwrite the new user session page.
$ rails generate devise:views
<%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<% if devise_mapping.rememberable? -%>
<%= f.check_box :remember_me %> <%= f.label :remember_me %><% end -%>
<% if devise_mapping.yubikey_database_authenticatable? -%>
<%= f.label :yubiotp, "Yubikey One Time Password" %><% end -%>
<%= f.password_field :yubiotp%>
<%= f.submit "Sign in" %><% end %>
<%= render :partial => "devise/shared/links" %>
5. Add user managment to the application for regulating and associating yubikeys with users
For this application I just created a user and added a yubikey to them through the rails console. To require a user to login with yubikey the boolean use_yubikey needs to be set to true. The other user also needs to have the registeredyubikey field set to their yubikeky. I did this through the console by copying the text output from the yubikey to a clipboard and setting the field in the console. Part of the code that was added to the user model is designed to peel of the first 11 characters of the yubikey one-time password which is its static and unique identifier.
In a real world application there will need to be features built in to managing the yubikey for the user. If the customers/users will be providing their own yubikey they will need to update these settings themselves. However if the yubikeys are distributed to a small group of users, managing these attributes should probably be hidden behind some administration side of the application.