rodauth-rails 0.8.2 → 0.12.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 +52 -0
- data/README.md +453 -223
- data/lib/generators/rodauth/install_generator.rb +26 -15
- data/lib/generators/rodauth/migration/base.erb +2 -2
- data/lib/generators/rodauth/templates/app/lib/rodauth_app.rb +50 -49
- data/lib/generators/rodauth/templates/app/mailers/rodauth_mailer.rb +3 -3
- data/lib/generators/rodauth/templates/app/views/rodauth/_global_logout_field.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_confirm_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_display.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_new_password_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_otp_auth_code_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_password_confirm_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_password_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_recovery_code_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_sms_code_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_sms_phone_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_submit.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/otp_setup.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/remember.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_mailer/unlock_account.text.erb +1 -1
- data/lib/rodauth/rails.rb +20 -0
- data/lib/rodauth/rails/app.rb +23 -31
- data/lib/rodauth/rails/app/flash.rb +7 -11
- data/lib/rodauth/rails/app/middleware.rb +20 -10
- data/lib/rodauth/rails/auth.rb +40 -0
- data/lib/rodauth/rails/controller_methods.rb +1 -5
- data/lib/rodauth/rails/feature.rb +17 -202
- data/lib/rodauth/rails/feature/base.rb +62 -0
- data/lib/rodauth/rails/feature/callbacks.rb +61 -0
- data/lib/rodauth/rails/feature/csrf.rb +65 -0
- data/lib/rodauth/rails/feature/email.rb +30 -0
- data/lib/rodauth/rails/feature/instrumentation.rb +71 -0
- data/lib/rodauth/rails/feature/render.rb +41 -0
- data/lib/rodauth/rails/version.rb +1 -1
- data/rodauth-rails.gemspec +1 -1
- metadata +15 -9
- data/lib/generators/rodauth/mailer_generator.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27d48e6bf86cf81b33f6b0282048c2fb6f16ec6602136e18de6ede5120cfd808
|
4
|
+
data.tar.gz: 2f79498ff25a42131a5ead77f3d4adf05152bc85f271c8b985f0f9fa8c04b503
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a0c44b54d304d4dfb2a205d41a5ac360e483209229fa49e767f9eaa595434b291661e283110f3ee39a8fbc17a4ad2d82f90a6e4545ca4112852ee50a35aa8da
|
7
|
+
data.tar.gz: 52bb16489dd97777f7ff2359be9014a2c55c7537b8d4449621eb95ef3b7f0030febcd06caa811d406db1fb24fcc884d22c7460a36a94255133ce261a2bbeb68d
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,55 @@
|
|
1
|
+
## 0.12.0 (2021-05-15)
|
2
|
+
|
3
|
+
* Include total view render time in logs for Rodauth requests (@janko)
|
4
|
+
|
5
|
+
* Instrument redirects (@janko)
|
6
|
+
|
7
|
+
* Instrument Rodauth requests on `action_controller` namespace (@janko)
|
8
|
+
|
9
|
+
* Update templates for Boostrap 5 compatibility (@janko)
|
10
|
+
|
11
|
+
* Log request parameters for Rodauth requests (@janko)
|
12
|
+
|
13
|
+
## 0.11.0 (2021-05-06)
|
14
|
+
|
15
|
+
* Add controller-like logging for requests to Rodauth endpoints (@janko)
|
16
|
+
|
17
|
+
* Add `#rails_routes` to Roda and Rodauth instance for accessing Rails route helpers (@janko)
|
18
|
+
|
19
|
+
* Add `#rails_request` to Roda and Rodauth instance for retrieving an `ActionDispatch::Request` instance (@janko)
|
20
|
+
|
21
|
+
## 0.10.0 (2021-03-23)
|
22
|
+
|
23
|
+
* Add `Rodauth::Rails::Auth` superclass for moving configurations into separate files (@janko)
|
24
|
+
|
25
|
+
* Load the `pass` Roda plugin and recommend calling `r.pass` on prefixed routes (@janko)
|
26
|
+
|
27
|
+
* Improve Roda middleware inspect output (@janko)
|
28
|
+
|
29
|
+
* Create `RodauthMailer` and email templates in `rodauth:install`, and remove `rodauth:mailer` (@janko)
|
30
|
+
|
31
|
+
* Raise `KeyError` in `#rodauth` method when the Rodauth instance doesn't exist (@janko)
|
32
|
+
|
33
|
+
* Add `Rodauth::Rails.authenticated` routing constraint for requiring authentication (@janko)
|
34
|
+
|
35
|
+
## 0.9.1 (2021-02-10)
|
36
|
+
|
37
|
+
* Fix flash integration being loaded for API-only apps and causing an error (@dmitryzuev)
|
38
|
+
|
39
|
+
* Change account status column default to `unverified` in migration to match Rodauth's default (@basabin54)
|
40
|
+
|
41
|
+
## 0.9.0 (2021-02-07)
|
42
|
+
|
43
|
+
* Load Roda's JSON support by default, so that enabling `json`/`jwt` feature is all that's needed (@janko)
|
44
|
+
|
45
|
+
* Bump Rodauth dependency to 2.9+ (@janko)
|
46
|
+
|
47
|
+
* Add `--json` option for `rodauth:install` generator for configuring `json` feature (@janko)
|
48
|
+
|
49
|
+
* Add `--jwt` option for `rodauth:install` generator for configuring `jwt` feature (@janko)
|
50
|
+
|
51
|
+
* Remove the `--api` option from `rodauth:install` generator (@janko)
|
52
|
+
|
1
53
|
## 0.8.2 (2021-01-10)
|
2
54
|
|
3
55
|
* Reset Rails session on `#clear_session`, protecting from potential session fixation attacks (@janko)
|
data/README.md
CHANGED
@@ -14,15 +14,16 @@ Articles:
|
|
14
14
|
* [Rodauth: A Refreshing Authentication Solution for Ruby](https://janko.io/rodauth-a-refreshing-authentication-solution-for-ruby/)
|
15
15
|
* [Adding Authentication in Rails with Rodauth](https://janko.io/adding-authentication-in-rails-with-rodauth/)
|
16
16
|
* [Adding Multifactor Authentication in Rails with Rodauth](https://janko.io/adding-multifactor-authentication-in-rails-with-rodauth/)
|
17
|
+
* [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)
|
17
18
|
|
18
19
|
## Why Rodauth?
|
19
20
|
|
20
21
|
There are already several popular authentication solutions for Rails (Devise,
|
21
|
-
Sorcery, Clearance, Authlogic), so why would you choose Rodauth?
|
22
|
-
|
22
|
+
Sorcery, Clearance, Authlogic), so why would you choose Rodauth? Here are some
|
23
|
+
of the advantages that stand out for me:
|
23
24
|
|
24
25
|
* multifactor authentication ([TOTP][otp], [SMS codes][sms_codes], [recovery codes][recovery_codes], [WebAuthn][webauthn])
|
25
|
-
* standardized [JSON API support][
|
26
|
+
* standardized [JSON API support][json] for every feature (including [JWT][jwt])
|
26
27
|
* enterprise security features ([password complexity][password_complexity], [disallow password reuse][disallow_password_reuse], [password expiration][password_expiration], [session expiration][session_expiration], [single session][single_session], [account expiration][account_expiration])
|
27
28
|
* [email authentication][email_auth] (aka "passwordless")
|
28
29
|
* [audit logging][audit_logging] (for any action)
|
@@ -32,6 +33,12 @@ it has many advantages over the mentioned alternatives:
|
|
32
33
|
* consistent before/after hooks around everything
|
33
34
|
* dedicated object encapsulating all authentication logic
|
34
35
|
|
36
|
+
One commmon concern is the fact that, unlike most other authentication
|
37
|
+
frameworks for Rails, Rodauth uses [Sequel] for database interaction instead of
|
38
|
+
Active Record. There are good reasons for this, and to make Rodauth work
|
39
|
+
smoothly alongside Active Record, rodauth-rails configures Sequel to [reuse
|
40
|
+
Active Record's database connection][sequel-activerecord_connection].
|
41
|
+
|
35
42
|
## Upgrading
|
36
43
|
|
37
44
|
### Upgrading to 0.7.0
|
@@ -54,7 +61,7 @@ documentation][hmac] for instructions on how to safely transition, or just set
|
|
54
61
|
Add the gem to your Gemfile:
|
55
62
|
|
56
63
|
```rb
|
57
|
-
gem "rodauth-rails", "~> 0.
|
64
|
+
gem "rodauth-rails", "~> 0.12"
|
58
65
|
|
59
66
|
# gem "jwt", require: false # for JWT feature
|
60
67
|
# gem "rotp", require: false # for OTP feature
|
@@ -73,118 +80,28 @@ $ rails generate rodauth:install
|
|
73
80
|
Or if you want Rodauth endpoints to be exposed via JSON API:
|
74
81
|
|
75
82
|
```sh
|
76
|
-
$ rails generate rodauth:install --
|
83
|
+
$ rails generate rodauth:install --json # regular authentication using the Rails session
|
84
|
+
# or
|
85
|
+
$ rails generate rodauth:install --jwt # token authentication via the "Authorization" header
|
77
86
|
$ bundle add jwt
|
78
87
|
```
|
79
88
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
* Rodauth initializer at `config/initializers/rodauth.rb`
|
84
|
-
* Sequel initializer at `config/initializers/sequel.rb` for ActiveRecord integration
|
85
|
-
* Rodauth app at `app/lib/rodauth_app.rb`
|
86
|
-
* Rodauth controller at `app/controllers/rodauth_controller.rb`
|
87
|
-
* Account model at `app/models/account.rb`
|
88
|
-
|
89
|
-
### Migration
|
90
|
-
|
91
|
-
The migration file creates tables required by Rodauth. You're encouraged to
|
92
|
-
review the migration, and modify it to only create tables for features you
|
93
|
-
intend to use.
|
94
|
-
|
95
|
-
```rb
|
96
|
-
# db/migrate/*_create_rodauth.rb
|
97
|
-
class CreateRodauth < ActiveRecord::Migration
|
98
|
-
def change
|
99
|
-
create_table :accounts do |t| ... end
|
100
|
-
create_table :account_password_hashes do |t| ... end
|
101
|
-
create_table :account_password_reset_keys do |t| ... end
|
102
|
-
create_table :account_verification_keys do |t| ... end
|
103
|
-
create_table :account_login_change_keys do |t| ... end
|
104
|
-
create_table :account_remember_keys do |t| ... end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
```
|
89
|
+
This generator will create a Rodauth app with common authentication features
|
90
|
+
enabled, a database migration with tables required by those features, a mailer
|
91
|
+
with default templates, and a few other files.
|
108
92
|
|
109
|
-
|
93
|
+
Feel free to remove any features you don't need, along with their corresponding
|
94
|
+
tables. Afterwards, run the migration:
|
110
95
|
|
111
|
-
```
|
96
|
+
```sh
|
112
97
|
$ rails db:migrate
|
113
98
|
```
|
114
99
|
|
115
|
-
### Rodauth initializer
|
116
|
-
|
117
|
-
The Rodauth initializer assigns the constant for your Rodauth app, which will
|
118
|
-
be called by the Rack middleware that's added in front of your Rails router.
|
119
|
-
|
120
|
-
```rb
|
121
|
-
# config/initializers/rodauth.rb
|
122
|
-
Rodauth::Rails.configure do |config|
|
123
|
-
config.app = "RodauthApp"
|
124
|
-
end
|
125
|
-
```
|
126
|
-
|
127
|
-
### Sequel initializer
|
128
|
-
|
129
|
-
Rodauth uses [Sequel] for database interaction. If you're using ActiveRecord,
|
130
|
-
an additional initializer will be created which configures Sequel to use the
|
131
|
-
ActiveRecord connection.
|
132
|
-
|
133
|
-
```rb
|
134
|
-
# config/initializers/sequel.rb
|
135
|
-
require "sequel/core"
|
136
|
-
|
137
|
-
# initialize Sequel and have it reuse Active Record's database connection
|
138
|
-
DB = Sequel.connect("postgresql://", extensions: :activerecord_connection)
|
139
|
-
```
|
140
|
-
|
141
|
-
### Rodauth app
|
142
|
-
|
143
|
-
Your Rodauth app is created in the `app/lib/` directory, and comes with a
|
144
|
-
default set of authentication features enabled, as well as extensive examples
|
145
|
-
on ways you can configure authentication behaviour.
|
146
|
-
|
147
|
-
```rb
|
148
|
-
# app/lib/rodauth_app.rb
|
149
|
-
class RodauthApp < Rodauth::Rails::App
|
150
|
-
configure do
|
151
|
-
# authentication configuration
|
152
|
-
end
|
153
|
-
|
154
|
-
route do |r|
|
155
|
-
# request handling
|
156
|
-
end
|
157
|
-
end
|
158
|
-
```
|
159
|
-
|
160
|
-
### Controller
|
161
|
-
|
162
|
-
Your Rodauth app will by default use `RodauthController` for view rendering,
|
163
|
-
CSRF protection, and running controller callbacks and rescue handlers around
|
164
|
-
Rodauth actions.
|
165
|
-
|
166
|
-
```rb
|
167
|
-
# app/controllers/rodauth_controller.rb
|
168
|
-
class RodauthController < ApplicationController
|
169
|
-
end
|
170
|
-
```
|
171
|
-
|
172
|
-
### Account model
|
173
|
-
|
174
|
-
Rodauth stores user accounts in the `accounts` table, so the generator will
|
175
|
-
also create an `Account` model for custom use.
|
176
|
-
|
177
|
-
```rb
|
178
|
-
# app/models/account.rb
|
179
|
-
class Account < ApplicationRecord
|
180
|
-
end
|
181
|
-
```
|
182
|
-
|
183
100
|
## Usage
|
184
101
|
|
185
102
|
### Routes
|
186
103
|
|
187
|
-
|
104
|
+
You can see the list of routes our Rodauth middleware handles:
|
188
105
|
|
189
106
|
```sh
|
190
107
|
$ rails rodauth:routes
|
@@ -206,7 +123,7 @@ Routes handled by RodauthApp:
|
|
206
123
|
/close-account rodauth.close_account_path
|
207
124
|
```
|
208
125
|
|
209
|
-
Using this information,
|
126
|
+
Using this information, you can add some basic authentication links to your
|
210
127
|
navigation header:
|
211
128
|
|
212
129
|
```erb
|
@@ -222,9 +139,22 @@ These routes are fully functional, feel free to visit them and interact with the
|
|
222
139
|
pages. The templates that ship with Rodauth aim to provide a complete
|
223
140
|
authentication experience, and the forms use [Bootstrap] markup.
|
224
141
|
|
142
|
+
Inside Rodauth configuration and the `route` block you can access Rails route
|
143
|
+
helpers through `#rails_routes`:
|
144
|
+
|
145
|
+
```rb
|
146
|
+
class RodauthApp < Rodauth::Rails::App
|
147
|
+
configure do
|
148
|
+
# ...
|
149
|
+
login_redirect { rails_routes.activity_path }
|
150
|
+
# ...
|
151
|
+
end
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
225
155
|
### Current account
|
226
156
|
|
227
|
-
To be able to fetch currently authenticated account,
|
157
|
+
To be able to fetch currently authenticated account, you can define a
|
228
158
|
`#current_account` method that fetches the account id from session and
|
229
159
|
retrieves the corresponding account record:
|
230
160
|
|
@@ -241,11 +171,11 @@ class ApplicationController < ActionController::Base
|
|
241
171
|
rodauth.logout
|
242
172
|
rodauth.login_required
|
243
173
|
end
|
244
|
-
helper_method :current_account
|
174
|
+
helper_method :current_account # skip if inheriting from ActionController::API
|
245
175
|
end
|
246
176
|
```
|
247
177
|
|
248
|
-
This allows
|
178
|
+
This allows you to access the current account in controllers and views:
|
249
179
|
|
250
180
|
```erb
|
251
181
|
<p>Authenticated as: <%= current_account.email %></p>
|
@@ -253,9 +183,9 @@ This allows us to access the current account in controllers and views:
|
|
253
183
|
|
254
184
|
### Requiring authentication
|
255
185
|
|
256
|
-
|
257
|
-
redirecting the user to the login page if they're not logged in.
|
258
|
-
in
|
186
|
+
You'll likely want to require authentication for certain parts of your app,
|
187
|
+
redirecting the user to the login page if they're not logged in. You can do this
|
188
|
+
in your Rodauth app's routing block, which helps keep the authentication logic
|
259
189
|
encapsulated:
|
260
190
|
|
261
191
|
```rb
|
@@ -274,7 +204,7 @@ class RodauthApp < Rodauth::Rails::App
|
|
274
204
|
end
|
275
205
|
```
|
276
206
|
|
277
|
-
|
207
|
+
You can also require authentication at the controller layer:
|
278
208
|
|
279
209
|
```rb
|
280
210
|
# app/controllers/application_controller.rb
|
@@ -299,15 +229,52 @@ class PostsController < ApplicationController
|
|
299
229
|
end
|
300
230
|
```
|
301
231
|
|
302
|
-
|
232
|
+
#### Routing constraints
|
233
|
+
|
234
|
+
In some cases it makes sense to require authentication at the Rails router
|
235
|
+
level. You can do this via the built-in `authenticated` routing constraint:
|
303
236
|
|
304
237
|
```rb
|
305
238
|
# config/routes.rb
|
306
239
|
Rails.application.routes.draw do
|
307
|
-
constraints
|
308
|
-
|
309
|
-
|
310
|
-
|
240
|
+
constraints Rodauth::Rails.authenticated do
|
241
|
+
# ... authenticated routes ...
|
242
|
+
end
|
243
|
+
end
|
244
|
+
```
|
245
|
+
|
246
|
+
If you want additional conditions, you can pass in a block, which is
|
247
|
+
called with the Rodauth instance:
|
248
|
+
|
249
|
+
```rb
|
250
|
+
# config/routes.rb
|
251
|
+
Rails.application.routes.draw do
|
252
|
+
# require multifactor authentication to be setup
|
253
|
+
constraints Rodauth::Rails.authenticated { |rodauth| rodauth.uses_two_factor_authentication? } do
|
254
|
+
# ...
|
255
|
+
end
|
256
|
+
end
|
257
|
+
```
|
258
|
+
|
259
|
+
You can specify the Rodauth configuration by passing the configuration name:
|
260
|
+
|
261
|
+
```rb
|
262
|
+
# config/routes.rb
|
263
|
+
Rails.application.routes.draw do
|
264
|
+
constraints Rodauth::Rails.authenticated(:admin) do
|
265
|
+
# ...
|
266
|
+
end
|
267
|
+
end
|
268
|
+
```
|
269
|
+
|
270
|
+
If you need something more custom, you can always create the routing constraint
|
271
|
+
manually:
|
272
|
+
|
273
|
+
```rb
|
274
|
+
# config/routes.rb
|
275
|
+
Rails.application.routes.draw do
|
276
|
+
constraints -> (r) { !r.env["rodauth"].logged_in? } do # or "rodauth.admin"
|
277
|
+
# routes when the user is not logged in
|
311
278
|
end
|
312
279
|
end
|
313
280
|
```
|
@@ -327,7 +294,7 @@ This will generate views for the default set of Rodauth features into the
|
|
327
294
|
`RodauthController`.
|
328
295
|
|
329
296
|
You can pass a list of Rodauth features to the generator to create views for
|
330
|
-
these features (this will not remove any existing views):
|
297
|
+
these features (this will not remove or overwrite any existing views):
|
331
298
|
|
332
299
|
```sh
|
333
300
|
$ rails generate rodauth:views login create_account lockout otp
|
@@ -375,58 +342,36 @@ end
|
|
375
342
|
|
376
343
|
### Mailer
|
377
344
|
|
378
|
-
|
379
|
-
|
345
|
+
The install generator will create `RodauthMailer` with default email templates,
|
346
|
+
and configure Rodauth features that send emails as part of the authentication
|
347
|
+
flow to use it.
|
380
348
|
|
381
349
|
```rb
|
382
|
-
# app/
|
383
|
-
class
|
384
|
-
|
385
|
-
configure do
|
350
|
+
# app/mailers/rodauth_mailer.rb
|
351
|
+
class RodauthMailer < ApplicationMailer
|
352
|
+
def verify_account(recipient, email_link)
|
386
353
|
# ...
|
387
|
-
|
388
|
-
|
389
|
-
email_subject_prefix "[MyApp] "
|
390
|
-
send_email(&:deliver_later)
|
354
|
+
end
|
355
|
+
def reset_password(recipient, email_link)
|
391
356
|
# ...
|
392
|
-
|
393
|
-
|
394
|
-
verify_account_email_body { "Verify your account by visting this link: #{verify_account_email_link}" }
|
357
|
+
end
|
358
|
+
def verify_login_change(recipient, old_login, new_login, email_link)
|
395
359
|
# ...
|
396
360
|
end
|
361
|
+
def password_changed(recipient)
|
362
|
+
# ...
|
363
|
+
end
|
364
|
+
# def email_auth(recipient, email_link)
|
365
|
+
# ...
|
366
|
+
# end
|
367
|
+
# def unlock_account(recipient, email_link)
|
368
|
+
# ...
|
369
|
+
# end
|
397
370
|
end
|
398
371
|
```
|
399
|
-
|
400
|
-
This is convenient when starting out, but eventually you might want to use your
|
401
|
-
own mailer. You can start by running the following command:
|
402
|
-
|
403
|
-
```sh
|
404
|
-
$ rails generate rodauth:mailer
|
405
|
-
```
|
406
|
-
|
407
|
-
This will create a `RodauthMailer` with the associated mailer views in
|
408
|
-
`app/views/rodauth_mailer` directory:
|
409
|
-
|
410
|
-
```rb
|
411
|
-
# app/mailers/rodauth_mailer.rb
|
412
|
-
class RodauthMailer < ApplicationMailer
|
413
|
-
def verify_account(recipient, email_link) ... end
|
414
|
-
def reset_password(recipient, email_link) ... end
|
415
|
-
def verify_login_change(recipient, old_login, new_login, email_link) ... end
|
416
|
-
def password_changed(recipient) ... end
|
417
|
-
# def email_auth(recipient, email_link) ... end
|
418
|
-
# def unlock_account(recipient, email_link) ... end
|
419
|
-
end
|
420
|
-
```
|
421
|
-
|
422
|
-
You can then uncomment the lines in your Rodauth configuration to have it call
|
423
|
-
your mailer. If you've enabled additional authentication features that send
|
424
|
-
emails, make sure to override their `create_*_email` methods as well.
|
425
|
-
|
426
372
|
```rb
|
427
373
|
# app/lib/rodauth_app.rb
|
428
374
|
class RodauthApp < Rodauth::Rails::App
|
429
|
-
# ...
|
430
375
|
configure do
|
431
376
|
# ...
|
432
377
|
create_reset_password_email do
|
@@ -456,10 +401,17 @@ class RodauthApp < Rodauth::Rails::App
|
|
456
401
|
end
|
457
402
|
```
|
458
403
|
|
459
|
-
This
|
460
|
-
|
461
|
-
|
462
|
-
|
404
|
+
This configuration calls `#deliver_later`, which uses Active Job to deliver
|
405
|
+
emails in a background job. It's generally recommended to send emails
|
406
|
+
asynchronously for better request throughput and the ability to retry
|
407
|
+
deliveries. However, if you want to send emails synchronously, modify the
|
408
|
+
configuration to call `#deliver_now` instead.
|
409
|
+
|
410
|
+
If you're using a background processing library without an Active Job adapter,
|
411
|
+
or a 3rd-party service for sending transactional emails, this two-phase API
|
412
|
+
might not be suitable. In this case, instead of overriding `#create_*_email`
|
413
|
+
and `#send_email`, override the `#send_*_email` methods instead, which are
|
414
|
+
required to send the email immediately.
|
463
415
|
|
464
416
|
### Migrations
|
465
417
|
|
@@ -481,6 +433,143 @@ class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration
|
|
481
433
|
end
|
482
434
|
```
|
483
435
|
|
436
|
+
### Multiple configurations
|
437
|
+
|
438
|
+
If you need to handle multiple types of accounts that require different
|
439
|
+
authentication logic, you can create additional configurations for them:
|
440
|
+
|
441
|
+
```rb
|
442
|
+
# app/lib/rodauth_app.rb
|
443
|
+
class RodauthApp < Rodauth::Rails::App
|
444
|
+
# primary configuration
|
445
|
+
configure do
|
446
|
+
# ...
|
447
|
+
end
|
448
|
+
|
449
|
+
# alternative configuration
|
450
|
+
configure(:admin) do
|
451
|
+
# ... enable features ...
|
452
|
+
prefix "/admin"
|
453
|
+
session_key_prefix "admin_"
|
454
|
+
remember_cookie_key "_admin_remember" # if using remember feature
|
455
|
+
|
456
|
+
# if you want separate tables
|
457
|
+
accounts_table :admin_accounts
|
458
|
+
password_hash_table :admin_account_password_hashes
|
459
|
+
# ...
|
460
|
+
end
|
461
|
+
|
462
|
+
route do |r|
|
463
|
+
r.rodauth
|
464
|
+
|
465
|
+
r.on "admin" do
|
466
|
+
r.rodauth(:admin)
|
467
|
+
r.pass # allow the Rails app to handle other "/admin/*" requests
|
468
|
+
end
|
469
|
+
|
470
|
+
# ...
|
471
|
+
end
|
472
|
+
end
|
473
|
+
```
|
474
|
+
|
475
|
+
Then in your application you can reference the secondary Rodauth instance:
|
476
|
+
|
477
|
+
```rb
|
478
|
+
rodauth(:admin).login_path #=> "/admin/login"
|
479
|
+
```
|
480
|
+
|
481
|
+
#### Named auth classes
|
482
|
+
|
483
|
+
A `configure` block inside `Rodauth::Rails::App` will internally create an
|
484
|
+
anonymous `Rodauth::Auth` subclass, and register it under the given name.
|
485
|
+
However, you can also define the auth classes explicitly, by creating
|
486
|
+
subclasses of `Rodauth::Rails::Auth`:
|
487
|
+
|
488
|
+
```rb
|
489
|
+
# app/lib/rodauth_main.rb
|
490
|
+
class RodauthMain < Rodauth::Rails::Auth
|
491
|
+
configure do
|
492
|
+
# ... main configuration ...
|
493
|
+
end
|
494
|
+
end
|
495
|
+
```
|
496
|
+
```rb
|
497
|
+
# app/lib/rodauth_admin.rb
|
498
|
+
class RodauthAdmin < Rodauth::Rails::Auth
|
499
|
+
configure do
|
500
|
+
# ...
|
501
|
+
prefix "/admin"
|
502
|
+
session_key_prefix "admin_"
|
503
|
+
# ...
|
504
|
+
end
|
505
|
+
end
|
506
|
+
```
|
507
|
+
```rb
|
508
|
+
# app/lib/rodauth_app.rb
|
509
|
+
class RodauthApp < Rodauth::Rails::App
|
510
|
+
configure RodauthMain
|
511
|
+
configure RodauthAdmin, :admin
|
512
|
+
# ...
|
513
|
+
end
|
514
|
+
```
|
515
|
+
|
516
|
+
This allows having each configuration in a dedicated file, and named constants
|
517
|
+
improve introspection and error messages. You can also use inheritance to share
|
518
|
+
common settings:
|
519
|
+
|
520
|
+
```rb
|
521
|
+
# app/lib/rodauth_base.rb
|
522
|
+
class RodauthBase < Rodauth::Rails::Auth
|
523
|
+
# common settings that can be shared between multiple configurations
|
524
|
+
configure do
|
525
|
+
enable :login, :logout
|
526
|
+
login_return_to_requested_location? true
|
527
|
+
logout_redirect "/"
|
528
|
+
# ...
|
529
|
+
end
|
530
|
+
end
|
531
|
+
```
|
532
|
+
```rb
|
533
|
+
# app/lib/rodauth_main.rb
|
534
|
+
class RodauthMain < RodauthBase # inherit common settings
|
535
|
+
configure do
|
536
|
+
# ... customize main ...
|
537
|
+
end
|
538
|
+
end
|
539
|
+
```
|
540
|
+
```rb
|
541
|
+
# app/lib/rodauth_admin.rb
|
542
|
+
class RodauthAdmin < RodauthBase # inherit common settings
|
543
|
+
configure do
|
544
|
+
# ... customize admin ...
|
545
|
+
end
|
546
|
+
end
|
547
|
+
```
|
548
|
+
|
549
|
+
Another benefit of explicit classes is that you can define custom methods
|
550
|
+
directly at the class level instead of inside an `auth_class_eval`:
|
551
|
+
|
552
|
+
```rb
|
553
|
+
# app/lib/rodauth_admin.rb
|
554
|
+
class RodauthAdmin < Rodauth::Rails::Auth
|
555
|
+
configure do
|
556
|
+
# ...
|
557
|
+
end
|
558
|
+
|
559
|
+
def superadmin?
|
560
|
+
Role.where(account_id: session_id, type: "superadmin").any?
|
561
|
+
end
|
562
|
+
end
|
563
|
+
```
|
564
|
+
```rb
|
565
|
+
# config/routes.rb
|
566
|
+
Rails.application.routes.draw do
|
567
|
+
constraints Rodauth::Rails.authenticated(:admin) { |rodauth| rodauth.superadmin? } do
|
568
|
+
mount Sidekiq::Web => "sidekiq"
|
569
|
+
end
|
570
|
+
end
|
571
|
+
```
|
572
|
+
|
484
573
|
### Calling controller methods
|
485
574
|
|
486
575
|
When using Rodauth before/after hooks or generally overriding your Rodauth
|
@@ -514,7 +603,7 @@ Rodauth operations outside of the request context. rodauth-rails gives you the
|
|
514
603
|
ability to retrieve the Rodauth instance:
|
515
604
|
|
516
605
|
```rb
|
517
|
-
rodauth = Rodauth::Rails.rodauth # or Rodauth::Rails.rodauth(:
|
606
|
+
rodauth = Rodauth::Rails.rodauth # or Rodauth::Rails.rodauth(:admin)
|
518
607
|
|
519
608
|
rodauth.login_url #=> "https://example.com/login"
|
520
609
|
rodauth.account_from_login("user@example.com") # loads user by email
|
@@ -523,7 +612,7 @@ rodauth.setup_account_verification
|
|
523
612
|
rodauth.close_account
|
524
613
|
```
|
525
614
|
|
526
|
-
This Rodauth instance will be initialized with basic Rack env that allows
|
615
|
+
This Rodauth instance will be initialized with basic Rack env that allows it
|
527
616
|
to generate URLs, using `config.action_mailer.default_url_options` options.
|
528
617
|
|
529
618
|
## How it works
|
@@ -545,8 +634,8 @@ The Rodauth app stores the `Rodauth::Auth` instance in the Rack env hash, which
|
|
545
634
|
is then available in your Rails app:
|
546
635
|
|
547
636
|
```rb
|
548
|
-
request.env["rodauth"]
|
549
|
-
request.env["rodauth.
|
637
|
+
request.env["rodauth"] #=> #<Rodauth::Auth>
|
638
|
+
request.env["rodauth.admin"] #=> #<Rodauth::Auth> (if using multiple configurations)
|
550
639
|
```
|
551
640
|
|
552
641
|
For convenience, this object can be accessed via the `#rodauth` method in views
|
@@ -555,14 +644,14 @@ and controllers:
|
|
555
644
|
```rb
|
556
645
|
class MyController < ApplicationController
|
557
646
|
def my_action
|
558
|
-
rodauth
|
559
|
-
rodauth(:
|
647
|
+
rodauth #=> #<Rodauth::Auth>
|
648
|
+
rodauth(:admin) #=> #<Rodauth::Auth> (if using multiple configurations)
|
560
649
|
end
|
561
650
|
end
|
562
651
|
```
|
563
652
|
```erb
|
564
|
-
<% rodauth
|
565
|
-
<% rodauth(:
|
653
|
+
<% rodauth #=> #<Rodauth::Auth> %>
|
654
|
+
<% rodauth(:admin) #=> #<Rodauth::Auth> (if using multiple configurations) %>
|
566
655
|
```
|
567
656
|
|
568
657
|
### App
|
@@ -584,7 +673,7 @@ any additional [plugin options].
|
|
584
673
|
class RodauthApp < Rodauth::Rails::App
|
585
674
|
configure { ... } # defining default Rodauth configuration
|
586
675
|
configure(json: true) { ... } # passing options to the Rodauth plugin
|
587
|
-
configure(:
|
676
|
+
configure(:admin) { ... } # defining multiple Rodauth configurations
|
588
677
|
end
|
589
678
|
```
|
590
679
|
|
@@ -619,15 +708,32 @@ function calls).
|
|
619
708
|
|
620
709
|
If ActiveRecord is used in the application, the `rodauth:install` generator
|
621
710
|
will have automatically configured Sequel to reuse ActiveRecord's database
|
622
|
-
connection
|
711
|
+
connection, using the [sequel-activerecord_connection] gem.
|
623
712
|
|
624
713
|
This means that, from the usage perspective, Sequel can be considered just
|
625
714
|
as an implementation detail of Rodauth.
|
626
715
|
|
627
716
|
## JSON API
|
628
717
|
|
629
|
-
|
630
|
-
|
718
|
+
To make Rodauth endpoints accessible via JSON API, enable the [`json`][json]
|
719
|
+
feature:
|
720
|
+
|
721
|
+
```rb
|
722
|
+
# app/lib/rodauth_app.rb
|
723
|
+
class RodauthApp < Rodauth::Rails::App
|
724
|
+
configure do
|
725
|
+
# ...
|
726
|
+
enable :json
|
727
|
+
only_json? true # accept only JSON requests (optional)
|
728
|
+
# ...
|
729
|
+
end
|
730
|
+
end
|
731
|
+
```
|
732
|
+
|
733
|
+
This will store account session data into the Rails session. If you rather want
|
734
|
+
stateless token-based authentication via the `Authorization` header, enable the
|
735
|
+
[`jwt`][jwt] feature (which builds on top of the `json` feature) and add the
|
736
|
+
[JWT gem] to the Gemfile:
|
631
737
|
|
632
738
|
```sh
|
633
739
|
$ bundle add jwt
|
@@ -635,23 +741,33 @@ $ bundle add jwt
|
|
635
741
|
```rb
|
636
742
|
# app/lib/rodauth_app.rb
|
637
743
|
class RodauthApp < Rodauth::Rails::App
|
638
|
-
configure
|
744
|
+
configure do
|
639
745
|
# ...
|
640
746
|
enable :jwt
|
641
|
-
|
642
|
-
|
747
|
+
jwt_secret "<YOUR_SECRET_KEY>" # store the JWT secret in a safe place
|
748
|
+
only_json? true # accept only JSON requests (optional)
|
643
749
|
# ...
|
644
750
|
end
|
645
751
|
end
|
646
752
|
```
|
647
753
|
|
648
|
-
|
649
|
-
|
650
|
-
:only` to `json: true`.
|
754
|
+
If you need Cross-Origin Resource Sharing and/or JWT refresh tokens, enable the
|
755
|
+
corresponding Rodauth features and create the necessary tables:
|
651
756
|
|
652
|
-
|
653
|
-
|
654
|
-
|
757
|
+
```sh
|
758
|
+
$ rails generate rodauth:migration jwt_refresh
|
759
|
+
$ rails db:migrate
|
760
|
+
```
|
761
|
+
```rb
|
762
|
+
# app/lib/rodauth_app.rb
|
763
|
+
class RodauthApp < Rodauth::Rails::App
|
764
|
+
configure do
|
765
|
+
# ...
|
766
|
+
enable :jwt, :jwt_cors, :jwt_refresh
|
767
|
+
# ...
|
768
|
+
end
|
769
|
+
end
|
770
|
+
```
|
655
771
|
|
656
772
|
## OmniAuth
|
657
773
|
|
@@ -709,7 +825,8 @@ end
|
|
709
825
|
<%= link_to "Login via Facebook", "/auth/facebook" %>
|
710
826
|
```
|
711
827
|
|
712
|
-
|
828
|
+
Finally, let's implement the OmniAuth callback endpoint on our Rodauth
|
829
|
+
controller:
|
713
830
|
|
714
831
|
```rb
|
715
832
|
# config/routes.rb
|
@@ -745,7 +862,7 @@ class RodauthController < ApplicationController
|
|
745
862
|
|
746
863
|
# create new account if it doesn't exist
|
747
864
|
unless account
|
748
|
-
account = Account.create!(email: auth["info"]["email"])
|
865
|
+
account = Account.create!(email: auth["info"]["email"], status: rodauth.account_open_status_value)
|
749
866
|
end
|
750
867
|
|
751
868
|
# create new identity if it doesn't exist
|
@@ -762,11 +879,8 @@ end
|
|
762
879
|
|
763
880
|
## Configuring
|
764
881
|
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
The `rails` feature rodauth-rails loads is customizable as well, here is the
|
769
|
-
list of its configuration methods:
|
882
|
+
The `rails` feature rodauth-rails loads provides the following configuration
|
883
|
+
methods:
|
770
884
|
|
771
885
|
| Name | Description |
|
772
886
|
| :---- | :---------- |
|
@@ -793,21 +907,27 @@ Rodauth::Rails.configure do |config|
|
|
793
907
|
end
|
794
908
|
```
|
795
909
|
|
910
|
+
For the list of configuration methods provided by Rodauth, see the [feature
|
911
|
+
documentation].
|
912
|
+
|
796
913
|
## Custom extensions
|
797
914
|
|
798
915
|
When developing custom extensions for Rodauth inside your Rails project, it's
|
799
|
-
better to use plain modules
|
800
|
-
feature
|
916
|
+
probably better to use plain modules, at least in the beginning, as Rodauth
|
917
|
+
feature design doesn't yet work well with Zeitwerk reloading.
|
918
|
+
|
919
|
+
Here is an example of an LDAP authentication extension that uses the
|
920
|
+
[simple_ldap_authenticator] gem.
|
801
921
|
|
802
922
|
```rb
|
803
|
-
# app/lib/
|
804
|
-
module
|
805
|
-
def
|
806
|
-
|
923
|
+
# app/lib/rodauth_ldap.rb
|
924
|
+
module RodauthLdap
|
925
|
+
def require_bcrypt?
|
926
|
+
false
|
807
927
|
end
|
808
928
|
|
809
|
-
def
|
810
|
-
|
929
|
+
def password_match?(password)
|
930
|
+
SimpleLdapAuthenticator.valid?(account[:email], password)
|
811
931
|
end
|
812
932
|
end
|
813
933
|
```
|
@@ -817,7 +937,7 @@ class RodauthApp < Rodauth::Rails::App
|
|
817
937
|
configure do
|
818
938
|
# ...
|
819
939
|
auth_class_eval do
|
820
|
-
include
|
940
|
+
include RodauthLdap
|
821
941
|
end
|
822
942
|
# ...
|
823
943
|
end
|
@@ -826,48 +946,156 @@ end
|
|
826
946
|
|
827
947
|
## Testing
|
828
948
|
|
829
|
-
|
830
|
-
authentication flow with tools like Capybara, and to not use any stubbing.
|
831
|
-
|
832
|
-
In functional and integration tests you can just make requests to Rodauth
|
833
|
-
routes:
|
949
|
+
System (browser) tests for Rodauth actions could look something like this:
|
834
950
|
|
835
951
|
```rb
|
836
|
-
# test/
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
952
|
+
# test/system/authentication_test.rb
|
953
|
+
require "test_helper"
|
954
|
+
|
955
|
+
class AuthenticationTest < ActionDispatch::SystemTestCase
|
956
|
+
include ActiveJob::TestHelper
|
957
|
+
driven_by :rack_test
|
958
|
+
|
959
|
+
test "creating and verifying an account" do
|
960
|
+
create_account
|
961
|
+
assert_match "An email has been sent to you with a link to verify your account", page.text
|
962
|
+
|
963
|
+
verify_account
|
964
|
+
assert_match "Your account has been verified", page.text
|
965
|
+
end
|
966
|
+
|
967
|
+
test "logging in and logging out" do
|
968
|
+
create_account(verify: true)
|
969
|
+
|
970
|
+
logout
|
971
|
+
assert_match "You have been logged out", page.text
|
841
972
|
|
842
973
|
login
|
843
|
-
|
974
|
+
assert_match "You have been logged in", page.text
|
975
|
+
end
|
976
|
+
|
977
|
+
private
|
978
|
+
|
979
|
+
def create_account(email: "user@example.com", password: "secret", verify: false)
|
980
|
+
visit "/create-account"
|
981
|
+
fill_in "Login", with: email
|
982
|
+
fill_in "Password", with: password
|
983
|
+
fill_in "Confirm Password", with: password
|
984
|
+
click_on "Create Account"
|
985
|
+
verify_account if verify
|
986
|
+
end
|
987
|
+
|
988
|
+
def verify_account
|
989
|
+
perform_enqueued_jobs # run enqueued email deliveries
|
990
|
+
email = ActionMailer::Base.deliveries.last
|
991
|
+
verify_account_link = email.body.to_s[/\S+verify-account\S+/]
|
992
|
+
visit verify_account_link
|
993
|
+
click_on "Verify Account"
|
994
|
+
end
|
995
|
+
|
996
|
+
def login(email: "user@example.com", password: "secret")
|
997
|
+
visit "/login"
|
998
|
+
fill_in "Login", with: email
|
999
|
+
fill_in "Password", with: password
|
1000
|
+
click_on "Login"
|
1001
|
+
end
|
1002
|
+
|
1003
|
+
def logout
|
1004
|
+
visit "/logout"
|
1005
|
+
click_on "Logout"
|
1006
|
+
end
|
1007
|
+
end
|
1008
|
+
```
|
1009
|
+
|
1010
|
+
While request tests in JSON API mode with JWT tokens could look something like
|
1011
|
+
this:
|
1012
|
+
|
1013
|
+
```rb
|
1014
|
+
# test/integration/authentication_test.rb
|
1015
|
+
require "test_helper"
|
1016
|
+
|
1017
|
+
class AuthenticationTest < ActionDispatch::IntegrationTest
|
1018
|
+
test "creating and verifying an account" do
|
1019
|
+
create_account
|
844
1020
|
assert_response :success
|
1021
|
+
assert_match "An email has been sent to you with a link to verify your account", JSON.parse(body)["success"]
|
1022
|
+
|
1023
|
+
verify_account
|
1024
|
+
assert_response :success
|
1025
|
+
assert_match "Your account has been verified", JSON.parse(body)["success"]
|
1026
|
+
end
|
1027
|
+
|
1028
|
+
test "logging in and logging out" do
|
1029
|
+
create_account(verify: true)
|
845
1030
|
|
846
1031
|
logout
|
847
|
-
|
1032
|
+
assert_response :success
|
1033
|
+
assert_match "You have been logged out", JSON.parse(body)["success"]
|
1034
|
+
|
1035
|
+
login
|
1036
|
+
assert_response :success
|
1037
|
+
assert_match "You have been logged in", JSON.parse(body)["success"]
|
848
1038
|
end
|
849
1039
|
|
850
1040
|
private
|
851
1041
|
|
852
|
-
def
|
853
|
-
post "/create-account", params: {
|
854
|
-
|
855
|
-
|
856
|
-
"password-confirm" => password,
|
857
|
-
}
|
1042
|
+
def create_account(email: "user@example.com", password: "secret", verify: false)
|
1043
|
+
post "/create-account", as: :json, params: { login: email, password: password, "password-confirm": password }
|
1044
|
+
verify_account if verify
|
1045
|
+
end
|
858
1046
|
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
1047
|
+
def verify_account
|
1048
|
+
perform_enqueued_jobs # run enqueued email deliveries
|
1049
|
+
email = ActionMailer::Base.deliveries.last
|
1050
|
+
verify_account_key = email.body.to_s[/verify-account\?key=(\S+)/, 1]
|
1051
|
+
post "/verify-account", as: :json, params: { key: verify_account_key }
|
1052
|
+
end
|
1053
|
+
|
1054
|
+
def login(email: "user@example.com", password: "secret")
|
1055
|
+
post "/login", as: :json, params: { login: email, password: password }
|
863
1056
|
end
|
864
1057
|
|
865
1058
|
def logout
|
866
|
-
post "/logout"
|
1059
|
+
post "/logout", as: :json, headers: { "Authorization" => headers["Authorization"] }
|
867
1060
|
end
|
868
1061
|
end
|
869
1062
|
```
|
870
1063
|
|
1064
|
+
If you're delivering emails in the background, make sure to set Active Job
|
1065
|
+
queue adapter to `:test` or `:inline`:
|
1066
|
+
|
1067
|
+
```rb
|
1068
|
+
# config/environments/test.rb
|
1069
|
+
Rails.application.configure do |config|
|
1070
|
+
# ...
|
1071
|
+
config.active_job.queue_adapter = :test # or :inline
|
1072
|
+
# ...
|
1073
|
+
end
|
1074
|
+
```
|
1075
|
+
|
1076
|
+
If you need to create an account record with a password directly, you can do it
|
1077
|
+
as follows:
|
1078
|
+
|
1079
|
+
```rb
|
1080
|
+
# app/models/account.rb
|
1081
|
+
class Account < ApplicationRecord
|
1082
|
+
has_one :password_hash, foreign_key: :id
|
1083
|
+
end
|
1084
|
+
```
|
1085
|
+
```rb
|
1086
|
+
# app/models/account/password_hash.rb
|
1087
|
+
class Account::PasswordHash < ApplicationRecord
|
1088
|
+
belongs_to :account, foreign_key: :id
|
1089
|
+
end
|
1090
|
+
```
|
1091
|
+
```rb
|
1092
|
+
require "bcrypt"
|
1093
|
+
|
1094
|
+
account = Account.create!(email: "user@example.com", status: "verified")
|
1095
|
+
password_hash = BCrypt::Password.create("secret", cost: BCrypt::Engine::MIN_COST)
|
1096
|
+
account.create_password_hash!(id: account.id, password_hash: password_hash)
|
1097
|
+
```
|
1098
|
+
|
871
1099
|
## Rodauth defaults
|
872
1100
|
|
873
1101
|
rodauth-rails changes some of the default Rodauth settings for easier setup:
|
@@ -976,6 +1204,7 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
|
|
976
1204
|
[sms_codes]: http://rodauth.jeremyevans.net/rdoc/files/doc/sms_codes_rdoc.html
|
977
1205
|
[recovery_codes]: http://rodauth.jeremyevans.net/rdoc/files/doc/recovery_codes_rdoc.html
|
978
1206
|
[webauthn]: http://rodauth.jeremyevans.net/rdoc/files/doc/webauthn_rdoc.html
|
1207
|
+
[json]: http://rodauth.jeremyevans.net/rdoc/files/doc/json_rdoc.html
|
979
1208
|
[jwt]: http://rodauth.jeremyevans.net/rdoc/files/doc/jwt_rdoc.html
|
980
1209
|
[email_auth]: http://rodauth.jeremyevans.net/rdoc/files/doc/email_auth_rdoc.html
|
981
1210
|
[audit_logging]: http://rodauth.jeremyevans.net/rdoc/files/doc/audit_logging_rdoc.html
|
@@ -987,3 +1216,4 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
|
|
987
1216
|
[session_expiration]: http://rodauth.jeremyevans.net/rdoc/files/doc/session_expiration_rdoc.html
|
988
1217
|
[single_session]: http://rodauth.jeremyevans.net/rdoc/files/doc/single_session_rdoc.html
|
989
1218
|
[account_expiration]: http://rodauth.jeremyevans.net/rdoc/files/doc/account_expiration_rdoc.html
|
1219
|
+
[simple_ldap_authenticator]: https://github.com/jeremyevans/simple_ldap_authenticator
|