rodauth-rails 0.4.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -0
- data/README.md +177 -77
- data/lib/generators/rodauth/install_generator.rb +28 -18
- data/lib/generators/rodauth/migration/account_expiration.erb +7 -0
- data/lib/generators/rodauth/migration/active_sessions.erb +7 -0
- data/lib/generators/rodauth/migration/audit_logging.erb +16 -0
- data/lib/generators/rodauth/migration/base.erb +19 -0
- data/lib/generators/rodauth/migration/disallow_password_reuse.erb +5 -0
- data/lib/generators/rodauth/migration/email_auth.erb +7 -0
- data/lib/generators/rodauth/migration/jwt_refresh.erb +7 -0
- data/lib/generators/rodauth/migration/lockout.erb +11 -0
- data/lib/generators/rodauth/migration/otp.erb +7 -0
- data/lib/generators/rodauth/migration/password_expiration.erb +5 -0
- data/lib/generators/rodauth/migration/recovery_codes.erb +6 -0
- data/lib/generators/rodauth/migration/remember.erb +6 -0
- data/lib/generators/rodauth/migration/reset_password.erb +7 -0
- data/lib/generators/rodauth/migration/single_session.erb +5 -0
- data/lib/generators/rodauth/migration/sms_codes.erb +8 -0
- data/lib/generators/rodauth/migration/verify_account.erb +7 -0
- data/lib/generators/rodauth/migration/verify_login_change.erb +7 -0
- data/lib/generators/rodauth/migration/webauthn.erb +12 -0
- data/lib/generators/rodauth/migration_generator.rb +32 -0
- data/lib/generators/rodauth/migration_helpers.rb +69 -0
- data/lib/generators/rodauth/templates/app/controllers/rodauth_controller.rb +2 -1
- data/lib/generators/rodauth/templates/app/lib/rodauth_app.rb +18 -18
- data/lib/generators/rodauth/templates/config/initializers/sequel.rb +1 -5
- data/lib/generators/rodauth/templates/db/migrate/create_rodauth.rb +2 -176
- data/lib/rodauth/rails.rb +23 -4
- data/lib/rodauth/rails/app.rb +3 -1
- data/lib/rodauth/rails/app/flash.rb +1 -1
- data/lib/rodauth/rails/app/middleware.rb +26 -0
- data/lib/rodauth/rails/feature.rb +92 -25
- data/lib/rodauth/rails/railtie.rb +11 -0
- data/lib/rodauth/rails/tasks.rake +28 -0
- data/lib/rodauth/rails/version.rb +1 -1
- data/rodauth-rails.gemspec +3 -3
- metadata +29 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9805b35cefee7e30cc6f7190e2ace9e7ea75c20f40651eb364edafea2f2382f7
|
4
|
+
data.tar.gz: 503b821866aaf2b6aa108265ed8015869a8c8a6a73e910aa3c38b35c5a542ac1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a3e69b6d62f20ee5bc5a13c89acd2974401830a4f0f8917cc7716c9a5ccaad021a20c0f3269a211336b648bd8eb65ae60094c90a039fa1d3968eaf322ec2e47
|
7
|
+
data.tar.gz: 567cf154e656f7062029e207d92149fa8cf2c87404d1ba72fef6327cb31f928d0bcf453a4a0d55f71740251cb74f8b6829b8c775373daec4fe638690cd702104
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,45 @@
|
|
1
|
+
## 0.6.1 (2020-11-25)
|
2
|
+
|
3
|
+
* Generate the Rodauth controller for API-only Rails apps as well (@janko)
|
4
|
+
|
5
|
+
* Fix remember cookie deadline not extending in remember feature (@janko)
|
6
|
+
|
7
|
+
## 0.6.0 (2020-11-22)
|
8
|
+
|
9
|
+
* Add `Rodauth::Rails.rodauth` method for retrieving Rodauth instance outside of request context (@janko)
|
10
|
+
|
11
|
+
* Add default Action Dispatch response headers in Rodauth responses (@janko)
|
12
|
+
|
13
|
+
* Run controller rescue handlers around Rodauth actions (@janko)
|
14
|
+
|
15
|
+
* Run controller action callbacks around Rodauth actions (@janko)
|
16
|
+
|
17
|
+
## 0.5.0 (2020-11-16)
|
18
|
+
|
19
|
+
* Support more Active Record adapters in `rodauth:install` generator (@janko)
|
20
|
+
|
21
|
+
* Add `rodauth:migration` generator for creating tables of specified features (@janko)
|
22
|
+
|
23
|
+
* Use UUIDs for primary keys if so configured in Rails generators (@janko)
|
24
|
+
|
25
|
+
* Add `rodauth:routes` rake task for printing routes handled by Rodauth middleware (@janko)
|
26
|
+
|
27
|
+
## 0.4.2 (2020-11-08)
|
28
|
+
|
29
|
+
* Drop support for Ruby 2.2 (@janko)
|
30
|
+
|
31
|
+
* Bump `sequel-activerecord_connection` dependency to 1.1+ (@janko)
|
32
|
+
|
33
|
+
* Set default bcrypt hash cost to `1` in tests (@janko)
|
34
|
+
|
35
|
+
* Call `AR::Base.connection_db_config` on Rails 6.1+ in `rodauth:install` generator (@janko)
|
36
|
+
|
37
|
+
## 0.4.1 (2020-11-02)
|
38
|
+
|
39
|
+
* Don't generate `RodauthController` in API-only mode (@janko)
|
40
|
+
|
41
|
+
* Pass `test: false` to Sequel in the `sequel.rb` initializer (@janko)
|
42
|
+
|
1
43
|
## 0.4.0 (2020-11-02)
|
2
44
|
|
3
45
|
* Support Rails API-only mode (@janko)
|
data/README.md
CHANGED
@@ -4,16 +4,22 @@ Provides Rails integration for the [Rodauth] authentication framework.
|
|
4
4
|
|
5
5
|
## Resources
|
6
6
|
|
7
|
+
Useful links:
|
8
|
+
|
7
9
|
* [Rodauth documentation](http://rodauth.jeremyevans.net/documentation.html)
|
8
|
-
* [rodauth-rails wiki](https://github.com/janko/rodauth-rails/wiki)
|
9
10
|
* [Rails demo](https://github.com/janko/rodauth-demo-rails)
|
10
11
|
|
12
|
+
Articles:
|
13
|
+
|
14
|
+
* [Rodauth: A Refreshing Authentication Solution for Ruby](https://janko.io/rodauth-a-refreshing-authentication-solution-for-ruby/)
|
15
|
+
* [Adding Authentication in Rails 6 with Rodauth](https://janko.io/adding-authentication-in-rails-with-rodauth/)
|
16
|
+
|
11
17
|
## Installation
|
12
18
|
|
13
19
|
Add the gem to your Gemfile:
|
14
20
|
|
15
21
|
```rb
|
16
|
-
gem "rodauth-rails", "~> 0.
|
22
|
+
gem "rodauth-rails", "~> 0.6"
|
17
23
|
|
18
24
|
# gem "jwt", require: false # for JWT feature
|
19
25
|
# gem "rotp", require: false # for OTP feature
|
@@ -54,7 +60,6 @@ class CreateRodauth < ActiveRecord::Migration
|
|
54
60
|
create_table :account_verification_keys do |t| ... end
|
55
61
|
create_table :account_login_change_keys do |t| ... end
|
56
62
|
create_table :account_remember_keys do |t| ... end
|
57
|
-
# ...
|
58
63
|
end
|
59
64
|
end
|
60
65
|
```
|
@@ -88,7 +93,7 @@ ActiveRecord connection.
|
|
88
93
|
require "sequel/core"
|
89
94
|
|
90
95
|
# initialize Sequel and have it reuse Active Record's database connection
|
91
|
-
DB = Sequel.
|
96
|
+
DB = Sequel.connect("postgresql://", extensions: :activerecord_connection)
|
92
97
|
```
|
93
98
|
|
94
99
|
### Rodauth app
|
@@ -112,8 +117,9 @@ end
|
|
112
117
|
|
113
118
|
### Controller
|
114
119
|
|
115
|
-
Your Rodauth app will by default use `RodauthController` for view rendering
|
116
|
-
and
|
120
|
+
Your Rodauth app will by default use `RodauthController` for view rendering,
|
121
|
+
CSRF protection, and running controller callbacks and rescue handlers around
|
122
|
+
Rodauth actions.
|
117
123
|
|
118
124
|
```rb
|
119
125
|
# app/controllers/rodauth_controller.rb
|
@@ -121,7 +127,7 @@ class RodauthController < ApplicationController
|
|
121
127
|
end
|
122
128
|
```
|
123
129
|
|
124
|
-
### Account
|
130
|
+
### Account model
|
125
131
|
|
126
132
|
Rodauth stores user accounts in the `accounts` table, so the generator will
|
127
133
|
also create an `Account` model for custom use.
|
@@ -132,10 +138,34 @@ class Account < ApplicationRecord
|
|
132
138
|
end
|
133
139
|
```
|
134
140
|
|
135
|
-
##
|
141
|
+
## Usage
|
142
|
+
|
143
|
+
### Routes
|
144
|
+
|
145
|
+
We can see the list of routes our Rodauth middleware handles:
|
136
146
|
|
137
|
-
|
138
|
-
|
147
|
+
```sh
|
148
|
+
$ rails rodauth:routes
|
149
|
+
```
|
150
|
+
```
|
151
|
+
Routes handled by RodauthApp:
|
152
|
+
|
153
|
+
/login rodauth.login_path
|
154
|
+
/create-account rodauth.create_account_path
|
155
|
+
/verify-account-resend rodauth.verify_account_resend_path
|
156
|
+
/verify-account rodauth.verify_account_path
|
157
|
+
/change-password rodauth.change_password_path
|
158
|
+
/change-login rodauth.change_login_path
|
159
|
+
/logout rodauth.logout_path
|
160
|
+
/remember rodauth.remember_path
|
161
|
+
/reset-password-request rodauth.reset_password_request_path
|
162
|
+
/reset-password rodauth.reset_password_path
|
163
|
+
/verify-login-change rodauth.verify_login_change_path
|
164
|
+
/close-account rodauth.close_account_path
|
165
|
+
```
|
166
|
+
|
167
|
+
Using this information, we could add some basic authentication links to our
|
168
|
+
navigation header:
|
139
169
|
|
140
170
|
```erb
|
141
171
|
<ul>
|
@@ -148,43 +178,48 @@ page:
|
|
148
178
|
</ul>
|
149
179
|
```
|
150
180
|
|
151
|
-
These
|
181
|
+
These routes are fully functional, feel free to visit them and interact with the
|
152
182
|
pages. The templates that ship with Rodauth aim to provide a complete
|
153
183
|
authentication experience, and the forms use [Bootstrap] markup.
|
154
184
|
|
155
|
-
|
156
|
-
|
185
|
+
### Current account
|
186
|
+
|
187
|
+
To be able to fetch currently authenticated account, let's define a
|
188
|
+
`#current_account` method that fetches the account id from session and
|
189
|
+
retrieves the corresponding account record:
|
157
190
|
|
158
191
|
```rb
|
159
192
|
# app/controllers/application_controller.rb
|
160
193
|
class ApplicationController < ActionController::Base
|
161
|
-
before_action :
|
194
|
+
before_action :current_account, if: -> { rodauth.authenticated? }
|
162
195
|
|
163
196
|
private
|
164
197
|
|
165
|
-
def
|
166
|
-
@current_account
|
198
|
+
def current_account
|
199
|
+
@current_account ||= Account.find(rodauth.session_value)
|
167
200
|
rescue ActiveRecord::RecordNotFound
|
168
201
|
rodauth.logout
|
169
202
|
rodauth.login_required
|
170
203
|
end
|
171
|
-
|
172
|
-
attr_reader :current_account
|
173
204
|
helper_method :current_account
|
174
205
|
end
|
175
206
|
```
|
207
|
+
|
208
|
+
This allows us to access the current account in controllers and views:
|
209
|
+
|
176
210
|
```erb
|
177
211
|
<p>Authenticated as: <%= current_account.email %></p>
|
178
212
|
```
|
179
213
|
|
180
214
|
### Requiring authentication
|
181
215
|
|
182
|
-
|
183
|
-
|
184
|
-
the authentication logic
|
216
|
+
We'll likely want to require authentication for certain parts of our app,
|
217
|
+
redirecting the user to the login page if they're not logged in. We can do this
|
218
|
+
in our Rodauth app's routing block, which helps keep the authentication logic
|
219
|
+
encapsulated:
|
185
220
|
|
186
221
|
```rb
|
187
|
-
# lib/rodauth_app.rb
|
222
|
+
# app/lib/rodauth_app.rb
|
188
223
|
class RodauthApp < Rodauth::Rails::App
|
189
224
|
# ...
|
190
225
|
route do |r|
|
@@ -239,9 +274,9 @@ end
|
|
239
274
|
|
240
275
|
### Views
|
241
276
|
|
242
|
-
The templates built into Rodauth are useful when getting started, but
|
243
|
-
|
244
|
-
|
277
|
+
The templates built into Rodauth are useful when getting started, but soon
|
278
|
+
you'll want to start editing the markup. You can run the following command to
|
279
|
+
copy Rodauth templates into your Rails app:
|
245
280
|
|
246
281
|
```sh
|
247
282
|
$ rails generate rodauth:views
|
@@ -265,7 +300,7 @@ $ rails generate rodauth:views --all
|
|
265
300
|
```
|
266
301
|
|
267
302
|
You can also tell the generator to create views into another directory (in this
|
268
|
-
case
|
303
|
+
case make sure to rename the Rodauth controller accordingly):
|
269
304
|
|
270
305
|
```sh
|
271
306
|
# generates views into app/views/authentication
|
@@ -300,11 +335,11 @@ end
|
|
300
335
|
|
301
336
|
### Mailer
|
302
337
|
|
303
|
-
Rodauth may send emails as part of
|
304
|
-
can be customized:
|
338
|
+
Depending on the features you've enabled, Rodauth may send emails as part of
|
339
|
+
the authentication flow. Most email settings can be customized:
|
305
340
|
|
306
341
|
```rb
|
307
|
-
# lib/rodauth_app.rb
|
342
|
+
# app/lib/rodauth_app.rb
|
308
343
|
class RodauthApp < Rodauth::Rails::App
|
309
344
|
# ...
|
310
345
|
configure do
|
@@ -345,46 +380,117 @@ end
|
|
345
380
|
```
|
346
381
|
|
347
382
|
You can then uncomment the lines in your Rodauth configuration to have it call
|
348
|
-
your mailer. If you've enabled additional authentication features
|
349
|
-
override their `
|
383
|
+
your mailer. If you've enabled additional authentication features that send
|
384
|
+
emails, make sure to override their `create_*_email` methods as well.
|
350
385
|
|
351
386
|
```rb
|
352
|
-
# lib/rodauth_app.rb
|
387
|
+
# app/lib/rodauth_app.rb
|
353
388
|
class RodauthApp < Rodauth::Rails::App
|
354
389
|
# ...
|
355
390
|
configure do
|
356
391
|
# ...
|
357
|
-
|
358
|
-
|
392
|
+
create_reset_password_email do
|
393
|
+
RodauthMailer.reset_password(email_to, reset_password_email_link)
|
359
394
|
end
|
360
|
-
|
361
|
-
|
395
|
+
create_verify_account_email do
|
396
|
+
RodauthMailer.verify_account(email_to, verify_account_email_link)
|
362
397
|
end
|
363
|
-
|
364
|
-
|
398
|
+
create_verify_login_change_email do |login|
|
399
|
+
RodauthMailer.verify_login_change(login, verify_login_change_old_login, verify_login_change_new_login, verify_login_change_email_link)
|
365
400
|
end
|
366
|
-
|
367
|
-
|
401
|
+
create_password_changed_email do
|
402
|
+
RodauthMailer.password_changed(email_to)
|
368
403
|
end
|
369
|
-
#
|
370
|
-
#
|
404
|
+
# create_email_auth_email do
|
405
|
+
# RodauthMailer.email_auth(email_to, email_auth_email_link)
|
371
406
|
# end
|
372
|
-
#
|
373
|
-
#
|
407
|
+
# create_unlock_account_email do
|
408
|
+
# RodauthMailer.unlock_account(email_to, unlock_account_email_link)
|
374
409
|
# end
|
375
|
-
|
410
|
+
send_email do |email|
|
376
411
|
# queue email delivery on the mailer after the transaction commits
|
377
|
-
|
378
|
-
db.after_commit do
|
379
|
-
RodauthMailer.public_send(type, *args).deliver_later
|
380
|
-
end
|
381
|
-
end
|
412
|
+
db.after_commit { email.deliver_later }
|
382
413
|
end
|
383
414
|
# ...
|
384
415
|
end
|
385
416
|
end
|
386
417
|
```
|
387
418
|
|
419
|
+
This approach can be used even if you're using a 3rd-party service for
|
420
|
+
transactional emails, where emails are sent via API requests instead of
|
421
|
+
SMTP. Whatever the `create_*_email` block returns will be passed to
|
422
|
+
`send_email`, so you can be creative.
|
423
|
+
|
424
|
+
### Migrations
|
425
|
+
|
426
|
+
The install generator will create a migration for tables used by the Rodauth
|
427
|
+
features enabled by default. For any additional features, you can use the
|
428
|
+
migration generator to create the corresponding tables:
|
429
|
+
|
430
|
+
```sh
|
431
|
+
$ rails generate rodauth:migration otp sms_codes recovery_codes
|
432
|
+
```
|
433
|
+
```rb
|
434
|
+
# db/migration/*_create_rodauth_otp_sms_codes_recovery_codes.rb
|
435
|
+
class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration
|
436
|
+
def change
|
437
|
+
create_table :account_otp_keys do |t| ... end
|
438
|
+
create_table :account_sms_codes do |t| ... end
|
439
|
+
create_table :account_recovery_codes do |t| ... end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
```
|
443
|
+
|
444
|
+
### JSON API
|
445
|
+
|
446
|
+
JSON API support in Rodauth is provided by the [JWT feature]. First you'll need
|
447
|
+
to add the [JWT gem] to your Gemfile:
|
448
|
+
|
449
|
+
```rb
|
450
|
+
gem "jwt"
|
451
|
+
```
|
452
|
+
|
453
|
+
The following configuration will enable the Rodauth endpoints to be accessed
|
454
|
+
via JSON requests (in addition to HTML requests):
|
455
|
+
|
456
|
+
```rb
|
457
|
+
# app/lib/rodauth_app.rb
|
458
|
+
class RodauthApp < Rodauth::Rails::App
|
459
|
+
configure(json: true) do
|
460
|
+
# ...
|
461
|
+
enable :jwt
|
462
|
+
jwt_secret "...your secret key..."
|
463
|
+
# ...
|
464
|
+
end
|
465
|
+
end
|
466
|
+
```
|
467
|
+
|
468
|
+
If you want the endpoints to be only accessible via JSON requests, or if your
|
469
|
+
Rails app is in API-only mode, instead of `json: true` pass `json: :only` to
|
470
|
+
the configure method.
|
471
|
+
|
472
|
+
Make sure to store the `jwt_secret` in a secure place, such as Rails
|
473
|
+
credentials or environment variables.
|
474
|
+
|
475
|
+
### Rodauth instance
|
476
|
+
|
477
|
+
In some cases you might need to use Rodauth more programmatically, and perform
|
478
|
+
Rodauth operations outside of the request context. rodauth-rails gives you the
|
479
|
+
ability to retrieve the Rodauth instance:
|
480
|
+
|
481
|
+
```rb
|
482
|
+
rodauth = Rodauth::Rails.rodauth # or Rodauth::Rails.rodauth(:secondary)
|
483
|
+
|
484
|
+
rodauth.login_url #=> "https://example.com/login"
|
485
|
+
rodauth.account_from_login("user@example.com") # loads user by email
|
486
|
+
rodauth.password_match?("secret") #=> true
|
487
|
+
rodauth.setup_account_verification
|
488
|
+
rodauth.close_account
|
489
|
+
```
|
490
|
+
|
491
|
+
This Rodauth instance will be initialized with basic Rack env that allows is it
|
492
|
+
to generate URLs, using `config.action_mailer.default_url_options` options.
|
493
|
+
|
388
494
|
## How it works
|
389
495
|
|
390
496
|
### Middleware
|
@@ -429,11 +535,12 @@ end
|
|
429
535
|
The `Rodauth::Rails::App` class is a [Roda] subclass that provides Rails
|
430
536
|
integration for Rodauth:
|
431
537
|
|
432
|
-
* uses
|
433
|
-
* uses
|
538
|
+
* uses Action Dispatch flash instead of Roda's
|
539
|
+
* uses Action Dispatch CSRF protection instead of Roda's
|
434
540
|
* sets [HMAC] secret to Rails' secret key base
|
435
|
-
* uses
|
436
|
-
*
|
541
|
+
* uses Action Controller for rendering templates
|
542
|
+
* runs Action Controller callbacks & rescue handlers around Rodauth actions
|
543
|
+
* uses Action Mailer for sending emails
|
437
544
|
|
438
545
|
The `configure { ... }` method wraps configuring the Rodauth plugin, forwarding
|
439
546
|
any additional [plugin options].
|
@@ -490,28 +597,6 @@ Rodauth::Rails.configure do |config|
|
|
490
597
|
end
|
491
598
|
```
|
492
599
|
|
493
|
-
## Working with JWT
|
494
|
-
|
495
|
-
To use Rodauth's [JWT feature], you'll need to load Roda's JSON support in
|
496
|
-
`configure`:
|
497
|
-
|
498
|
-
```rb
|
499
|
-
# lib/rodauth_app.rb
|
500
|
-
class RodauthApp < Rodauth::Rails::App
|
501
|
-
configure(json: true) do
|
502
|
-
enable :jwt
|
503
|
-
jwt_secret "...your secret key..."
|
504
|
-
# your configuration
|
505
|
-
end
|
506
|
-
end
|
507
|
-
```
|
508
|
-
|
509
|
-
Make sure to store the `jwt_secret` in a secure place, such as Rails
|
510
|
-
credentials or environment variables.
|
511
|
-
|
512
|
-
Rodauth's JWT feature depends on the [JWT gem], so make sure to add it to your
|
513
|
-
Gemfile.
|
514
|
-
|
515
600
|
## Testing
|
516
601
|
|
517
602
|
If you're writing system tests, it's generally better to go through the actual
|
@@ -579,6 +664,22 @@ disables the use of database functions, though you can always turn it back on.
|
|
579
664
|
use_database_authentication_functions? true
|
580
665
|
```
|
581
666
|
|
667
|
+
To create the database functions, pass the Sequel database object into the
|
668
|
+
Rodauth method for creating database functions:
|
669
|
+
|
670
|
+
```rb
|
671
|
+
# db/migrate/*_create_rodauth_database_functions.rb
|
672
|
+
class CreateRodauthDatabaseFunctions < ActiveRecord::Migration
|
673
|
+
def up
|
674
|
+
Rodauth.create_database_authentication_functions(DB)
|
675
|
+
end
|
676
|
+
|
677
|
+
def down
|
678
|
+
Rodauth.drop_database_authentication_functions(DB)
|
679
|
+
end
|
680
|
+
end
|
681
|
+
```
|
682
|
+
|
582
683
|
### Account statuses
|
583
684
|
|
584
685
|
The recommended [Rodauth migration] stores possible account status values in a
|
@@ -631,7 +732,6 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
|
|
631
732
|
|
632
733
|
[Rodauth]: https://github.com/jeremyevans/rodauth
|
633
734
|
[Sequel]: https://github.com/jeremyevans/sequel
|
634
|
-
[rendering views outside of controllers]: https://blog.bigbinary.com/2016/01/08/rendering-views-outside-of-controllers-in-rails-5.html
|
635
735
|
[feature documentation]: http://rodauth.jeremyevans.net/documentation.html
|
636
736
|
[JWT feature]: http://rodauth.jeremyevans.net/rdoc/files/doc/jwt_rdoc.html
|
637
737
|
[JWT gem]: https://github.com/jwt/ruby-jwt
|