rodauth-rails 0.18.1 → 1.2.1

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