{"id":15602881,"url":"https://github.com/stas/otp-jwt","last_synced_at":"2025-04-05T20:09:41.030Z","repository":{"id":47716340,"uuid":"177045134","full_name":"stas/otp-jwt","owner":"stas","description":"One time password (email, SMS) authentication support for HTTP APIs.","archived":false,"fork":false,"pushed_at":"2024-03-30T12:48:15.000Z","size":77,"stargazers_count":104,"open_issues_count":0,"forks_count":7,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-29T19:07:43.524Z","etag":null,"topics":["api-auth","jwt-authentication","jwt-authorization","jwt-tokens","otp-jwt","rails","rails-auth"],"latest_commit_sha":null,"homepage":"https://rubygems.org/gems/otp-jwt","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/stas.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2019-03-22T00:38:13.000Z","updated_at":"2025-02-18T02:48:12.000Z","dependencies_parsed_at":"2024-03-30T13:41:23.954Z","dependency_job_id":"1535ac76-b516-4360-87ed-af610c57911d","html_url":"https://github.com/stas/otp-jwt","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stas%2Fotp-jwt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stas%2Fotp-jwt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stas%2Fotp-jwt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stas%2Fotp-jwt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stas","download_url":"https://codeload.github.com/stas/otp-jwt/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247393573,"owners_count":20931813,"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":["api-auth","jwt-authentication","jwt-authorization","jwt-tokens","otp-jwt","rails","rails-auth"],"created_at":"2024-10-03T03:00:44.659Z","updated_at":"2025-04-05T20:09:41.005Z","avatar_url":"https://github.com/stas.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# OTP JWT ⎆\n\nOne time password (email, SMS) authentication support for HTTP APIs.\n\n\u003e The man who wrote the book on password management has a confession to make:\n\u003e He blew it.\n\u003e\n\u003e— [WSJ.com](https://www.wsj.com/articles/the-man-who-wrote-those-password-rules-has-a-new-tip-n3v-r-m1-d-1502124118)\n\nThis project provides a couple of mixins to help you build\napplications/HTTP APIs without asking your users to provide passwords.\n\n[Your browser probably can work seamlessly with OTPs](https://web.dev/web-otp/)!!! :heart_eyes:\n\n## About\n\nThe goal of this project is to provide support for one time passwords\nwhich are delivered via different channels (email, SMS), along with a\nsimple and easy to use JWT authentication.\n\nMain goals:\n * No _magic_ please\n * No DSLs please\n * Less code, less maintenance\n * Good docs and test coverage\n * Keep it up-to-date (or at least tell people this is no longer maintained)\n\nThe available features include:\n * Flexible models support for\n   [counter based OTP](https://github.com/mdp/rotp#counter-based-otps)\n * Flexible JWT token generation helpers for models and arbitrary data\n * Pluggable authentication flow using the OTP and JWT\n * Pluggable OTP mailer\n * Pluggable OTP SMS background processing job\n\n\nThis little project wouldn't be possible without the previous work on\n[ROTP](https://github.com/mdp/rotp)\nand [JWT](https://github.com/jwt/ruby-jwt/).\n\nThanks to everyone who worked on these amazing projects!\n\n\n## Sponsors\n\nI'm grateful for the following companies for supporting this project!\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://www.luneteyewear.com\"\u003e\u003cimg src=\"https://user-images.githubusercontent.com/112147/136836142-2bfba96e-447f-4eb6-b137-2445aee81b37.png\"/\u003e\u003c/a\u003e\n\u003ca href=\"https://www.startuplandia.io\"\u003e\u003cimg src=\"https://user-images.githubusercontent.com/112147/136836147-93f8ab17-2465-4477-a7ab-e38255483c66.png\"/\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'otp-jwt'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install otp-jwt\n\n## Usage\n\n * [OTP for Active Record models](#otp-for-active-record-models)\n   * [Mailer support](#mailer-support)\n   * [SMS delivery support](#sms-delivery-support)\n * [JWT for Active Record models](#jwt-for-active-record-models)\n * [JWT authorization](#jwt-authorization)\n * [JWT authentication](#jwt-authentication)\n * [JWT Tokens](#jwt-tokens)\n\n---\n\nTo start using it with Rails, add this to an initializer and configure your\nkeys:\n\n```ruby\n# config/initializers/otp-jwt.rb\nrequire 'otp'\n# To load the JWT related support.\nrequire 'otp/jwt'\n\n# Set to 'none' to disable verification at all.\n# OTP::JWT::Token.jwt_algorithm = 'HS256'\n\n# How long the token will be valid.\n# OTP::JWT::Token.jwt_lifetime = 60 * 60 * 24\n\nOTP::JWT::Token.jwt_signature_key = ENV['YOUR-SIGN-KEY']\n```\n### OTP for Active Record models\n\nTo add support for OTP to your models, use the `OTP::ActiveRecord` concern:\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  include OTP::ActiveRecord\n\n  ...\nend\n```\n\nThis will provide two new methods which you can use to generate and verify\none time passwords:\n * `User#otp`\n * `User#verify_otp`\n\nThis concern expects two attributes to be provided by the model, the:\n * `otp_secret`: of type string, used to store the OTP signature key\n * `otp_counter`: of type integer, used to store the OTP counter\n * `expire_jwt_at`: of type datetime, **optional** and used to force a token to expire\n\nA migration to add these two looks like this:\n```\n$ rails g migration add_otp_to_users otp_secret:string otp_counter:integer\n```\n\nGenerate `opt_secret` by running the following in rails console if you have preexisting user data:\n```\nUser.all.each do |u|\n  u.save()\nend\n```\n\n##### Force a token to expire\n\nIf there's an `expire_jwt_at` value that is in the past, the user token will\nbe reset and it will require a new authentication to receive a working token.\n\nThis is handy if the user access needs to be scheduled and/or removed.\n#### Mailer support\n\nYou can use the built-in mailer to deliver the OTP, just require it and\noverwrite the helper method:\n\n```ruby\nrequire 'otp/mailer'\n\nclass User \u003c ActiveRecord::Base\n  include OTP::ActiveRecord\n\n  def email_otp\n    OTP::Mailer.otp(email, otp, self).deliver_later\n  end\nend\n```\n\nTo customize the mailer subject, address and template, update the defaults:\n\n```ruby\nrequire 'otp/mailer'\n\nOTP::Mailer.default subject: 'Your App magic password 🗝️'\nOTP::Mailer.default from: ENV['DEFAUL_MAILER_FROM']\n# Tell mailer to use the template from app/views/otp/mailer/otp.html.erb\nOTP::Mailer.prepend_view_path(Rails.root.join('app', 'views'))\n```\n\n#### SMS delivery support\n\nYou can use the built-in job to deliver the OTP via SMS, just require it and\noverwrite the helper method:\n\n```ruby\nrequire 'otp/sms_otp_job'\n\nclass User \u003c ActiveRecord::Base\n  include OTP::ActiveRecord\n\n  SMS_TEMPLATE = '%{otp} is your APP magic password 🗝️'\n\n  def sms_otp\n    OTP::SMSOTPJob.perform_later(\n      phone_number,\n      otp,\n      SMS_TEMPLATE # \u003c-- Optional text message template.\n    ) if phone_number.present?\n  end\nend\n```\n\nYou will have to provide your model with the phone number attribute if you\nwant to deliver the OTPs via SMS.\n\nThis job requires `aws-sdk-sns` gem to work. You will have to add it manually\nand configure to use the correct keys. The SNS region is fetched from the\nenvironment variable `AWS_SMS_REGION`.\n\n### JWT for Active Record models\n\nTo add support for JWT to your models, use the `OTP::JWT::ActiveRecord` concern:\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  include OTP::JWT::ActiveRecord\n\n  ...\nend\n```\n\nThis will provide two new methods which you can use to generate and verify JWT\ntokens:\n * `User#from_jwt`\n * `User#to_jwt`\n\n### JWT authorization\n\nTo add support for JWT to your controllers,\nuse the `OTP::JWT::ActionController` concern:\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n  include OTP::JWT::ActionController\n\n  private\n\n  def current_user\n    @jwt_user ||= User.from_jwt(request_authorization_header)\n  end\n\n  def current_user!\n    current_user || raise('User authentication failed')\n  rescue\n    head(:unauthorized)\n  end\nend\n```\n\nThe example from above includes helpers you can use interact with the\ncurrently authenticated user or just use as part of `before_action` callback.\n\nThe `request_authorization_header` method is also provided by the concern and\nallows you to customize from where the token is received. A query parameter\nbased alternative would look like this:\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n  include OTP::JWT::ActionController\n\n  private\n\n  def current_user\n    @jwt_user ||= User.from_jwt(params[:token])\n  end\n\n  ...\nend\n```\n\n### JWT authentication\n\nThe `OTP::JWT::ActionController` concern provides support for handling the\nauthentication requests and token generation by using the `jwt_from_otp` method.\n\nHere's an example of a tokens controller:\n\n```ruby\nclass TokensController \u003c ApplicationController\n  def create\n    user = User.find_by(email: params[:email])\n\n    jwt_from_otp(user, params[:otp]) do |auth_user|\n      # Let's update the last login date before we send the token...\n      # auth_user.update_column(:last_login_at, DateTime.current)\n\n      render json: { token: auth_user.to_jwt }, status: :created\n    end\n  end\nend\n```\n\nThe `jwt_from_otp` does a couple of things here:\n * It will try to authenticate the user you found by email and respond with\n   a valid JWT token\n * It will try to schedule an email or SMS delivery of the OTP and it will\n   respond with the 400 HTTP status\n * It will respond with the 403 HTTP status if there's no user\n   or the OTP is wrong\n\nThe OTP delivery is handled by the `User#deliver_otp` method\nand can be customized. By default it will call the `sms_otp` method and\nif nothing is returned, it will proceed with the `email_otp` method.\n\n### JWT Tokens\n\nTo help sign any sort of data, a lightweight JWT Token wrapper is provided.\n\nSigning a payload will follow the pre-defined settings like the lifetime and\nthe encryption key. Decoding a token will validate any claims as well. Finally\nthere's a safe wrapper to help you with the JWT specific exceptions handling.\n\n```ruby\nrequire 'otp/jwt/token'\n\ntoken = OTP::JWT::Token.sign(sub: 'my subject')\nOTP::JWT::Token.decode(token) == {'sub' =\u003e 'my subject'}\nOTP::JWT::Token.decode('bad token') == nil\n```\n\n## Development\n\nAfter checking out the repo, run `bundle` to install dependencies.\n\nThen, run `rake` to run the tests.\n\nTo install this gem onto your local machine, run `bundle exec rake install`.\n\nTo release a new version, update the version number in `version.rb`, and then\nrun `bundle exec rake release`, which will create a git tag for the version,\npush git commits and tags, and push the `.gem` file to\n[rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at\nhttps://github.com/stas/otp-jwt\n\nThis project is intended to be a safe, welcoming space for collaboration, and\ncontributors are expected to adhere to the\n[Contributor Covenant](http://contributor-covenant.org) code of conduct.\n\n## License\n\nThe gem is available as open source under the terms of the\n[MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstas%2Fotp-jwt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstas%2Fotp-jwt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstas%2Fotp-jwt/lists"}