RoseQuartz
A gem that adds two-factor authentication (time-based one-time passwords) to Devise using the rotp library.
It attempts to stay lightweight by making a lot of assumptions — for example, that
you have a single authenticatable resource, User
, and that you're using ActiveRecord
.
Highlights:
- Adds optional TOTP (compatible with Google Authenticator) to the sign-in process.
- Provides a backup code as a fallback option; resets it once it has been used and notifies the user.
- Does not tamper with the
User
model — no additional fields, no included modules. - Employs a separate table that can be updated in future without affecting your codebase and data.
- Built with Rails 5 and Devise 4 in mind.
What it does not do:
Use a multiple-page login system (email and password first, two-factor authentication token next). This introduces lots of needless complexity, which goes against the purpose of the gem.
What it should do, but does not (yet):
- Encrypt the backup code and the secret used to generate OTP.
Getting Started
First, add RoseQuartz to your Gemfile:
gem 'rose_quartz'
And run:
bundle install
Next, you need to copy initializers, locales, and add a migration:
rails g rose_quartz:install
Finally, run the migration:
rails db:migrate
Adding views
Signing in
You need a special field for one-time password/backup code on the sign-in page (app/views/devise/sessions/new.html.erb).
Here's an example:
<%# E-mail and password fields %>
<div class="field">
<%= label_tag :otp, 'Two-factor authentication token' %>
<%= text_field_tag :otp, '', autocomplete: "off" %>
</div>
<%# The rest of the form %>
Note that you must leave the parameter name (otp
) intact.
Enabling/disabling two-factor authentication
The gem adds a special extension to Devise that allows you to include two-factor authentication setup in the account editing page (app/views/devise/registrations/edit.html.erb).
As with other settings there, a password is required to toggle two-factor authentication. The user also needs to provide a correct token generated by their TOTP application of choice, which ensures that their device clock is in sync with the server.
Here's a sample implementation:
<div class="field">
<%= fields_for :two_factor_authentication do |tfa| %>
<% if two_factor_authentication_enabled? %>
<%= tfa.label :disable, 'Disable two-factor authentication' %>
<%= tfa.check_box :disable %>
<p>
Your backup code is <strong><%= two_factor_authentication_backup_code %></strong> -
save it to access your account if you ever lose your device or don't have it with you.
</p>
<%= tfa.label :reset_backup_code %>
<%= tfa.check_box :reset_backup_code %>
<% else %>
<%= tfa.hidden_field :secret, value: two_factor_authentication_secret %>
<%= image_tag two_factor_authentication_qr_code_uri(size: 200) %>
<p>
Scan this QR code with your device and enter the token below:
</p>
<%= tfa.label :token, 'Token' %><br />
<%= tfa.text_field :token, value: '' %>
<p>
Tip: to configure authentication on multiple devices, scan the code using each device.
</p>
<% end %>
<% end %>
</div>
The following helper methods are available in the view: two_factor_authentication_enabled?
,
two_factor_authentication_backup_code
, two_factor_authentication_qr_code_uri
, two_factor_authentication_secret
.