Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/stas/otp-jwt
One time password (email, SMS) authentication support for HTTP APIs.
https://github.com/stas/otp-jwt
api-auth jwt-authentication jwt-authorization jwt-tokens otp-jwt rails rails-auth
Last synced: about 2 hours ago
JSON representation
One time password (email, SMS) authentication support for HTTP APIs.
- Host: GitHub
- URL: https://github.com/stas/otp-jwt
- Owner: stas
- License: mit
- Created: 2019-03-22T00:38:13.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2024-03-30T12:48:15.000Z (9 months ago)
- Last Synced: 2024-12-22T16:06:25.641Z (7 days ago)
- Topics: api-auth, jwt-authentication, jwt-authorization, jwt-tokens, otp-jwt, rails, rails-auth
- Language: Ruby
- Homepage: https://rubygems.org/gems/otp-jwt
- Size: 75.2 KB
- Stars: 102
- Watchers: 6
- Forks: 7
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# OTP JWT ⎆
One time password (email, SMS) authentication support for HTTP APIs.
> The man who wrote the book on password management has a confession to make:
> He blew it.
>
>— [WSJ.com](https://www.wsj.com/articles/the-man-who-wrote-those-password-rules-has-a-new-tip-n3v-r-m1-d-1502124118)This project provides a couple of mixins to help you build
applications/HTTP APIs without asking your users to provide passwords.[Your browser probably can work seamlessly with OTPs](https://web.dev/web-otp/)!!! :heart_eyes:
## About
The goal of this project is to provide support for one time passwords
which are delivered via different channels (email, SMS), along with a
simple and easy to use JWT authentication.Main goals:
* No _magic_ please
* No DSLs please
* Less code, less maintenance
* Good docs and test coverage
* Keep it up-to-date (or at least tell people this is no longer maintained)The available features include:
* Flexible models support for
[counter based OTP](https://github.com/mdp/rotp#counter-based-otps)
* Flexible JWT token generation helpers for models and arbitrary data
* Pluggable authentication flow using the OTP and JWT
* Pluggable OTP mailer
* Pluggable OTP SMS background processing jobThis little project wouldn't be possible without the previous work on
[ROTP](https://github.com/mdp/rotp)
and [JWT](https://github.com/jwt/ruby-jwt/).Thanks to everyone who worked on these amazing projects!
## Sponsors
I'm grateful for the following companies for supporting this project!
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'otp-jwt'
```And then execute:
$ bundle
Or install it yourself as:
$ gem install otp-jwt
## Usage
* [OTP for Active Record models](#otp-for-active-record-models)
* [Mailer support](#mailer-support)
* [SMS delivery support](#sms-delivery-support)
* [JWT for Active Record models](#jwt-for-active-record-models)
* [JWT authorization](#jwt-authorization)
* [JWT authentication](#jwt-authentication)
* [JWT Tokens](#jwt-tokens)---
To start using it with Rails, add this to an initializer and configure your
keys:```ruby
# config/initializers/otp-jwt.rb
require 'otp'
# To load the JWT related support.
require 'otp/jwt'# Set to 'none' to disable verification at all.
# OTP::JWT::Token.jwt_algorithm = 'HS256'# How long the token will be valid.
# OTP::JWT::Token.jwt_lifetime = 60 * 60 * 24OTP::JWT::Token.jwt_signature_key = ENV['YOUR-SIGN-KEY']
```
### OTP for Active Record modelsTo add support for OTP to your models, use the `OTP::ActiveRecord` concern:
```ruby
class User < ActiveRecord::Base
include OTP::ActiveRecord...
end
```This will provide two new methods which you can use to generate and verify
one time passwords:
* `User#otp`
* `User#verify_otp`This concern expects two attributes to be provided by the model, the:
* `otp_secret`: of type string, used to store the OTP signature key
* `otp_counter`: of type integer, used to store the OTP counter
* `expire_jwt_at`: of type datetime, **optional** and used to force a token to expireA migration to add these two looks like this:
```
$ rails g migration add_otp_to_users otp_secret:string otp_counter:integer
```Generate `opt_secret` by running the following in rails console if you have preexisting user data:
```
User.all.each do |u|
u.save()
end
```##### Force a token to expire
If there's an `expire_jwt_at` value that is in the past, the user token will
be reset and it will require a new authentication to receive a working token.This is handy if the user access needs to be scheduled and/or removed.
#### Mailer supportYou can use the built-in mailer to deliver the OTP, just require it and
overwrite the helper method:```ruby
require 'otp/mailer'class User < ActiveRecord::Base
include OTP::ActiveRecorddef email_otp
OTP::Mailer.otp(email, otp, self).deliver_later
end
end
```To customize the mailer subject, address and template, update the defaults:
```ruby
require 'otp/mailer'OTP::Mailer.default subject: 'Your App magic password 🗝️'
OTP::Mailer.default from: ENV['DEFAUL_MAILER_FROM']
# Tell mailer to use the template from app/views/otp/mailer/otp.html.erb
OTP::Mailer.prepend_view_path(Rails.root.join('app', 'views'))
```#### SMS delivery support
You can use the built-in job to deliver the OTP via SMS, just require it and
overwrite the helper method:```ruby
require 'otp/sms_otp_job'class User < ActiveRecord::Base
include OTP::ActiveRecordSMS_TEMPLATE = '%{otp} is your APP magic password 🗝️'
def sms_otp
OTP::SMSOTPJob.perform_later(
phone_number,
otp,
SMS_TEMPLATE # <-- Optional text message template.
) if phone_number.present?
end
end
```You will have to provide your model with the phone number attribute if you
want to deliver the OTPs via SMS.This job requires `aws-sdk-sns` gem to work. You will have to add it manually
and configure to use the correct keys. The SNS region is fetched from the
environment variable `AWS_SMS_REGION`.### JWT for Active Record models
To add support for JWT to your models, use the `OTP::JWT::ActiveRecord` concern:
```ruby
class User < ActiveRecord::Base
include OTP::JWT::ActiveRecord...
end
```This will provide two new methods which you can use to generate and verify JWT
tokens:
* `User#from_jwt`
* `User#to_jwt`### JWT authorization
To add support for JWT to your controllers,
use the `OTP::JWT::ActionController` concern:```ruby
class ApplicationController < ActionController::Base
include OTP::JWT::ActionControllerprivate
def current_user
@jwt_user ||= User.from_jwt(request_authorization_header)
enddef current_user!
current_user || raise('User authentication failed')
rescue
head(:unauthorized)
end
end
```The example from above includes helpers you can use interact with the
currently authenticated user or just use as part of `before_action` callback.The `request_authorization_header` method is also provided by the concern and
allows you to customize from where the token is received. A query parameter
based alternative would look like this:```ruby
class ApplicationController < ActionController::Base
include OTP::JWT::ActionControllerprivate
def current_user
@jwt_user ||= User.from_jwt(params[:token])
end...
end
```### JWT authentication
The `OTP::JWT::ActionController` concern provides support for handling the
authentication requests and token generation by using the `jwt_from_otp` method.Here's an example of a tokens controller:
```ruby
class TokensController < ApplicationController
def create
user = User.find_by(email: params[:email])jwt_from_otp(user, params[:otp]) do |auth_user|
# Let's update the last login date before we send the token...
# auth_user.update_column(:last_login_at, DateTime.current)render json: { token: auth_user.to_jwt }, status: :created
end
end
end
```The `jwt_from_otp` does a couple of things here:
* It will try to authenticate the user you found by email and respond with
a valid JWT token
* It will try to schedule an email or SMS delivery of the OTP and it will
respond with the 400 HTTP status
* It will respond with the 403 HTTP status if there's no user
or the OTP is wrongThe OTP delivery is handled by the `User#deliver_otp` method
and can be customized. By default it will call the `sms_otp` method and
if nothing is returned, it will proceed with the `email_otp` method.### JWT Tokens
To help sign any sort of data, a lightweight JWT Token wrapper is provided.
Signing a payload will follow the pre-defined settings like the lifetime and
the encryption key. Decoding a token will validate any claims as well. Finally
there's a safe wrapper to help you with the JWT specific exceptions handling.```ruby
require 'otp/jwt/token'token = OTP::JWT::Token.sign(sub: 'my subject')
OTP::JWT::Token.decode(token) == {'sub' => 'my subject'}
OTP::JWT::Token.decode('bad token') == nil
```## Development
After checking out the repo, run `bundle` to install dependencies.
Then, run `rake` to run the tests.
To install this gem onto your local machine, run `bundle exec rake install`.
To release a new version, update the version number in `version.rb`, and then
run `bundle exec rake release`, which will create a git tag for the version,
push git commits and tags, and push the `.gem` file to
[rubygems.org](https://rubygems.org).## Contributing
Bug reports and pull requests are welcome on GitHub at
https://github.com/stas/otp-jwtThis project is intended to be a safe, welcoming space for collaboration, and
contributors are expected to adhere to the
[Contributor Covenant](http://contributor-covenant.org) code of conduct.## License
The gem is available as open source under the terms of the
[MIT License](https://opensource.org/licenses/MIT).