Baike.dev
All toolsOnline toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
A

active_model_otp

> 编程语言
Open source

Adds methods to set and authenticate against one time passwords (Two-Factor Authentication). Inspired in AM::SecurePassword

822 stars0 likes1 views
WebsiteGitHub

About

Adds methods to set and authenticate against one time passwords (Two-Factor Authentication). Inspired in AM::SecurePassword

ActiveModel::Otp

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 5.0 (AMo::Otp is also compatible with Rails 4.x versions). We're going to use a User model and try to add options provided by ActiveModel::Otp. Inspired by AM::SecurePassword

Dependencies

  • ROTP 6.2.0 or higher
  • Ruby 2.3 or greater

Installation

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

Setting your Model

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.

ruby
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.

ruby
class User  "jt3gdd2qm6su5iqh"

Note: You can fork the applications for iPhone & Android and customize them

Getting current code (e.g. to send via SMS)

ruby
user.otp_code # => '186522'
sleep 30
user.otp_code # => '850738'

# Override current time
user.otp_code(time: Time.now + 3600) # => '317438'

Authenticating using a code

ruby
user.authenticate_otp('186522') # => true
sleep 30 # let's wait 30 secs
user.authenticate_otp('186522') # => false

Authenticating using a slightly old code

ruby
user.authenticate_otp('186522') # => true
sleep 30 # lets wait again
user.authenticate_otp('186522', drift: 60) # => true

Preventing reuse of Time based OTP's

By keeping track of the last time a user's OTP was verified, we can prevent token reuse during the interval window (default 30 seconds). It is useful with SMS, that is commonly used in combination with drift to extend the life of the code.

ruby
rails g migration AddLastOtpAtToUsers last_otp_at:integer
=>
      invoke  active_record
      create    db/migrate/20220407010931_add_last_otp_at_to_users.rb
ruby
class User  true
user.authenticate_otp('186522') # => false

Counter based OTP

An additional counter field is required in our User Model

ruby
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.

ruby
change_column :users, :otp_counter, :integer, default: 0

In addition set the counter flag option to true

ruby
class User  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.

ruby
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'

Backup codes

We're going to add a field to our User Model, so each user can have an otp backup codes. The next step is to run the migration generator in order to add the backup codes field.

ruby
rails g migration AddOtpBackupCodesToUsers otp_backup_codes:text
=>
      invoke  active_record
      create    db/migrate/20210126030834_add_otp_backup_codes_to_users.rb

You can change backup codes column name by option backup_codes_column_name:

ruby
class User  true
user.authenticate_otp('186522') # => false

Google Authenticator Compatible

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.

ruby
# 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.

Setting up a customer interval

If you define a custom interval for TOTP codes, just as has_one_time_password interval: 10 (for example), remember to include the interval also in provisioning_uri method. If not defined, the default value is 30 seconds (according to ROTP gem: https://github.com/mdp/rotp/blob/master/lib/rotp/totp.rb#L9)

ruby
class User  'otpauth://totp/hello?secret=2z6hxkdwi3uvrnpn&period=10'

# This code snippet generates OTP codes that expires every 10 seconds.

Note: Only some authenticator apps are compatible with custom period of tokens, for more details check these links:

  • https://labanskoller.se/blog/2019/07/11/many-common-mobile-authenticator-apps-accept-qr-codes-for-modes-they-dont-support
  • https://www.ibm.com/docs/en/sva/9.0.7?topic=authentication-configuring-totp-one-time-password-mechanism

So, be careful and aware when using custom intervals/periods for your TOTP codes beyond the default 30 seconds :)

Working example

Scan the following barcode with your phone, using Google Authenticator

Now run the following and compare the output

ruby
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 = '[email protected]'
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)

Useful Examples

  • 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

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

Issues· 12 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

Ruby

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言