ActiveModel::Otp makes adding Two Factor Authentication (TFA) to a model simple. Let's see what's required to get AMo::Otp working in our Application, using Rails 4.0 (AMo::Otp is also compatible with Rails 3.x versions). We're going to use a User model and some authentication to do it. Inspired by AM::SecurePassword
Add this line to your application's Gemfile:
gem 'active_model_otp'
And then execute:
$ bundle
Or install it yourself as follows:
$ gem install active_model_otp
We're going to add a field to our User
Model, so each user can have an otp secret key. The next step is to run the migration generator in order to add the secret key field.
rails g migration AddOtpSecretKeyToUsers otp_secret_key:string
=>
invoke active_record
create db/migrate/20130707010931_add_otp_secret_key_to_users.rb
We’ll then need to run rake db:migrate to update the users table in the database. The next step is to update the model code. We need to use has_one_time_password to make it use TFA.
class User < ActiveRecord::Base
has_one_time_password
end
Note: If you're adding this to an existing user model you'll need to generate otp_secret_key with a migration like:
User.find_each { |user| user.update_attribute(:otp_secret_key, ROTP::Base32.random_base32) }
To use a custom column to store the secret key field you can use the column_name option. It is also possible to generate codes with a specified length.
class User < ActiveRecord::Base
has_one_time_password column_name: :my_otp_secret_column, length: 4
end
The has_one_time_password statement provides to the model some useful methods in order to implement our TFA system. AMo:Otp generates one time passwords according to TOTP RFC 6238 and the HOTP RFC 4226. This is compatible with Google Authenticator apps available for Android and iPhone, and now in use on GMail.
The otp_secret_key is saved automatically when an object is created,
user = User.create(email: "hello@heapsource.com")
user.otp_secret_key
=> "jt3gdd2qm6su5iqh"
Note: You can fork the applications for iPhone & Android and customize them
user.otp_code # => '186522'
sleep 30
user.otp_code # => '850738'
# Override current time
user.otp_code(time: Time.now + 3600) # => '317438'
# Custom OTP interval
user.otp_code(interval: 1.minute)
user.authenticate_otp('186522') # => true
sleep 30 # let's wait 30 secs
user.authenticate_otp('186522') # => false
user.authenticate_otp('186522') # => true
sleep 30 # lets wait again
user.authenticate_otp('186522', drift: 60) # => true
user.authenticate_otp('186522', interval: 1.minute) # => true
sleep 59.seconds
user.authenticate_otp('186522', interval: 1.minute) # => true
An additonal counter field is required in our User
Model
rails g migration AddCounterForOtpToUsers otp_counter:integer
=>
invoke active_record
create db/migrate/20130707010931_add_counter_for_otp_to_users.rb
Set default value for otp_counter to 0.
change_column :users, :otp_counter, :integer, default: 0
In addition set the counter flag option to true
class User < ActiveRecord::Base
has_one_time_password counter_based: true
end
And for a custom counter column
class User < ActiveRecord::Base
has_one_time_password counter_based: true, counter_column_name: :my_otp_secret_counter_column
end
Authentication is done the same. You can manually adjust the counter for your usage or set auto_increment on success to true.
user.authenticate_otp('186522') # => true
user.authenticate_otp('186522', auto_increment: true) # => true
user.authenticate_otp('186522') # => false
user.otp_counter -= 1
user.authenticate_otp('186522') # => true
When retrieving an otp_code
you can also pass the auto_increment
option.
user.otp_code # => '186522'
user.otp_code # => '186522'
user.otp_code(auto_increment: true) # => '768273'
user.otp_code(auto_increment: true) # => '002811'
user.otp_code # => '002811'
The library works with the Google Authenticator iPhone and Android app, and also includes the ability to generate provisioning URI's to use with the QR Code scanner built into the app.
# Use your user's email address to generate the provisioning_url
user.provisioning_uri # => 'otpauth://totp/hello@heapsource.com?secret=2z6hxkdwi3uvrnpn'
# Use a custom field to generate the provisioning_url
user.provisioning_uri("hello") # => 'otpauth://totp/hello?secret=2z6hxkdwi3uvrnpn'
# You can customize the generated url, by passing a hash of Options
# `:issuer` lets you set the Issuer name in Google Authenticator, so it doesn't show as a blank entry.
user.provisioning_uri(nil, issuer: 'MYAPP') #=> 'otpauth://totp/hello@heapsource.com?secret=2z6hxkdwi3uvrnpn&issuer=MYAPP'
This can then be rendered as a QR Code which can be scanned and added to the users list of OTP credentials.
Scan the following barcode with your phone, using Google Authenticator
Now run the following and compare the output
require "active_model_otp"
class User
extend ActiveModel::Callbacks
include ActiveModel::Validations
include ActiveModel::OneTimePassword
define_model_callbacks :create
attr_accessor :otp_secret_key, :email
has_one_time_password
end
user = User.new
user.email = 'roberto@heapsource.com'
user.otp_secret_key = "2z6hxkdwi3uvrnpn"
puts "Current code #{user.otp_code}"
Note: otp_secret_key must be generated using RFC 3548 base32 key strings (for compatilibity with google authenticator)
- Drifting Ruby Tutorial
- Generate QR code with rqrcode gem
- Generating QR Code with Google Charts API
- Sending code via SMS with Twilio
- Using with Mongoid
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request