{"id":13463345,"url":"https://github.com/heapsource/active_model_otp","last_synced_at":"2025-05-13T20:22:44.789Z","repository":{"id":7880183,"uuid":"9255015","full_name":"heapsource/active_model_otp","owner":"heapsource","description":"Adds methods to set and authenticate against one time passwords (Two-Factor Authentication). Inspired in AM::SecurePassword ","archived":false,"fork":false,"pushed_at":"2025-03-13T21:17:27.000Z","size":137,"stargazers_count":798,"open_issues_count":9,"forks_count":81,"subscribers_count":14,"default_branch":"main","last_synced_at":"2025-04-28T11:59:33.150Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/heapsource.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2013-04-06T03:51:01.000Z","updated_at":"2025-04-26T15:36:25.000Z","dependencies_parsed_at":"2023-01-13T14:32:28.014Z","dependency_job_id":"36cb5dac-dd77-43fe-b4d0-357269bba9c4","html_url":"https://github.com/heapsource/active_model_otp","commit_stats":{"total_commits":137,"total_committers":34,"mean_commits":4.029411764705882,"dds":0.5547445255474452,"last_synced_commit":"4e92e519dd7392728813ba92a015c51aa8f9b227"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heapsource%2Factive_model_otp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heapsource%2Factive_model_otp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heapsource%2Factive_model_otp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heapsource%2Factive_model_otp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/heapsource","download_url":"https://codeload.github.com/heapsource/active_model_otp/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251311332,"owners_count":21569008,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T13:00:51.548Z","updated_at":"2025-04-28T11:59:44.033Z","avatar_url":"https://github.com/heapsource.png","language":"Ruby","readme":"[![Active Model OTP](https://github.com/heapsource/active_model_otp/actions/workflows/active_model_otp.yml/badge.svg?branch=main)](https://github.com/heapsource/active_model_otp/actions/workflows/active_model_otp.yml)\n[![Gem Version](https://badge.fury.io/rb/active_model_otp.svg)](http://badge.fury.io/rb/active_model_otp)\n[![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)\n\n\n# ActiveModel::Otp\n\n**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\n\n## Dependencies\n\n* [ROTP](https://github.com/mdp/rotp) 6.2.0 or higher\n* Ruby 2.3 or greater\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n    gem 'active_model_otp'\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as follows:\n\n    $ gem install active_model_otp\n\n## Setting your Model\n\nWe'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.\n\n```ruby\nrails g migration AddOtpSecretKeyToUsers otp_secret_key:string\n=\u003e\n      invoke  active_record\n      create    db/migrate/20130707010931_add_otp_secret_key_to_users.rb\n```\n\nWe’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.\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_one_time_password\nend\n```\n\nNote: If you're adding this to an existing user model you'll need to generate *otp_secret_key* with a migration like:\n```ruby\nUser.find_each { |user| user.update_attribute(:otp_secret_key, User.otp_random_secret) }\n```\n\nTo 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.\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_one_time_password column_name: :my_otp_secret_column, length: 4\nend\n```\n\n## Usage\n\nThe 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](https://tools.ietf.org/html/rfc6238) and the [HOTP RFC 4226](https://www.ietf.org/rfc/rfc4226). This is compatible with Google Authenticator apps available for Android and iPhone, and now in use on GMail.\n\nThe otp_secret_key is saved automatically when an object is created,\n\n```ruby\nuser = User.create(email: \"hello@heapsource.com\")\nuser.otp_secret_key\n =\u003e \"jt3gdd2qm6su5iqh\"\n```\n\n**Note:** You can fork the applications for [iPhone](https://github.com/heapsource/google-authenticator) \u0026 [Android](https://github.com/heapsource/google-authenticator.android) and customize them\n\n### Getting current code (e.g. to send via SMS)\n```ruby\nuser.otp_code # =\u003e '186522'\nsleep 30\nuser.otp_code # =\u003e '850738'\n\n# Override current time\nuser.otp_code(time: Time.now + 3600) # =\u003e '317438'\n```\n\n### Authenticating using a code\n\n```ruby\nuser.authenticate_otp('186522') # =\u003e true\nsleep 30 # let's wait 30 secs\nuser.authenticate_otp('186522') # =\u003e false\n```\n\n### Authenticating using a slightly old code\n\n```ruby\nuser.authenticate_otp('186522') # =\u003e true\nsleep 30 # lets wait again\nuser.authenticate_otp('186522', drift: 60) # =\u003e true\n```\n\n### Preventing reuse of Time based OTP's\n\nBy 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.\n\n```ruby\nrails g migration AddLastOtpAtToUsers last_otp_at:integer\n=\u003e\n      invoke  active_record\n      create    db/migrate/20220407010931_add_last_otp_at_to_users.rb\n```\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_one_time_password after_column_name: :last_otp_at\nend\n```\n\n```ruby\nuser.authenticate_otp('186522') # =\u003e true\nuser.authenticate_otp('186522') # =\u003e false\n```\n\n## Counter based OTP\n\nAn additional counter field is required in our ``User`` Model\n\n```ruby\nrails g migration AddCounterForOtpToUsers otp_counter:integer\n=\u003e\n      invoke  active_record\n      create    db/migrate/20130707010931_add_counter_for_otp_to_users.rb\n```\n\nSet default value for otp_counter to 0.\n```ruby\nchange_column :users, :otp_counter, :integer, default: 0\n```\n\nIn addition set the counter flag option to true\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_one_time_password counter_based: true\nend\n```\n\nAnd for a custom counter column\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_one_time_password counter_based: true, counter_column_name: :my_otp_secret_counter_column\nend\n```\n\nAuthentication is done the same. You can manually adjust the counter for your usage or set auto_increment on success to true.\n\n```ruby\nuser.authenticate_otp('186522') # =\u003e true\nuser.authenticate_otp('186522', auto_increment: true) # =\u003e true\nuser.authenticate_otp('186522') # =\u003e false\nuser.otp_counter -= 1\nuser.authenticate_otp('186522') # =\u003e true\n```\n\nWhen retrieving an ```otp_code``` you can also pass the ```auto_increment``` option.\n\n```ruby\nuser.otp_code # =\u003e '186522'\nuser.otp_code # =\u003e '186522'\nuser.otp_code(auto_increment: true) # =\u003e '768273'\nuser.otp_code(auto_increment: true) # =\u003e '002811'\nuser.otp_code # =\u003e '002811'\n```\n\n## Backup codes\n\nWe'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.\n\n```ruby\nrails g migration AddOtpBackupCodesToUsers otp_backup_codes:text\n=\u003e\n      invoke  active_record\n      create    db/migrate/20210126030834_add_otp_backup_codes_to_users.rb\n```\n\nYou can change backup codes column name by option `backup_codes_column_name`:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_one_time_password backup_codes_column_name: 'secret_codes'\nend\n```\n\nThen use array type in schema or serialize attribute in model as Array (depending on used db type). Or even consider to use some libs like (lockbox)[https://github.com/ankane/lockbox] with type array.\n\nAfter that user can use one of automatically generated backup codes for authentication using same method `authenticate_otp`.\n\nBy default it generates 12 backup codes. You can change it by option `backup_codes_count`:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_one_time_password backup_codes_count: 6\nend\n```\n\nBy default each backup code can be reused an infinite number of times. You can\nchange it with option `one_time_backup_codes`:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_one_time_password one_time_backup_codes: true\nend\n```\n\n```ruby\nuser.authenticate_otp('186522') # =\u003e true\nuser.authenticate_otp('186522') # =\u003e false\n```\n\n## Google Authenticator Compatible\n\nThe 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.\n\n```ruby\n# Use your user's email address to generate the provisioning_url\nuser.provisioning_uri # =\u003e 'otpauth://totp/hello@heapsource.com?secret=2z6hxkdwi3uvrnpn'\n\n# Use a custom field to generate the provisioning_url\nuser.provisioning_uri(\"hello\") # =\u003e 'otpauth://totp/hello?secret=2z6hxkdwi3uvrnpn'\n\n# You can customize the generated url, by passing a hash of Options\n# `:issuer` lets you set the Issuer name in Google Authenticator, so it doesn't show as a blank entry.\nuser.provisioning_uri(nil, issuer: 'MYAPP') #=\u003e 'otpauth://totp/hello@heapsource.com?secret=2z6hxkdwi3uvrnpn\u0026issuer=MYAPP'\n```\n\nThis can then be rendered as a QR Code which can be scanned and added to the users list of OTP credentials.\n\n### Setting up a customer interval\n\nIf 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)\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_one_time_password interval: 10 # the interval value is in seconds\nend\n\nuser = User.new\nuser.provisioning_uri(\"hello\", interval: 10) # =\u003e 'otpauth://totp/hello?secret=2z6hxkdwi3uvrnpn\u0026period=10'\n\n# This code snippet generates OTP codes that expires every 10 seconds.\n```\n\n**Note**: Only some authenticator apps are compatible with custom `period` of tokens, for more details check these links:\n\n- https://labanskoller.se/blog/2019/07/11/many-common-mobile-authenticator-apps-accept-qr-codes-for-modes-they-dont-support\n- https://www.ibm.com/docs/en/sva/9.0.7?topic=authentication-configuring-totp-one-time-password-mechanism\n\nSo, be careful and aware when using custom intervals/periods for your TOTP codes beyond the default 30 seconds :)\n\n### Working example\n\nScan the following barcode with your phone, using Google Authenticator\n\n![QRCODE](http://qrfree.kaywa.com/?l=1\u0026s=8\u0026d=otpauth%3A%2F%2Ftotp%2Froberto%40heapsource.com%3Fsecret%3D2z6hxkdwi3uvrnpn)\n\nNow run the following and compare the output\n\n```ruby\nrequire \"active_model_otp\"\n\nclass User\n  extend ActiveModel::Callbacks\n  include ActiveModel::Validations\n  include ActiveModel::OneTimePassword\n\n  define_model_callbacks :create\n  attr_accessor :otp_secret_key, :email\n\n  has_one_time_password\nend\n\nuser = User.new\nuser.email = 'roberto@heapsource.com'\nuser.otp_secret_key = \"2z6hxkdwi3uvrnpn\"\nputs \"Current code #{user.otp_code}\"\n```\n\n**Note:** otp_secret_key must be generated using RFC 3548 base32 key strings (for compatilibity with google authenticator)\n\n### Useful Examples\n- [Drifting Ruby Tutorial](https://www.driftingruby.com/episodes/two-factor-authentication)\n- [Generate QR code with rqrcode gem](https://github.com/heapsource/active_model_otp/wiki/Generate-QR-code-with-rqrcode-gem)\n- Generating QR Code with Google Charts API\n- [Sending code via SMS with Twilio](https://github.com/heapsource/active_model_otp/wiki/Send-code-via-Twilio-SMS)\n- [Using with Mongoid](https://github.com/heapsource/active_model_otp/wiki/Using-with-Mongoid)\n\n## Contributing\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n","funding_links":[],"categories":["Security","Ruby"],"sub_categories":["Security Tools"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheapsource%2Factive_model_otp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fheapsource%2Factive_model_otp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheapsource%2Factive_model_otp/lists"}