rodauth-rails 0.18.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -0
- data/LICENSE.txt +1 -1
- data/README.md +366 -651
- data/lib/generators/rodauth/install_generator.rb +32 -35
- data/lib/generators/rodauth/migration/active_sessions.erb +2 -2
- data/lib/generators/rodauth/migration/audit_logging.erb +1 -1
- data/lib/generators/rodauth/migration/base.erb +2 -2
- data/lib/generators/rodauth/migration/email_auth.erb +1 -1
- data/lib/generators/rodauth/migration/otp.erb +1 -1
- data/lib/generators/rodauth/migration/password_expiration.erb +1 -1
- data/lib/generators/rodauth/migration/reset_password.erb +1 -1
- data/lib/generators/rodauth/migration/sms_codes.erb +1 -1
- data/lib/generators/rodauth/migration/verify_account.erb +2 -2
- data/lib/generators/rodauth/migration/webauthn.erb +1 -1
- data/lib/generators/rodauth/migration_generator.rb +9 -2
- data/lib/generators/rodauth/migration_helpers.rb +8 -0
- data/lib/generators/rodauth/templates/INSTRUCTIONS +40 -0
- data/lib/generators/rodauth/templates/app/mailers/rodauth_mailer.rb +36 -19
- data/lib/generators/rodauth/templates/app/misc/rodauth_app.rb +27 -0
- data/lib/generators/rodauth/templates/app/{lib/rodauth_app.rb → misc/rodauth_main.rb} +10 -56
- data/lib/generators/rodauth/templates/app/models/account.rb +1 -0
- data/lib/generators/rodauth/templates/app/views/rodauth/_email_auth_request_form.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/change_login.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/change_password.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/close_account.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/confirm_password.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/create_account.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/email_auth.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/logout.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/otp_auth.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/otp_disable.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/otp_setup.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/recovery_auth.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/remember.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/reset_password.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/reset_password_request.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/sms_auth.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/sms_confirm.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/sms_disable.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/sms_request.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/sms_setup.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/two_factor_disable.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/unlock_account.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/unlock_account_request.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/verify_account.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/verify_account_resend.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/verify_login_change.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_auth.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_remove.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_setup.html.erb +1 -1
- data/lib/rodauth/rails/app.rb +18 -4
- data/lib/rodauth/rails/auth.rb +1 -16
- data/lib/rodauth/rails/controller_methods.rb +4 -29
- data/lib/rodauth/rails/feature/base.rb +21 -0
- data/lib/rodauth/rails/feature/internal_request.rb +10 -4
- data/lib/rodauth/rails/feature/render.rb +8 -0
- data/lib/rodauth/rails/tasks.rake +2 -2
- data/lib/rodauth/rails/version.rb +1 -1
- data/lib/rodauth/rails.rb +9 -20
- data/rodauth-rails.gemspec +2 -2
- metadata +10 -8
data/README.md
CHANGED
@@ -8,12 +8,15 @@ Useful links:
|
|
8
8
|
|
9
9
|
* [Rodauth documentation](http://rodauth.jeremyevans.net/documentation.html)
|
10
10
|
* [Rails demo](https://github.com/janko/rodauth-demo-rails)
|
11
|
+
* [JSON API guide](https://github.com/janko/rodauth-rails/wiki/JSON-API)
|
12
|
+
* [OmniAuth guide](https://github.com/janko/rodauth-rails/wiki/OmniAuth)
|
13
|
+
* [Testing guide](https://github.com/janko/rodauth-rails/wiki/Testing)
|
11
14
|
|
12
15
|
Articles:
|
13
16
|
|
14
17
|
* [Rodauth: A Refreshing Authentication Solution for Ruby](https://janko.io/rodauth-a-refreshing-authentication-solution-for-ruby/)
|
15
|
-
* [
|
16
|
-
* [
|
18
|
+
* [Rails Authentication with Rodauth](https://janko.io/adding-authentication-in-rails-with-rodauth/)
|
19
|
+
* [Multifactor Authentication in Rails with Rodauth](https://janko.io/adding-multifactor-authentication-in-rails-with-rodauth/)
|
17
20
|
* [How to build an OIDC provider using rodauth-oauth on Rails](https://honeyryderchuck.gitlab.io/httpx/2021/03/15/oidc-provider-on-rails-using-rodauth-oauth.html)
|
18
21
|
|
19
22
|
## Why Rodauth?
|
@@ -39,17 +42,12 @@ Active Record. There are good reasons for this, and to make Rodauth work
|
|
39
42
|
smoothly alongside Active Record, rodauth-rails configures Sequel to [reuse
|
40
43
|
Active Record's database connection][sequel-activerecord_connection].
|
41
44
|
|
42
|
-
## Upgrading
|
43
|
-
|
44
|
-
For instructions on upgrading from previous rodauth-rails versions, see
|
45
|
-
[UPGRADING.md](/UPGRADING.md).
|
46
|
-
|
47
45
|
## Installation
|
48
46
|
|
49
47
|
Add the gem to your Gemfile:
|
50
48
|
|
51
49
|
```rb
|
52
|
-
gem "rodauth-rails", "~> 0
|
50
|
+
gem "rodauth-rails", "~> 1.0"
|
53
51
|
|
54
52
|
# gem "jwt", require: false # for JWT feature
|
55
53
|
# gem "rotp", require: false # for OTP feature
|
@@ -74,9 +72,9 @@ $ rails generate rodauth:install --jwt # token authentication via the "Authoriza
|
|
74
72
|
$ bundle add jwt
|
75
73
|
```
|
76
74
|
|
77
|
-
This generator will create a Rodauth app with common
|
78
|
-
enabled, a database migration with tables required by
|
79
|
-
with default templates, and a few other files.
|
75
|
+
This generator will create a Rodauth app and configuration with common
|
76
|
+
authentication features enabled, a database migration with tables required by
|
77
|
+
those features, a mailer with default templates, and a few other files.
|
80
78
|
|
81
79
|
Feel free to remove any features you don't need, along with their corresponding
|
82
80
|
tables. Afterwards, run the migration:
|
@@ -85,11 +83,23 @@ tables. Afterwards, run the migration:
|
|
85
83
|
$ rails db:migrate
|
86
84
|
```
|
87
85
|
|
86
|
+
For your mailer to be able to generate email links, you'll need to set up
|
87
|
+
default URL options in each environment. Here is a possible configuration for
|
88
|
+
`config/environments/development.rb`:
|
89
|
+
|
90
|
+
```rb
|
91
|
+
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
|
92
|
+
```
|
93
|
+
|
88
94
|
## Usage
|
89
95
|
|
90
96
|
### Routes
|
91
97
|
|
92
|
-
|
98
|
+
Because requests to Rodauth endpoints are handled by a Rack middleware (and not
|
99
|
+
a Rails controller), Rodauth routes will not show in `rails routes`.
|
100
|
+
|
101
|
+
Use the `rodauth:routes` rake task to view the list of endpoints based on
|
102
|
+
currently loaded features:
|
93
103
|
|
94
104
|
```sh
|
95
105
|
$ rails rodauth:routes
|
@@ -127,32 +137,17 @@ These routes are fully functional, feel free to visit them and interact with the
|
|
127
137
|
pages. The templates that ship with Rodauth aim to provide a complete
|
128
138
|
authentication experience, and the forms use [Bootstrap] markup.
|
129
139
|
|
130
|
-
Inside Rodauth configuration and the `route` block you can access Rails route
|
131
|
-
helpers through `#rails_routes`:
|
132
|
-
|
133
|
-
```rb
|
134
|
-
class RodauthApp < Rodauth::Rails::App
|
135
|
-
configure do
|
136
|
-
# ...
|
137
|
-
login_redirect { rails_routes.activity_path }
|
138
|
-
# ...
|
139
|
-
end
|
140
|
-
end
|
141
|
-
```
|
142
|
-
|
143
140
|
### Current account
|
144
141
|
|
145
142
|
The `#current_account` method is defined in controllers and views, which
|
146
|
-
returns the model instance of the currently logged in account.
|
143
|
+
returns the model instance of the currently logged in account. If the account
|
144
|
+
doesn't exist in the database, the session will be cleared.
|
147
145
|
|
148
146
|
```rb
|
149
147
|
current_account #=> #<Account id=123 email="user@example.com">
|
150
148
|
current_account.email #=> "user@example.com"
|
151
149
|
```
|
152
150
|
|
153
|
-
If the account doesn't exist in the database, the session will be cleared and
|
154
|
-
login required.
|
155
|
-
|
156
151
|
Pass the configuration name to retrieve accounts belonging to other Rodauth
|
157
152
|
configurations:
|
158
153
|
|
@@ -160,13 +155,17 @@ configurations:
|
|
160
155
|
current_account(:admin)
|
161
156
|
```
|
162
157
|
|
158
|
+
This just delegates to the `#rails_account` method on the Rodauth object.
|
159
|
+
|
160
|
+
#### Custom account model
|
161
|
+
|
163
162
|
The `#current_account` method will try to infer the account model class from
|
164
163
|
the configured table name. If that fails, you can set the account model
|
165
164
|
manually:
|
166
165
|
|
167
166
|
```rb
|
168
|
-
# app/
|
169
|
-
class
|
167
|
+
# app/misc/rodauth_main.rb
|
168
|
+
class RodauthMain < Rodauth::Rails::Auth
|
170
169
|
configure do
|
171
170
|
# ...
|
172
171
|
rails_account_model Authentication::Account # custom model name
|
@@ -182,7 +181,7 @@ in your Rodauth app's routing block, which helps keep the authentication logic
|
|
182
181
|
encapsulated:
|
183
182
|
|
184
183
|
```rb
|
185
|
-
# app/
|
184
|
+
# app/misc/rodauth_app.rb
|
186
185
|
class RodauthApp < Rodauth::Rails::App
|
187
186
|
# ...
|
188
187
|
route do |r|
|
@@ -249,6 +248,18 @@ Rails.application.routes.draw do
|
|
249
248
|
end
|
250
249
|
```
|
251
250
|
|
251
|
+
The current account can be retrieved via the `#rails_account` method:
|
252
|
+
|
253
|
+
```rb
|
254
|
+
# config/routes.rb
|
255
|
+
Rails.application.routes.draw do
|
256
|
+
# require user to be admin
|
257
|
+
constraints Rodauth::Rails.authenticated { |rodauth| rodauth.rails_account.admin? } do
|
258
|
+
# ...
|
259
|
+
end
|
260
|
+
end
|
261
|
+
```
|
262
|
+
|
252
263
|
You can specify the Rodauth configuration by passing the configuration name:
|
253
264
|
|
254
265
|
```rb
|
@@ -305,12 +316,33 @@ Use `--name` to generate views for a different Rodauth configuration:
|
|
305
316
|
$ rails generate rodauth:views webauthn --name admin
|
306
317
|
```
|
307
318
|
|
319
|
+
#### Page titles
|
320
|
+
|
321
|
+
The generated view templates use `content_for(:title)` to store Rodauth's page
|
322
|
+
titles, which you can then retrieve in your layout template to set the page
|
323
|
+
title:
|
324
|
+
|
325
|
+
```erb
|
326
|
+
<!-- app/views/layouts/application.html.erb -->
|
327
|
+
<!DOCTYPE html>
|
328
|
+
<html>
|
329
|
+
<head>
|
330
|
+
<title><%= content_for(:title) %></title>
|
331
|
+
<!-- ... -->
|
332
|
+
</head>
|
333
|
+
<body>
|
334
|
+
<!-- ... -->
|
335
|
+
</body>
|
336
|
+
</html>
|
337
|
+
```
|
338
|
+
|
308
339
|
#### Layout
|
309
340
|
|
310
341
|
To use different layouts for different Rodauth views, you can compare the
|
311
342
|
request path in the layout method:
|
312
343
|
|
313
344
|
```rb
|
345
|
+
# app/controllers/rodauth_controller.rb
|
314
346
|
class RodauthController < ApplicationController
|
315
347
|
layout :rodauth_layout
|
316
348
|
|
@@ -331,6 +363,15 @@ class RodauthController < ApplicationController
|
|
331
363
|
end
|
332
364
|
```
|
333
365
|
|
366
|
+
#### Turbo
|
367
|
+
|
368
|
+
[Turbo] has been disabled by default on all built-in and generated view
|
369
|
+
templates, because some Rodauth actions (multi-phase login, adding recovery
|
370
|
+
codes) aren't Turbo-compatible, as they return 200 responses on POST requests.
|
371
|
+
|
372
|
+
That being said, most of Rodauth *is* Turbo-compatible, so feel free to enable
|
373
|
+
Turbo for actions where you want to use it.
|
374
|
+
|
334
375
|
### Mailer
|
335
376
|
|
336
377
|
The install generator will create `RodauthMailer` with default email templates,
|
@@ -340,48 +381,48 @@ flow to use it.
|
|
340
381
|
```rb
|
341
382
|
# app/mailers/rodauth_mailer.rb
|
342
383
|
class RodauthMailer < ApplicationMailer
|
343
|
-
def verify_account(
|
384
|
+
def verify_account(account_id, key)
|
344
385
|
# ...
|
345
386
|
end
|
346
|
-
def reset_password(
|
387
|
+
def reset_password(account_id, key)
|
347
388
|
# ...
|
348
389
|
end
|
349
|
-
def verify_login_change(
|
390
|
+
def verify_login_change(account_id, old_login, new_login, key)
|
350
391
|
# ...
|
351
392
|
end
|
352
|
-
def password_changed(
|
393
|
+
def password_changed(account_id)
|
353
394
|
# ...
|
354
395
|
end
|
355
|
-
# def email_auth(
|
396
|
+
# def email_auth(account_id, key)
|
356
397
|
# ...
|
357
398
|
# end
|
358
|
-
# def unlock_account(
|
399
|
+
# def unlock_account(account_id, key)
|
359
400
|
# ...
|
360
401
|
# end
|
361
402
|
end
|
362
403
|
```
|
363
404
|
```rb
|
364
|
-
# app/
|
365
|
-
class
|
405
|
+
# app/misc/rodauth_main.rb
|
406
|
+
class RodauthMain < Rodauth::Rails::Auth
|
366
407
|
configure do
|
367
408
|
# ...
|
368
409
|
create_reset_password_email do
|
369
|
-
RodauthMailer.reset_password(
|
410
|
+
RodauthMailer.reset_password(account_id, reset_password_key_value)
|
370
411
|
end
|
371
412
|
create_verify_account_email do
|
372
|
-
RodauthMailer.verify_account(
|
413
|
+
RodauthMailer.verify_account(account_id, verify_account_key_value)
|
373
414
|
end
|
374
|
-
create_verify_login_change_email do |
|
375
|
-
RodauthMailer.verify_login_change(
|
415
|
+
create_verify_login_change_email do |_login|
|
416
|
+
RodauthMailer.verify_login_change(account_id, verify_login_change_old_login, verify_login_change_new_login, verify_login_change_key_value)
|
376
417
|
end
|
377
418
|
create_password_changed_email do
|
378
|
-
RodauthMailer.password_changed(
|
419
|
+
RodauthMailer.password_changed(account_id)
|
379
420
|
end
|
380
421
|
# create_email_auth_email do
|
381
|
-
# RodauthMailer.email_auth(
|
422
|
+
# RodauthMailer.email_auth(account_id, email_auth_key_value)
|
382
423
|
# end
|
383
424
|
# create_unlock_account_email do
|
384
|
-
# RodauthMailer.unlock_account(
|
425
|
+
# RodauthMailer.unlock_account(account_id, unlock_account_key_value)
|
385
426
|
# end
|
386
427
|
send_email do |email|
|
387
428
|
# queue email delivery on the mailer after the transaction commits
|
@@ -399,44 +440,8 @@ deliveries. However, if you want to send emails synchronously, you can modify
|
|
399
440
|
the configuration to call `#deliver_now` instead.
|
400
441
|
|
401
442
|
If you're using a background processing library without an Active Job adapter,
|
402
|
-
or a 3rd-party service for sending transactional emails, this
|
403
|
-
|
404
|
-
and `#send_email`, override the `#send_*_email` methods instead, which are
|
405
|
-
required to send the email immediately. For example:
|
406
|
-
|
407
|
-
```rb
|
408
|
-
# app/workers/rodauth_mailer_worker.rb
|
409
|
-
class RodauthMailerWorker
|
410
|
-
include Sidekiq::Worker
|
411
|
-
|
412
|
-
def perform(name, *args)
|
413
|
-
email = RodauthMailer.public_send(name, *args)
|
414
|
-
email.deliver_now
|
415
|
-
end
|
416
|
-
end
|
417
|
-
```
|
418
|
-
```rb
|
419
|
-
# app/lib/rodauth_app.rb
|
420
|
-
class RodauthApp < Rodauth::Rails::App
|
421
|
-
configure do
|
422
|
-
# ...
|
423
|
-
# use `#send_*_email` method to be able to immediately enqueue email delivery
|
424
|
-
send_reset_password_email do
|
425
|
-
enqueue_email(:reset_password, email_to, reset_password_email_link)
|
426
|
-
end
|
427
|
-
# ...
|
428
|
-
auth_class_eval do
|
429
|
-
# custom method for enqueuing email delivery using our worker
|
430
|
-
def enqueue_email(name, *args)
|
431
|
-
db.after_commit do
|
432
|
-
RodauthMailerWorker.perform_async(name, *args)
|
433
|
-
end
|
434
|
-
end
|
435
|
-
end
|
436
|
-
# ...
|
437
|
-
end
|
438
|
-
end
|
439
|
-
```
|
443
|
+
or a 3rd-party service for sending transactional emails, see [this wiki
|
444
|
+
page][custom mailer worker] on how to set it up.
|
440
445
|
|
441
446
|
### Migrations
|
442
447
|
|
@@ -458,7 +463,23 @@ class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration
|
|
458
463
|
end
|
459
464
|
```
|
460
465
|
|
461
|
-
|
466
|
+
#### Custom migration name
|
467
|
+
|
468
|
+
You can change the default migration name:
|
469
|
+
|
470
|
+
```sh
|
471
|
+
$ rails generate rodauth:migration email_auth --name create_account_email_auth_keys
|
472
|
+
```
|
473
|
+
```rb
|
474
|
+
# db/migration/*_create_account_email_auth_keys
|
475
|
+
class CreateAccountEmailAuthKeys < ActiveRecord::Migration
|
476
|
+
def change
|
477
|
+
create_table :account_email_auth_keys do |t| ... end
|
478
|
+
end
|
479
|
+
end
|
480
|
+
```
|
481
|
+
|
482
|
+
## Model
|
462
483
|
|
463
484
|
The `Rodauth::Rails::Model` mixin can be included into the account model, which
|
464
485
|
defines a password attribute and associations for tables used by enabled
|
@@ -470,7 +491,7 @@ class Account < ApplicationRecord
|
|
470
491
|
end
|
471
492
|
```
|
472
493
|
|
473
|
-
|
494
|
+
### Password attribute
|
474
495
|
|
475
496
|
Regardless of whether you're storing the password hash in a column in the
|
476
497
|
accounts table, or in a separate table, the `#password` attribute can be used
|
@@ -494,7 +515,7 @@ Note that the password attribute doesn't come with validations, making it
|
|
494
515
|
unsuitable for forms. It was primarily intended to allow easily creating
|
495
516
|
accounts in development console and in tests.
|
496
517
|
|
497
|
-
|
518
|
+
### Associations
|
498
519
|
|
499
520
|
The `Rodauth::Rails::Model` mixin defines associations for Rodauth tables
|
500
521
|
associated to the accounts table:
|
@@ -544,6 +565,8 @@ class Account < ApplicationRecord
|
|
544
565
|
end
|
545
566
|
```
|
546
567
|
|
568
|
+
#### Association reference
|
569
|
+
|
547
570
|
Below is a list of all associations defined depending on the features loaded:
|
548
571
|
|
549
572
|
| Feature | Association | Type | Model | Table (default) |
|
@@ -568,6 +591,12 @@ Below is a list of all associations defined depending on the features loaded:
|
|
568
591
|
| webauthn | `:webauthn_keys` | `has_many` | `WebauthnKey` | `account_webauthn_keys` |
|
569
592
|
| webauthn | `:webauthn_user_id` | `has_one` | `WebauthnUserId` | `account_webauthn_user_ids` |
|
570
593
|
|
594
|
+
Note that some Rodauth tables use composite primary keys, which Active Record
|
595
|
+
doesn't support out of the box. For associations to work properly, you might
|
596
|
+
need to add the [composite_primary_keys] gem to your Gemfile.
|
597
|
+
|
598
|
+
#### Association options
|
599
|
+
|
571
600
|
By default, all associations except for audit logs have `dependent: :destroy`
|
572
601
|
set, to allow for easy deletion of account records in the console. You can use
|
573
602
|
`:association_options` to modify global or per-association options:
|
@@ -582,138 +611,64 @@ Rodauth::Rails.model(association_options: -> (name) {
|
|
582
611
|
})
|
583
612
|
```
|
584
613
|
|
585
|
-
|
586
|
-
doesn't support out of the box. For associations to work properly, you might
|
587
|
-
need to add the [composite_primary_keys] gem to your Gemfile.
|
588
|
-
|
589
|
-
### Multiple configurations
|
614
|
+
## Multiple configurations
|
590
615
|
|
591
616
|
If you need to handle multiple types of accounts that require different
|
592
|
-
authentication logic, you can create
|
617
|
+
authentication logic, you can create new configurations for them. This
|
618
|
+
is done by creating new `Rodauth::Rails::Auth` subclasses, and registering
|
619
|
+
them under a name.
|
593
620
|
|
594
621
|
```rb
|
595
|
-
# app/
|
622
|
+
# app/misc/rodauth_app.rb
|
596
623
|
class RodauthApp < Rodauth::Rails::App
|
597
|
-
# primary configuration
|
598
|
-
configure
|
599
|
-
# ...
|
600
|
-
end
|
624
|
+
configure RodauthMain # primary configuration
|
625
|
+
configure RodauthAdmin, :admin # secondary configuration
|
601
626
|
|
602
|
-
|
603
|
-
|
627
|
+
route do |r|
|
628
|
+
r.rodauth # route primary rodauth requests
|
629
|
+
r.rodauth(:admin) # route secondary rodauth requests
|
630
|
+
end
|
631
|
+
end
|
632
|
+
```
|
633
|
+
```rb
|
634
|
+
# app/misc/rodauth_admin.rb
|
635
|
+
class RodauthAdmin < Rodauth::Rails::Auth
|
636
|
+
configure do
|
604
637
|
# ... enable features ...
|
605
638
|
prefix "/admin"
|
606
639
|
session_key_prefix "admin_"
|
607
640
|
remember_cookie_key "_admin_remember" # if using remember feature
|
608
|
-
# ...
|
609
|
-
end
|
610
641
|
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
r.on "admin" do
|
615
|
-
r.rodauth(:admin)
|
616
|
-
break # allow routing of other /admin/* requests to continue to Rails
|
617
|
-
end
|
618
|
-
|
619
|
-
# ...
|
642
|
+
# search views in `app/views/admin/rodauth` directory
|
643
|
+
rails_controller { Admin::RodauthController }
|
620
644
|
end
|
621
645
|
end
|
622
646
|
```
|
623
|
-
|
624
|
-
Then in your application you can reference the secondary Rodauth instance:
|
625
|
-
|
626
647
|
```rb
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
You'll likely want to save the information of which account belongs to which
|
631
|
-
configuration to the database. One way would be to have a separate table that
|
632
|
-
stores account types:
|
633
|
-
|
634
|
-
```sh
|
635
|
-
$ rails generate migration create_account_types
|
636
|
-
```
|
637
|
-
```rb
|
638
|
-
# db/migrate/*_create_account_types.rb
|
639
|
-
class CreateAccountTypes < ActiveRecord::Migration
|
640
|
-
def change
|
641
|
-
create_table :account_types do |t|
|
642
|
-
t.references :account, foreign_key: { on_delete: :cascade }, null: false
|
643
|
-
t.string :type, null: false
|
644
|
-
end
|
645
|
-
end
|
648
|
+
# app/controllers/admin/rodauth_controller.rb
|
649
|
+
class Admin::RodauthController < ApplicationController
|
646
650
|
end
|
647
651
|
```
|
648
|
-
```sh
|
649
|
-
$ rails db:migrate
|
650
|
-
```
|
651
652
|
|
652
|
-
Then
|
653
|
-
Rodauth retrieves accounts you could filter only those belonging to the current
|
654
|
-
configuration:
|
653
|
+
Then in your application you can reference the secondary Rodauth instance:
|
655
654
|
|
656
655
|
```rb
|
657
|
-
|
658
|
-
class RodauthApp < Rodauth::Rails::App
|
659
|
-
configure(:admin) do
|
660
|
-
# ...
|
661
|
-
after_create_account do
|
662
|
-
db[:account_types].insert(account_id: account_id, type: "admin")
|
663
|
-
end
|
664
|
-
auth_class_eval do
|
665
|
-
def account_ds(*)
|
666
|
-
super.join(:account_types, account_id: :id).where(type: "admin")
|
667
|
-
end
|
668
|
-
end
|
669
|
-
# ...
|
670
|
-
end
|
671
|
-
end
|
656
|
+
rodauth(:admin).login_path #=> "/admin/login"
|
672
657
|
```
|
673
658
|
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
anonymous `Rodauth::Auth` subclass, and register it under the given name.
|
678
|
-
However, you can also define the auth classes explicitly, by creating
|
679
|
-
subclasses of `Rodauth::Rails::Auth`:
|
659
|
+
You'll likely want to save the information of which account belongs to which
|
660
|
+
configuration to the database. See [this guide][account types] on how you can do
|
661
|
+
that.
|
680
662
|
|
681
|
-
|
682
|
-
# app/lib/rodauth_main.rb
|
683
|
-
class RodauthMain < Rodauth::Rails::Auth
|
684
|
-
configure do
|
685
|
-
# ... main configuration ...
|
686
|
-
end
|
687
|
-
end
|
688
|
-
```
|
689
|
-
```rb
|
690
|
-
# app/lib/rodauth_admin.rb
|
691
|
-
class RodauthAdmin < Rodauth::Rails::Auth
|
692
|
-
configure do
|
693
|
-
# ...
|
694
|
-
prefix "/admin"
|
695
|
-
session_key_prefix "admin_"
|
696
|
-
# ...
|
697
|
-
end
|
698
|
-
end
|
699
|
-
```
|
700
|
-
```rb
|
701
|
-
# app/lib/rodauth_app.rb
|
702
|
-
class RodauthApp < Rodauth::Rails::App
|
703
|
-
configure RodauthMain
|
704
|
-
configure RodauthAdmin, :admin
|
705
|
-
# ...
|
706
|
-
end
|
707
|
-
```
|
663
|
+
### Sharing configuration
|
708
664
|
|
709
|
-
|
710
|
-
|
711
|
-
common settings:
|
665
|
+
If there are common settings that you want to share between Rodauth
|
666
|
+
configurations, you can do so via inheritance:
|
712
667
|
|
713
668
|
```rb
|
714
|
-
# app/
|
669
|
+
# app/misc/rodauth_base.rb
|
715
670
|
class RodauthBase < Rodauth::Rails::Auth
|
716
|
-
# common settings that
|
671
|
+
# common settings that are shared between multiple configurations
|
717
672
|
configure do
|
718
673
|
enable :login, :logout
|
719
674
|
login_return_to_requested_location? true
|
@@ -723,7 +678,7 @@ class RodauthBase < Rodauth::Rails::Auth
|
|
723
678
|
end
|
724
679
|
```
|
725
680
|
```rb
|
726
|
-
# app/
|
681
|
+
# app/misc/rodauth_main.rb
|
727
682
|
class RodauthMain < RodauthBase # inherit common settings
|
728
683
|
configure do
|
729
684
|
# ... customize main ...
|
@@ -731,7 +686,7 @@ class RodauthMain < RodauthBase # inherit common settings
|
|
731
686
|
end
|
732
687
|
```
|
733
688
|
```rb
|
734
|
-
# app/
|
689
|
+
# app/misc/rodauth_admin.rb
|
735
690
|
class RodauthAdmin < RodauthBase # inherit common settings
|
736
691
|
configure do
|
737
692
|
# ... customize admin ...
|
@@ -739,112 +694,63 @@ class RodauthAdmin < RodauthBase # inherit common settings
|
|
739
694
|
end
|
740
695
|
```
|
741
696
|
|
742
|
-
|
743
|
-
directly at the class level instead of inside an `auth_class_eval`:
|
744
|
-
|
745
|
-
```rb
|
746
|
-
# app/lib/rodauth_admin.rb
|
747
|
-
class RodauthAdmin < Rodauth::Rails::Auth
|
748
|
-
configure do
|
749
|
-
# ...
|
750
|
-
end
|
751
|
-
|
752
|
-
def superadmin?
|
753
|
-
Role.where(account_id: session_id, type: "superadmin").any?
|
754
|
-
end
|
755
|
-
end
|
756
|
-
```
|
757
|
-
```rb
|
758
|
-
# config/routes.rb
|
759
|
-
Rails.application.routes.draw do
|
760
|
-
constraints Rodauth::Rails.authenticated(:admin) { |rodauth| rodauth.superadmin? } do
|
761
|
-
mount Sidekiq::Web => "sidekiq"
|
762
|
-
end
|
763
|
-
end
|
764
|
-
```
|
765
|
-
|
766
|
-
### Calling controller methods
|
767
|
-
|
768
|
-
When using Rodauth before/after hooks or generally overriding your Rodauth
|
769
|
-
configuration, in some cases you might want to call methods defined on your
|
770
|
-
controllers. You can do so with `rails_controller_eval`, for example:
|
771
|
-
|
772
|
-
```rb
|
773
|
-
# app/controllers/application_controller.rb
|
774
|
-
class ApplicationController < ActionController::Base
|
775
|
-
private
|
776
|
-
def setup_tracking(account_id)
|
777
|
-
# ... some implementation ...
|
778
|
-
end
|
779
|
-
end
|
780
|
-
```
|
781
|
-
```rb
|
782
|
-
# app/lib/rodauth_app.rb
|
783
|
-
class RodauthApp < Rodauth::Rails::App
|
784
|
-
configure do
|
785
|
-
after_create_account do
|
786
|
-
rails_controller_eval { setup_tracking(account_id) }
|
787
|
-
end
|
788
|
-
end
|
789
|
-
end
|
790
|
-
```
|
697
|
+
## Outside of a request
|
791
698
|
|
792
|
-
###
|
699
|
+
### Calling actions
|
793
700
|
|
794
701
|
In some cases you might need to use Rodauth more programmatically. If you want
|
795
702
|
to perform authentication operations outside of request context, Rodauth ships
|
796
703
|
with the [internal_request] feature just for that.
|
797
704
|
|
798
705
|
```rb
|
799
|
-
# app/
|
800
|
-
class
|
706
|
+
# app/misc/rodauth_main.rb
|
707
|
+
class RodauthMain < Rodauth::Rails::Auth
|
801
708
|
configure do
|
802
709
|
enable :internal_request
|
803
710
|
end
|
804
711
|
end
|
805
712
|
```
|
806
713
|
```rb
|
807
|
-
#
|
714
|
+
# primary configuration
|
808
715
|
RodauthApp.rodauth.create_account(login: "user@example.com", password: "secret")
|
809
716
|
RodauthApp.rodauth.verify_account(account_login: "user@example.com")
|
810
717
|
|
811
718
|
# secondary configuration
|
812
|
-
RodauthApp.rodauth(:admin).close_account(account_login: "
|
719
|
+
RodauthApp.rodauth(:admin).close_account(account_login: "user@example.com")
|
813
720
|
```
|
814
721
|
|
815
|
-
|
816
|
-
`config.action_mailer.default_url_options`, which is used for generating email
|
817
|
-
links.
|
722
|
+
### Generating URLs
|
818
723
|
|
819
724
|
For generating authentication URLs outside of a request use the
|
820
725
|
[path_class_methods] plugin:
|
821
726
|
|
822
727
|
```rb
|
823
|
-
# app/
|
824
|
-
class
|
728
|
+
# app/misc/rodauth_main.rb
|
729
|
+
class RodauthMain < Rodauth::Rails::Auth
|
825
730
|
configure do
|
826
731
|
enable :path_class_methods
|
732
|
+
create_account_route "register"
|
827
733
|
end
|
828
734
|
end
|
829
735
|
```
|
830
736
|
```rb
|
831
|
-
#
|
832
|
-
RodauthApp.rodauth.create_account_path
|
833
|
-
RodauthApp.rodauth.verify_account_url(key: "abc123")
|
737
|
+
# primary configuration
|
738
|
+
RodauthApp.rodauth.create_account_path # => "/register"
|
739
|
+
RodauthApp.rodauth.verify_account_url(key: "abc123") #=> "https://example.com/verify-account?key=abc123"
|
834
740
|
|
835
741
|
# secondary configuration
|
836
|
-
RodauthApp.rodauth(:admin).close_account_path
|
742
|
+
RodauthApp.rodauth(:admin).close_account_path(foo: "bar") #=> "/admin/close-account?foo=bar"
|
837
743
|
```
|
838
744
|
|
839
|
-
|
745
|
+
### Calling instance methods
|
840
746
|
|
841
747
|
If you need to access Rodauth methods not exposed as internal requests, you can
|
842
748
|
use `Rodauth::Rails.rodauth` to retrieve the Rodauth instance used by the
|
843
749
|
internal_request feature:
|
844
750
|
|
845
751
|
```rb
|
846
|
-
# app/
|
847
|
-
class
|
752
|
+
# app/misc/rodauth_main.rb
|
753
|
+
class RodauthMain < Rodauth::Rails::Auth
|
848
754
|
configure do
|
849
755
|
enable :internal_request # this is required
|
850
756
|
end
|
@@ -852,7 +758,7 @@ end
|
|
852
758
|
```
|
853
759
|
```rb
|
854
760
|
account = Account.find_by!(email: "user@example.com")
|
855
|
-
rodauth = Rodauth::Rails.rodauth(account: account)
|
761
|
+
rodauth = Rodauth::Rails.rodauth(account: account) #=> #<RodauthMain::InternalRequest ...>
|
856
762
|
|
857
763
|
rodauth.compute_hmac("token") #=> "TpEJTKfKwqYvIDKWsuZhkhKlhaBXtR1aodskBAflD8U"
|
858
764
|
rodauth.open_account? #=> true
|
@@ -873,480 +779,290 @@ Rodauth::Rails.rodauth(session: { two_factor_auth_setup: true })
|
|
873
779
|
Rodauth::Rails.rodauth(:admin, params: { "param" => "value" })
|
874
780
|
```
|
875
781
|
|
876
|
-
##
|
782
|
+
## Configuring
|
877
783
|
|
878
|
-
###
|
784
|
+
### Configuration methods
|
879
785
|
|
880
|
-
rodauth-rails
|
881
|
-
|
882
|
-
reaches the Rails router.
|
786
|
+
The `rails` feature rodauth-rails loads provides the following configuration
|
787
|
+
methods:
|
883
788
|
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
789
|
+
| Name | Description |
|
790
|
+
| :---- | :---------- |
|
791
|
+
| `rails_render(**options)` | Renders the template with given render options. |
|
792
|
+
| `rails_csrf_tag` | Hidden field added to Rodauth templates containing the CSRF token. |
|
793
|
+
| `rails_csrf_param` | Value of the `name` attribute for the CSRF tag. |
|
794
|
+
| `rails_csrf_token` | Value of the `value` attribute for the CSRF tag. |
|
795
|
+
| `rails_check_csrf!` | Verifies the authenticity token for the current request. |
|
796
|
+
| `rails_controller_instance` | Instance of the controller with the request env context. |
|
797
|
+
| `rails_controller` | Controller class to use for rendering and CSRF protection. |
|
798
|
+
| `rails_account_model` | Model class connected with the accounts table. |
|
890
799
|
|
891
|
-
|
892
|
-
|
800
|
+
For the list of configuration methods provided by Rodauth, see the [feature
|
801
|
+
documentation].
|
893
802
|
|
894
|
-
|
895
|
-
request.env["rodauth"] #=> #<Rodauth::Auth>
|
896
|
-
request.env["rodauth.admin"] #=> #<Rodauth::Auth> (if using multiple configurations)
|
897
|
-
```
|
803
|
+
### Defining custom methods
|
898
804
|
|
899
|
-
|
900
|
-
|
805
|
+
All Rodauth configuration methods are just syntax sugar for defining instance
|
806
|
+
methods on the auth class. You can also define your own custom methods on the
|
807
|
+
auth class:
|
901
808
|
|
902
809
|
```rb
|
903
|
-
class
|
904
|
-
|
905
|
-
|
906
|
-
|
810
|
+
class RodauthMain < Rodauth::Rails::Auth
|
811
|
+
configure do
|
812
|
+
# ...
|
813
|
+
password_match? { |password| ldap_valid?(password) }
|
814
|
+
# ...
|
907
815
|
end
|
908
|
-
end
|
909
|
-
```
|
910
|
-
```erb
|
911
|
-
<% rodauth #=> #<Rodauth::Auth> %>
|
912
|
-
<% rodauth(:admin) #=> #<Rodauth::Auth> (if using multiple configurations) %>
|
913
|
-
```
|
914
|
-
|
915
|
-
### App
|
916
|
-
|
917
|
-
The `Rodauth::Rails::App` class is a [Roda] subclass that provides Rails
|
918
|
-
integration for Rodauth:
|
919
816
|
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
* runs Action Controller callbacks & rescue handlers around Rodauth actions
|
925
|
-
* uses Action Mailer for sending emails
|
817
|
+
# Example external identities table
|
818
|
+
def identities
|
819
|
+
db[:account_identities].where(account_id: account_id).all
|
820
|
+
end
|
926
821
|
|
927
|
-
|
928
|
-
any additional [plugin options].
|
822
|
+
private
|
929
823
|
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
configure(:admin) { ... } # defining multiple Rodauth configurations
|
824
|
+
# Example LDAP authentication
|
825
|
+
def ldap_valid?(password)
|
826
|
+
SimpleLdapAuthenticator.valid?(account[:email], password)
|
827
|
+
end
|
935
828
|
end
|
936
829
|
```
|
830
|
+
```rb
|
831
|
+
rodauth.identities #=> [{ provider: "facebook", uid: "abc123", ... }, ...]
|
832
|
+
```
|
937
833
|
|
938
|
-
|
939
|
-
|
834
|
+
### Rails URL helpers
|
835
|
+
|
836
|
+
Inside Rodauth configuration and the `route` block you can access Rails route
|
837
|
+
helpers through `#rails_routes`:
|
940
838
|
|
941
839
|
```rb
|
942
|
-
|
943
|
-
|
944
|
-
|
840
|
+
# app/misc/rodauth_main.rb
|
841
|
+
class RodauthMain < Rodauth::Rails::Auth
|
842
|
+
configure do
|
843
|
+
login_redirect { rails_routes.activity_path }
|
945
844
|
end
|
946
845
|
end
|
947
846
|
```
|
948
847
|
|
949
|
-
|
950
|
-
|
848
|
+
### Calling controller methods
|
849
|
+
|
850
|
+
When using Rodauth before/after hooks or generally overriding your Rodauth
|
851
|
+
configuration, in some cases you might want to call methods defined on your
|
852
|
+
controllers. You can do so with `rails_controller_eval`, for example:
|
951
853
|
|
952
854
|
```rb
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
855
|
+
# app/controllers/application_controller.rb
|
856
|
+
class ApplicationController < ActionController::Base
|
857
|
+
private
|
858
|
+
def setup_tracking(account_id)
|
859
|
+
# ... some implementation ...
|
860
|
+
end
|
958
861
|
end
|
959
862
|
```
|
960
|
-
|
961
|
-
### Sequel
|
962
|
-
|
963
|
-
Rodauth uses the [Sequel] library for database queries, due to more advanced
|
964
|
-
database usage (SQL expressions, database-agnostic date arithmetic, SQL
|
965
|
-
function calls).
|
966
|
-
|
967
|
-
If ActiveRecord is used in the application, the `rodauth:install` generator
|
968
|
-
will have automatically configured Sequel to reuse ActiveRecord's database
|
969
|
-
connection, using the [sequel-activerecord_connection] gem.
|
970
|
-
|
971
|
-
This means that, from the usage perspective, Sequel can be considered just
|
972
|
-
as an implementation detail of Rodauth.
|
973
|
-
|
974
|
-
## JSON API
|
975
|
-
|
976
|
-
To make Rodauth endpoints accessible via JSON API, enable the [`json`][json]
|
977
|
-
feature:
|
978
|
-
|
979
863
|
```rb
|
980
|
-
# app/
|
981
|
-
class
|
864
|
+
# app/misc/rodauth_main.rb
|
865
|
+
class RodauthMain < Rodauth::Rails::Auth
|
982
866
|
configure do
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
# ...
|
867
|
+
after_create_account do
|
868
|
+
rails_controller_eval { setup_tracking(account_id) }
|
869
|
+
end
|
987
870
|
end
|
988
871
|
end
|
989
872
|
```
|
990
873
|
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
874
|
+
### Single-file configuration
|
875
|
+
|
876
|
+
If you would prefer to have all Rodauth logic contained inside a single file,
|
877
|
+
you call `Rodauth::Rails::App.configure` with a block, which will create an
|
878
|
+
anonymous auth class.
|
995
879
|
|
996
|
-
```sh
|
997
|
-
$ bundle add jwt
|
998
|
-
```
|
999
880
|
```rb
|
1000
|
-
# app/
|
881
|
+
# app/misc/rodauth_app.rb
|
1001
882
|
class RodauthApp < Rodauth::Rails::App
|
883
|
+
# primary configuration
|
1002
884
|
configure do
|
1003
|
-
|
1004
|
-
enable :jwt
|
1005
|
-
jwt_secret "<YOUR_SECRET_KEY>" # store the JWT secret in a safe place
|
1006
|
-
only_json? true # accept only JSON requests (optional)
|
885
|
+
enable :login, :logout, :create_account, :verify_account
|
1007
886
|
# ...
|
1008
887
|
end
|
1009
|
-
end
|
1010
|
-
```
|
1011
888
|
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
class ApplicationController < ActionController::Base
|
1018
|
-
# ...
|
1019
|
-
after_action :set_jwt_token
|
1020
|
-
|
1021
|
-
private
|
889
|
+
# secondary configuration
|
890
|
+
configure(:admin) do
|
891
|
+
enable :email_auth, :single_session
|
892
|
+
# ...
|
893
|
+
end
|
1022
894
|
|
1023
|
-
|
1024
|
-
|
1025
|
-
response.headers["Authorization"] = rodauth.session_jwt
|
1026
|
-
end
|
895
|
+
route do |r|
|
896
|
+
# ...
|
1027
897
|
end
|
1028
|
-
# ...
|
1029
898
|
end
|
1030
899
|
```
|
1031
900
|
|
1032
|
-
##
|
1033
|
-
|
1034
|
-
While Rodauth doesn't yet come with [OmniAuth] integration, we can build one
|
1035
|
-
ourselves using the existing Rodauth API.
|
1036
|
-
|
1037
|
-
Let's assume we're building Facebook login. We'll start by installing the
|
1038
|
-
necessary gems, and loading the Facebook OmniAuth strategy:
|
901
|
+
## How it works
|
1039
902
|
|
1040
|
-
|
1041
|
-
# Gemfile
|
1042
|
-
gem "omniauth", "~> 2.0"
|
1043
|
-
gem "omniauth-rails_csrf_protection" # https://github.com/omniauth/omniauth/wiki/Resolving-CVE-2015-9284
|
1044
|
-
gem "omniauth-facebook"
|
1045
|
-
```
|
1046
|
-
```rb
|
1047
|
-
# config/initializers/omniauth.rb
|
1048
|
-
Rails.application.config.middleware.use OmniAuth::Builder do
|
1049
|
-
provider :facebook, ENV["FACEBOOK_APP_ID"], ENV["FACEBOOK_APP_SECRET"],
|
1050
|
-
scope: "email", callback_path: "/auth/facebook/callback"
|
1051
|
-
end
|
1052
|
-
```
|
903
|
+
### Rack middleware
|
1053
904
|
|
1054
|
-
|
1055
|
-
|
1056
|
-
with the `accounts` table:
|
905
|
+
The railtie inserts [`Rodauth::Rails::Middleware`](/lib/rodauth/rails/middleware.rb)
|
906
|
+
at the end of the middleware stack, which calls your Rodauth app around each request.
|
1057
907
|
|
1058
908
|
```sh
|
1059
|
-
$ rails
|
909
|
+
$ rails middleware
|
910
|
+
# ...
|
911
|
+
# use Rodauth::Rails::Middleware
|
912
|
+
# run MyApp::Application.routes
|
1060
913
|
```
|
1061
|
-
```rb
|
1062
|
-
# db/migrate/*_create_account_identities.rb
|
1063
|
-
class CreateAccountIdentities < ActiveRecord::Migration
|
1064
|
-
def change
|
1065
|
-
create_table :account_identities do |t|
|
1066
|
-
t.references :account, null: false, foreign_key: { on_delete: :cascade }
|
1067
|
-
t.string :provider, null: false
|
1068
|
-
t.string :uid, null: false
|
1069
|
-
t.jsonb :info, null: false, default: {} # adjust JSON column type for your database
|
1070
914
|
|
1071
|
-
|
915
|
+
It can be inserted at any point in the middleware stack:
|
1072
916
|
|
1073
|
-
t.index [:provider, :uid], unique: true
|
1074
|
-
end
|
1075
|
-
end
|
1076
|
-
end
|
1077
|
-
```
|
1078
917
|
```rb
|
1079
|
-
|
1080
|
-
|
1081
|
-
belongs_to :account
|
1082
|
-
end
|
1083
|
-
```
|
1084
|
-
```rb
|
1085
|
-
# app/models/account.rb
|
1086
|
-
class Account < ApplicationRecord
|
1087
|
-
has_many :identities, class_name: "AccountIdentity"
|
918
|
+
Rodauth::Rails.configure do |config|
|
919
|
+
config.middleware = false # disable auto-insertion
|
1088
920
|
end
|
1089
|
-
```
|
1090
|
-
|
1091
|
-
Next, let's add a POST button pointing to the request URL to our login form:
|
1092
921
|
|
1093
|
-
|
1094
|
-
<%= button_to "Login via Facebook", "/auth/facebook",
|
1095
|
-
method: :post, data: { turbo: false }, class: "btn btn-link p-0" %>
|
922
|
+
Rails.application.config.middleware.insert_before AnotherMiddleware, Rodauth::Rails::Middleware
|
1096
923
|
```
|
1097
924
|
|
1098
|
-
|
1099
|
-
|
925
|
+
The middleware retrieves the Rodauth app via `Rodauth::Rails.app`, which is
|
926
|
+
specified as a string to keep the class autoloadable and reloadable in
|
927
|
+
development.
|
1100
928
|
|
1101
929
|
```rb
|
1102
|
-
|
1103
|
-
|
1104
|
-
# ...
|
1105
|
-
get "/auth/:provider/callback", to: "rodauth#omniauth"
|
930
|
+
Rodauth::Rails.configure do |config|
|
931
|
+
config.app = "RodauthApp"
|
1106
932
|
end
|
1107
933
|
```
|
1108
|
-
```rb
|
1109
|
-
# app/controllres/rodauth_controller.rb
|
1110
|
-
class RodauthController < ApplicationController
|
1111
|
-
def omniauth
|
1112
|
-
auth = request.env["omniauth.auth"]
|
1113
934
|
|
1114
|
-
|
1115
|
-
|
935
|
+
In addition to Zeitwerk compatibility, this extra layer catches Rodauth redirects
|
936
|
+
that happen on the controller level (e.g. when calling
|
937
|
+
`rodauth.require_authentication` in a `before_action` filter).
|
1116
938
|
|
1117
|
-
|
1118
|
-
# update any external info changes
|
1119
|
-
identity.update!(info: auth["info"])
|
1120
|
-
# set account from identity
|
1121
|
-
account = identity.account
|
1122
|
-
end
|
939
|
+
### Roda app
|
1123
940
|
|
1124
|
-
|
1125
|
-
|
941
|
+
The [`Rodauth::Rails::App`](/lib/rodauth/rails/app.rb) class is a [Roda]
|
942
|
+
subclass that provides a convenience layer for Rodauth:
|
1126
943
|
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
end
|
944
|
+
* uses Action Dispatch flash messages
|
945
|
+
* provides syntax sugar for loading the rodauth plugin
|
946
|
+
* saves Rodauth object(s) to Rack env hash
|
947
|
+
* propagates edited headers to Rails responses
|
1132
948
|
|
1133
|
-
|
1134
|
-
unless account
|
1135
|
-
account = Account.create!(email: auth["info"]["email"], status: rodauth.account_open_status_value)
|
1136
|
-
end
|
949
|
+
#### Configure block
|
1137
950
|
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
# load the account into the rodauth instance
|
1144
|
-
rodauth.account_from_login(account.email)
|
1145
|
-
|
1146
|
-
rodauth_response do # ensures any `after_action` callbacks get called
|
1147
|
-
# sign in the loaded account
|
1148
|
-
rodauth.login("omniauth")
|
1149
|
-
end
|
1150
|
-
end
|
1151
|
-
end
|
1152
|
-
```
|
1153
|
-
|
1154
|
-
## Configuring
|
1155
|
-
|
1156
|
-
The `rails` feature rodauth-rails loads provides the following configuration
|
1157
|
-
methods:
|
1158
|
-
|
1159
|
-
| Name | Description |
|
1160
|
-
| :---- | :---------- |
|
1161
|
-
| `rails_render(**options)` | Renders the template with given render options. |
|
1162
|
-
| `rails_csrf_tag` | Hidden field added to Rodauth templates containing the CSRF token. |
|
1163
|
-
| `rails_csrf_param` | Value of the `name` attribute for the CSRF tag. |
|
1164
|
-
| `rails_csrf_token` | Value of the `value` attribute for the CSRF tag. |
|
1165
|
-
| `rails_check_csrf!` | Verifies the authenticity token for the current request. |
|
1166
|
-
| `rails_controller_instance` | Instance of the controller with the request env context. |
|
1167
|
-
| `rails_controller` | Controller class to use for rendering and CSRF protection. |
|
1168
|
-
| `rails_account_model` | Model class connected with the accounts table. |
|
951
|
+
The `configure` call loads the rodauth plugin. By convention, it receives an
|
952
|
+
auth class and configuration name as positional arguments (forwarded as
|
953
|
+
`:auth_class` and `:name` plugin options), a block for anonymous auth classes,
|
954
|
+
and also accepts any additional plugin options.
|
1169
955
|
|
1170
|
-
|
956
|
+
```rb
|
957
|
+
class RodauthApp < Rodauth::Rails::App
|
958
|
+
# named auth class
|
959
|
+
configure(RodauthMain)
|
960
|
+
configure(RodauthAdmin, :admin)
|
1171
961
|
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
| `middleware` | Whether to insert the middleware into the Rails application's middleware stack. Defaults to `true`. |
|
962
|
+
# anonymous auth class
|
963
|
+
configure { ... }
|
964
|
+
configure(:admin) { ... }
|
1176
965
|
|
1177
|
-
|
1178
|
-
|
1179
|
-
Rodauth::Rails.configure do |config|
|
1180
|
-
config.app = "RodauthApp"
|
1181
|
-
config.middleware = true
|
966
|
+
# plugin options
|
967
|
+
configure(RodauthMain, json: :only)
|
1182
968
|
end
|
1183
969
|
```
|
1184
970
|
|
1185
|
-
|
1186
|
-
documentation].
|
1187
|
-
|
1188
|
-
## Custom extensions
|
971
|
+
#### Route block
|
1189
972
|
|
1190
|
-
|
1191
|
-
|
1192
|
-
feature design doesn't yet work well with Zeitwerk reloading.
|
1193
|
-
|
1194
|
-
Here is an example of an LDAP authentication extension that uses the
|
1195
|
-
[simple_ldap_authenticator] gem.
|
973
|
+
The `route` block is called for each request, before it reaches the Rails
|
974
|
+
router, and it's yielded the request object.
|
1196
975
|
|
1197
976
|
```rb
|
1198
|
-
# app/lib/rodauth_ldap.rb
|
1199
|
-
module RodauthLdap
|
1200
|
-
def require_bcrypt?
|
1201
|
-
false
|
1202
|
-
end
|
1203
|
-
|
1204
|
-
def password_match?(password)
|
1205
|
-
SimpleLdapAuthenticator.valid?(account[:email], password)
|
1206
|
-
end
|
1207
|
-
end
|
1208
|
-
```
|
1209
|
-
```rb
|
1210
|
-
# app/lib/rodauth_app.rb
|
1211
977
|
class RodauthApp < Rodauth::Rails::App
|
1212
|
-
|
1213
|
-
#
|
1214
|
-
auth_class_eval do
|
1215
|
-
include RodauthLdap
|
1216
|
-
end
|
1217
|
-
# ...
|
978
|
+
route do |r|
|
979
|
+
# called before each request
|
1218
980
|
end
|
1219
981
|
end
|
1220
982
|
```
|
1221
983
|
|
1222
|
-
|
984
|
+
#### Routing prefix
|
1223
985
|
|
1224
|
-
|
986
|
+
If you use a routing prefix, you don't need to add a call to `r.on` like with
|
987
|
+
vanilla Rodauth, as `r.rodauth` has been modified to automatically route the
|
988
|
+
prefix.
|
1225
989
|
|
1226
990
|
```rb
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
class AuthenticationTest < ActionDispatch::SystemTestCase
|
1231
|
-
include ActiveJob::TestHelper
|
1232
|
-
driven_by :rack_test
|
1233
|
-
|
1234
|
-
test "creating and verifying an account" do
|
1235
|
-
create_account
|
1236
|
-
assert_match "An email has been sent to you with a link to verify your account", page.text
|
1237
|
-
|
1238
|
-
verify_account
|
1239
|
-
assert_match "Your account has been verified", page.text
|
1240
|
-
end
|
1241
|
-
|
1242
|
-
test "logging in and logging out" do
|
1243
|
-
create_account(verify: true)
|
1244
|
-
|
1245
|
-
logout
|
1246
|
-
assert_match "You have been logged out", page.text
|
1247
|
-
|
1248
|
-
login
|
1249
|
-
assert_match "You have been logged in", page.text
|
1250
|
-
end
|
1251
|
-
|
1252
|
-
private
|
1253
|
-
|
1254
|
-
def create_account(email: "user@example.com", password: "secret", verify: false)
|
1255
|
-
visit "/create-account"
|
1256
|
-
fill_in "Login", with: email
|
1257
|
-
fill_in "Password", with: password
|
1258
|
-
fill_in "Confirm Password", with: password
|
1259
|
-
click_on "Create Account"
|
1260
|
-
verify_account if verify
|
1261
|
-
end
|
1262
|
-
|
1263
|
-
def verify_account
|
1264
|
-
perform_enqueued_jobs # run enqueued email deliveries
|
1265
|
-
email = ActionMailer::Base.deliveries.last
|
1266
|
-
verify_account_link = email.body.to_s[/\S+verify-account\S+/]
|
1267
|
-
visit verify_account_link
|
1268
|
-
click_on "Verify Account"
|
1269
|
-
end
|
1270
|
-
|
1271
|
-
def login(email: "user@example.com", password: "secret")
|
1272
|
-
visit "/login"
|
1273
|
-
fill_in "Login", with: email
|
1274
|
-
fill_in "Password", with: password
|
1275
|
-
click_on "Login"
|
991
|
+
class RodauthApp < Rodauth::Rails::App
|
992
|
+
configure do
|
993
|
+
prefix "/user"
|
1276
994
|
end
|
1277
995
|
|
1278
|
-
|
1279
|
-
|
1280
|
-
click_on "Logout"
|
996
|
+
route do |r|
|
997
|
+
r.rodauth # no need to wrap with `r.on("user") { ... }`
|
1281
998
|
end
|
1282
999
|
end
|
1283
1000
|
```
|
1284
1001
|
|
1285
|
-
|
1286
|
-
|
1002
|
+
### Auth class
|
1003
|
+
|
1004
|
+
The [`Rodauth::Rails::Auth`](/lib/rodauth/rails/auth.rb) class is a subclass of
|
1005
|
+
`Rodauth::Auth`, which preloads the `rails` rodauth feature, sets [HMAC] secret to
|
1006
|
+
Rails' secret key base, and modifies some [configuration defaults](#rodauth-defaults).
|
1287
1007
|
|
1288
1008
|
```rb
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1292
|
-
class AuthenticationTest < ActionDispatch::IntegrationTest
|
1293
|
-
test "creating and verifying an account" do
|
1294
|
-
create_account
|
1295
|
-
assert_response :success
|
1296
|
-
assert_match "An email has been sent to you with a link to verify your account", JSON.parse(body)["success"]
|
1297
|
-
|
1298
|
-
verify_account
|
1299
|
-
assert_response :success
|
1300
|
-
assert_match "Your account has been verified", JSON.parse(body)["success"]
|
1009
|
+
class RodauthMain < Rodauth::Rails::Auth
|
1010
|
+
configure do
|
1011
|
+
# authentication configuration
|
1301
1012
|
end
|
1013
|
+
end
|
1014
|
+
```
|
1302
1015
|
|
1303
|
-
|
1304
|
-
create_account(verify: true)
|
1016
|
+
### Rodauth feature
|
1305
1017
|
|
1306
|
-
|
1307
|
-
|
1308
|
-
assert_match "You have been logged out", JSON.parse(body)["success"]
|
1018
|
+
The [`rails`](/lib/rodauth/rails/feature.rb) Rodauth feature loaded by
|
1019
|
+
`Rodauth::Rails::Auth` provides the main part of the Rails integration for Rodauth:
|
1309
1020
|
|
1310
|
-
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1021
|
+
* uses Action View for template rendering
|
1022
|
+
* uses Action Dispatch for CSRF protection
|
1023
|
+
* runs Action Controller callbacks and rescue from blocks around Rodauth requests
|
1024
|
+
* uses Action Mailer to create and deliver emails
|
1025
|
+
* uses Action Controller instrumentation around Rodauth requests
|
1026
|
+
* uses Action Mailer's default URL options when calling Rodauth outside of a request
|
1314
1027
|
|
1315
|
-
|
1028
|
+
### Controller
|
1316
1029
|
|
1317
|
-
|
1318
|
-
|
1319
|
-
verify_account if verify
|
1320
|
-
end
|
1030
|
+
The Rodauth app stores the `Rodauth::Rails::Auth` instances in the Rack env
|
1031
|
+
hash, which is then available in your Rails app:
|
1321
1032
|
|
1322
|
-
|
1323
|
-
|
1324
|
-
|
1325
|
-
|
1326
|
-
post "/verify-account", as: :json, params: { key: verify_account_key }
|
1327
|
-
end
|
1033
|
+
```rb
|
1034
|
+
request.env["rodauth"] #=> #<RodauthMain>
|
1035
|
+
request.env["rodauth.admin"] #=> #<RodauthAdmin> (if using multiple configurations)
|
1036
|
+
```
|
1328
1037
|
|
1329
|
-
|
1330
|
-
|
1331
|
-
end
|
1038
|
+
For convenience, this object can be accessed via the `#rodauth` method in views
|
1039
|
+
and controllers:
|
1332
1040
|
|
1333
|
-
|
1334
|
-
|
1041
|
+
```rb
|
1042
|
+
class MyController < ApplicationController
|
1043
|
+
def my_action
|
1044
|
+
rodauth #=> #<RodauthMain>
|
1045
|
+
rodauth(:admin) #=> #<RodauthAdmin> (if using multiple configurations)
|
1335
1046
|
end
|
1336
1047
|
end
|
1337
1048
|
```
|
1049
|
+
```erb
|
1050
|
+
<% rodauth #=> #<RodauthMain> %>
|
1051
|
+
<% rodauth(:admin) #=> #<RodauthAdmin> (if using multiple configurations) %>
|
1052
|
+
```
|
1338
1053
|
|
1339
|
-
|
1340
|
-
queue adapter to `:test` or `:inline`:
|
1054
|
+
### Sequel
|
1341
1055
|
|
1342
|
-
|
1343
|
-
|
1344
|
-
|
1345
|
-
|
1346
|
-
|
1347
|
-
|
1348
|
-
|
1349
|
-
|
1056
|
+
Rodauth uses the [Sequel] library for database interaction, which offers
|
1057
|
+
powerful APIs for building advanced queries (it supports SQL expressions,
|
1058
|
+
database-agnostic date arithmetic, SQL function calls).
|
1059
|
+
|
1060
|
+
If you're using Active Record in your application, the `rodauth:install`
|
1061
|
+
generator automatically configures Sequel to reuse ActiveRecord's database
|
1062
|
+
connection, using the [sequel-activerecord_connection] gem.
|
1063
|
+
|
1064
|
+
This means that, from the usage perspective, Sequel can be considered just
|
1065
|
+
as an implementation detail of Rodauth.
|
1350
1066
|
|
1351
1067
|
## Rodauth defaults
|
1352
1068
|
|
@@ -1393,16 +1109,15 @@ end
|
|
1393
1109
|
|
1394
1110
|
The recommended [Rodauth migration] stores possible account status values in a
|
1395
1111
|
separate table, and creates a foreign key on the accounts table, which ensures
|
1396
|
-
only a valid status value will be persisted.
|
1112
|
+
only a valid status value will be persisted. Unfortunately, this doesn't work
|
1113
|
+
when the database is restored from the schema file, in which case the account
|
1114
|
+
statuses table will be empty. This happens in tests by default, but it's also
|
1115
|
+
not unusual to do it in development.
|
1397
1116
|
|
1398
|
-
|
1399
|
-
|
1400
|
-
|
1401
|
-
|
1402
|
-
To address this, rodauth-rails modifies the setup to store account status text
|
1403
|
-
directly in the accounts table. If you're worried about invalid status values
|
1404
|
-
creeping in, you may use enums instead. Alternatively, you can always go back
|
1405
|
-
to the setup recommended by Rodauth.
|
1117
|
+
To address this, rodauth-rails uses a `status` column without a separate table.
|
1118
|
+
If you're worried about invalid status values creeping in, you may use enums
|
1119
|
+
instead. Alternatively, you can always go back to the setup recommended by
|
1120
|
+
Rodauth.
|
1406
1121
|
|
1407
1122
|
```rb
|
1408
1123
|
# in the migration:
|
@@ -1418,14 +1133,13 @@ create_table :accounts do |t|
|
|
1418
1133
|
end
|
1419
1134
|
```
|
1420
1135
|
```diff
|
1421
|
-
|
1422
|
-
|
1423
|
-
|
1424
|
-
-
|
1425
|
-
|
1426
|
-
|
1427
|
-
|
1428
|
-
end
|
1136
|
+
class RodauthMain < Rodauth::Rails::Auth
|
1137
|
+
configure do
|
1138
|
+
# ...
|
1139
|
+
- account_status_column :status
|
1140
|
+
# ...
|
1141
|
+
end
|
1142
|
+
end
|
1429
1143
|
```
|
1430
1144
|
|
1431
1145
|
### Deadline values
|
@@ -1454,7 +1168,6 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
|
|
1454
1168
|
[Rodauth]: https://github.com/jeremyevans/rodauth
|
1455
1169
|
[Sequel]: https://github.com/jeremyevans/sequel
|
1456
1170
|
[feature documentation]: http://rodauth.jeremyevans.net/documentation.html
|
1457
|
-
[JWT gem]: https://github.com/jwt/ruby-jwt
|
1458
1171
|
[Bootstrap]: https://getbootstrap.com/
|
1459
1172
|
[Roda]: http://roda.jeremyevans.net/
|
1460
1173
|
[HMAC]: http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-HMAC
|
@@ -1463,7 +1176,6 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
|
|
1463
1176
|
[sequel-activerecord_connection]: https://github.com/janko/sequel-activerecord_connection
|
1464
1177
|
[plugin options]: http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-Plugin+Options
|
1465
1178
|
[hmac]: http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-HMAC
|
1466
|
-
[OmniAuth]: https://github.com/omniauth/omniauth
|
1467
1179
|
[otp]: http://rodauth.jeremyevans.net/rdoc/files/doc/otp_rdoc.html
|
1468
1180
|
[sms_codes]: http://rodauth.jeremyevans.net/rdoc/files/doc/sms_codes_rdoc.html
|
1469
1181
|
[recovery_codes]: http://rodauth.jeremyevans.net/rdoc/files/doc/recovery_codes_rdoc.html
|
@@ -1484,3 +1196,6 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
|
|
1484
1196
|
[internal_request]: http://rodauth.jeremyevans.net/rdoc/files/doc/internal_request_rdoc.html
|
1485
1197
|
[composite_primary_keys]: https://github.com/composite-primary-keys/composite_primary_keys
|
1486
1198
|
[path_class_methods]: https://rodauth.jeremyevans.net/rdoc/files/doc/path_class_methods_rdoc.html
|
1199
|
+
[account types]: https://github.com/janko/rodauth-rails/wiki/Account-Types
|
1200
|
+
[custom mailer worker]: https://github.com/janko/rodauth-rails/wiki/Custom-Mailer-Worker
|
1201
|
+
[Turbo]: https://turbo.hotwired.dev/
|