rodauth-oauth 0.10.4 → 1.0.0.pre.beta2
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/MIGRATION-GUIDE-v1.md +286 -0
- data/README.md +28 -35
- data/doc/release_notes/1_0_0_beta1.md +38 -0
- data/doc/release_notes/1_0_0_beta2.md +34 -0
- data/lib/generators/rodauth/oauth/install_generator.rb +0 -1
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +21 -11
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb +1 -1
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb +2 -2
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +1 -6
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +0 -2
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_grants.html.erb +41 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +2 -2
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_grants.html.erb +37 -0
- data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +57 -57
- data/lib/rodauth/features/oauth_application_management.rb +61 -74
- data/lib/rodauth/features/oauth_assertion_base.rb +19 -23
- data/lib/rodauth/features/oauth_authorization_code_grant.rb +62 -90
- data/lib/rodauth/features/oauth_authorize_base.rb +115 -22
- data/lib/rodauth/features/oauth_base.rb +397 -315
- data/lib/rodauth/features/oauth_client_credentials_grant.rb +20 -18
- data/lib/rodauth/features/{oauth_device_grant.rb → oauth_device_code_grant.rb} +62 -73
- data/lib/rodauth/features/oauth_dynamic_client_registration.rb +52 -31
- data/lib/rodauth/features/oauth_grant_management.rb +70 -0
- data/lib/rodauth/features/oauth_implicit_grant.rb +29 -27
- data/lib/rodauth/features/oauth_jwt.rb +53 -689
- data/lib/rodauth/features/oauth_jwt_base.rb +458 -0
- data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +48 -17
- data/lib/rodauth/features/oauth_jwt_jwks.rb +47 -0
- data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +116 -0
- data/lib/rodauth/features/oauth_management_base.rb +2 -0
- data/lib/rodauth/features/oauth_pkce.rb +22 -26
- data/lib/rodauth/features/oauth_resource_indicators.rb +33 -25
- data/lib/rodauth/features/oauth_resource_server.rb +59 -0
- data/lib/rodauth/features/oauth_saml_bearer_grant.rb +7 -1
- data/lib/rodauth/features/oauth_token_introspection.rb +76 -46
- data/lib/rodauth/features/oauth_token_revocation.rb +46 -33
- data/lib/rodauth/features/oidc.rb +382 -241
- data/lib/rodauth/features/oidc_dynamic_client_registration.rb +127 -51
- data/lib/rodauth/features/oidc_rp_initiated_logout.rb +115 -0
- data/lib/rodauth/oauth/database_extensions.rb +8 -6
- data/lib/rodauth/oauth/http_extensions.rb +74 -0
- data/lib/rodauth/oauth/railtie.rb +20 -0
- data/lib/rodauth/oauth/ttl_store.rb +2 -0
- data/lib/rodauth/oauth/version.rb +1 -1
- data/lib/rodauth/oauth.rb +29 -1
- data/locales/en.yml +34 -22
- data/locales/pt.yml +34 -22
- data/templates/authorize.str +19 -17
- data/templates/device_search.str +1 -1
- data/templates/device_verification.str +2 -2
- data/templates/jwks_field.str +1 -0
- data/templates/new_oauth_application.str +1 -2
- data/templates/oauth_application.str +2 -2
- data/templates/oauth_application_oauth_grants.str +54 -0
- data/templates/oauth_applications.str +2 -2
- data/templates/oauth_grants.str +52 -0
- metadata +23 -16
- data/lib/generators/rodauth/oauth/templates/app/models/oauth_token.rb +0 -4
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +0 -39
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +0 -35
- data/lib/rodauth/features/oauth.rb +0 -9
- data/lib/rodauth/features/oauth_http_mac.rb +0 -86
- data/lib/rodauth/features/oauth_token_management.rb +0 -81
- data/lib/rodauth/oauth/refinements.rb +0 -48
- data/templates/jwt_public_key_field.str +0 -4
- data/templates/oauth_application_oauth_tokens.str +0 -52
- data/templates/oauth_tokens.str +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f96bced835f21567ea5603751e8cf06b53d4eae70d9cab8bc685e2fca8c2027
|
4
|
+
data.tar.gz: 59badde6a055fa2638bcb41b01534f0b5b8de9eac541ac596f25e6d5cd6fb043
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 492a5c3c12bcc678c5eb6171e9d9851db745412fdb3675674c61b512dbfd1ff1aec09da02a3d4df3acb9133148990538200c49ce05de7a34dea85107aebf151b
|
7
|
+
data.tar.gz: 65f1223065b2a0bce609b4137b8fadd206fffa9904caac97c41dc4d429528dea474a8b450128409ed164f6407c690e95a3e7c4a83b8f9302fb41d0336e132dea
|
@@ -0,0 +1,286 @@
|
|
1
|
+
# Migration Guide v1
|
2
|
+
|
3
|
+
This guide is to share a few helpful tips to migrate a production app using `rodauth-oauth` v0.10 to v1. There are quite a few breaking changes which will require some work to pull through, and I'll go 1 by 1.
|
4
|
+
|
5
|
+
**Most important tip**: Make sure you're running v0.10.3 before you try to migrate to v1!
|
6
|
+
|
7
|
+
## Minimum ruby version: 2.5
|
8
|
+
|
9
|
+
Make sure you're at least running ruby 2.5 before considering migrating.
|
10
|
+
|
11
|
+
## The Oauth Token Resource was removed
|
12
|
+
|
13
|
+
The access and refresh tokens are now stored in the `oauth_grants` table, and the `oauth_tokens` table is no longer necessary.
|
14
|
+
|
15
|
+
This means that:
|
16
|
+
|
17
|
+
* Both the table, columns and labels config options prefixed by `oauth_tokens` were removed.
|
18
|
+
* "Oauth Token Revocation" becomes "OAuth Grant Revocation", and all relevant options and text changed accordingly.
|
19
|
+
* OAuth management plugins also change (i.e. URL path for listing grants is `/oauth-grants`)
|
20
|
+
|
21
|
+
### How to migrate
|
22
|
+
|
23
|
+
The tl;dr is: you'll need to start by adding the new columns to the `oauth_grants` table, start backfilling the values in runtime, backfill historical grants in the background, then deploy v1.
|
24
|
+
|
25
|
+
This example is going to use `token` and `refresh_token` columns as an example, but it's extensible to `token_hash` and `refresh_token_hash`.
|
26
|
+
|
27
|
+
Add the required token columns to the `oauth_grants` table and start backfilling themm:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
# example sequel migration
|
31
|
+
|
32
|
+
Sequel.migration do
|
33
|
+
up do
|
34
|
+
alter_table :oauth_grants do
|
35
|
+
String :code, nullable: true # to change the nullable constraint
|
36
|
+
String :token, nullable: true, unique: true
|
37
|
+
String :refresh_token, nullable: true, unique: true
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
# Then the runtime backfilling can happen. You can do that using a trigger on the `oauth_tokens` table
|
42
|
+
|
43
|
+
run <<-SQL
|
44
|
+
CREATE OR REPLACE FUNCTION copy_tokens_to_grant()
|
45
|
+
RETURNS trigger AS
|
46
|
+
$$
|
47
|
+
BEGIN
|
48
|
+
UPDATE "oauth_grants" SET "oauth_grants"."token" = NEW."token",
|
49
|
+
"oauth_grants"."refresh_token" = NEW."refresh_token",
|
50
|
+
"oauth_grants"."expires_in" = NEW."expires_in",
|
51
|
+
"oauth_grants"."code" = NULL
|
52
|
+
WHERE "oauth_grants"."id" = NEW."oauth_grant_id";
|
53
|
+
|
54
|
+
RETURN NEW;
|
55
|
+
END;
|
56
|
+
$$
|
57
|
+
LANGUAGE 'plpgsql';
|
58
|
+
|
59
|
+
CREATE TRIGGER copy_tokens_to_grant
|
60
|
+
AFTER INSERT
|
61
|
+
ON oauth_tokens
|
62
|
+
FOR EACH ROW
|
63
|
+
EXECUTE PROCEDURE copy_tokens_to_grant();
|
64
|
+
SQL
|
65
|
+
end
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
Then you'll need too backfill grants created before the changes above.
|
70
|
+
|
71
|
+
```SQL
|
72
|
+
--- this is how to do it in SQL, but can also be accomplished with a ruby script looping on rows.
|
73
|
+
|
74
|
+
UPDATE "oauth_grants"
|
75
|
+
INNER JOIN "oauth_tokens" ON "oauth_tokens"."oauth_grant_id" = "oauth_grants"."id"
|
76
|
+
SET "oauth_grants"."token" = "oauth_tokens"."token",
|
77
|
+
"oauth_grants"."refresh_token" = "oauth_tokens"."refresh_token",
|
78
|
+
"oauth_grants"."expires_in" = "oauth_tokens"."expires_in",
|
79
|
+
"oauth_grants"."code" = NULL
|
80
|
+
WHERE "oauth_grants"."token" IS NULL;
|
81
|
+
```
|
82
|
+
|
83
|
+
And now you can deploy the app with v1 installed (after you've done the required changes).
|
84
|
+
|
85
|
+
## oauth applications: oauth_applications_client_secret_hash_column
|
86
|
+
|
87
|
+
The client secret is hashed (with bcrypt) before being stored, by default. While previously it was also hashed by default, this is now driven by the new `:oauth_applications_client_secret_hash_column` options, which is set to `:client_secret`. In order to disable hashing and store the client secret in plain-text (smth whic, p.ex. the `client_secret_jwt` auth method requires), just do this:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
oauth_applications_client_secret_hash_column nil
|
91
|
+
```
|
92
|
+
|
93
|
+
## oauth grants: access token and refresh token hashed by default
|
94
|
+
|
95
|
+
access token and refresh token columns are now hashed by default, and point to the same column as the main counterpart:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
# this is now the default
|
99
|
+
oauth_grants_token_hash_column :token
|
100
|
+
oauth_grants_token_column :token
|
101
|
+
oauth_grants_refresh_token_hash_column :refresh_token
|
102
|
+
oauth_grants_refresh_token_column :refresh_token
|
103
|
+
```
|
104
|
+
|
105
|
+
in order to keep storing the tokens in plaintext, set the hash column options to`nil`. In order to keep storing the hashed token in a separate coluumn, just redefine it to the name of the column.
|
106
|
+
|
107
|
+
## oauth_response_mode is now form_post.
|
108
|
+
|
109
|
+
To get the old behaviour back:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
oauth_response_mode "query"
|
113
|
+
```
|
114
|
+
|
115
|
+
## renamed options
|
116
|
+
|
117
|
+
The following auth config methods were renamed (rename them if you're redefining them):
|
118
|
+
|
119
|
+
* `description_param` (replaced by `oauth_applications_description_param`)
|
120
|
+
* `client_id_param` (replaced by `oauth_applications_client_id_param`)
|
121
|
+
* `oauth_applications_jws_jwk_column` (replaced by `oauth_applications_jwks_column`)
|
122
|
+
* `oauth_applications_jws_jwk_label` (replaced by `oauth_applications_jwks_label`)
|
123
|
+
* `oauth_application_jws_jwk_param` (replaced by `oauth_applications_jwks_param`)
|
124
|
+
* all config methods terminated in `"_error_status"` are now prefixed by `"oauth_"`
|
125
|
+
* all config methods terminated in `"_message"` are now prefixed by `"oauth_`
|
126
|
+
* all config methods terminated in `"_error_code`` are now prefixed by `"oauth"`
|
127
|
+
* `unique_error_message` config method was removed (not in use)
|
128
|
+
* `oauth_jwt_token_issuer` renamed to `oauth_jwt_issuer`
|
129
|
+
* `oauth_auth_methods_supported` renamed to `oauth_token_endpoint_auth_methods_supported`
|
130
|
+
* `oauth_jwt_algorithms_supported` renamed to `oauth_jwt_jws_algorithms_supported`
|
131
|
+
* `oauth_token_expires_in` renamed to `oauth_access_token_expires_in`
|
132
|
+
|
133
|
+
## Removed options
|
134
|
+
|
135
|
+
### Base options
|
136
|
+
|
137
|
+
* `oauth_application_default_scope`: if you were using it to pre-fill scopes in the Authorization form, or the New OAuth Application form, you'll have to do it yourself.
|
138
|
+
|
139
|
+
### JWT options
|
140
|
+
|
141
|
+
* `oauth_jwt_key`: can be replaced by using it as the value in `oauth_jwt_keys` (```oauth_jwt_keys("RS256" => [privkey])```);
|
142
|
+
* `oauth_jwt_algorithm`: can be replaced by using it as the key in `oauth_jwt_keys` (```oauth_jwt_keys("RS256" => [privkey])```);
|
143
|
+
* `oauth_jwt_public_key`: can be replaced by using it as the value in `oauth_jwt_public_keys` (```oauth_jwt_public_keys("RS256" => [pubkey])```);
|
144
|
+
* `oauth_jwe_key`: can be replaced by using it as the value in `oauth_jwe_keys` (```oauth_jwe_keys((%w[RSA-OAEP A128CBC-HS256] => [privkey])```);
|
145
|
+
* `oauth_jwt_jwe_algorithm`: can be replaced by using it as the first element in the key tuple in `oauth_jwe_keys` (```oauth_jwe_keys(%w[RSA-OAEP A128CBC-HS256] => [privkey])```);
|
146
|
+
* `oauth_jwt_jwe_encryption_method`: can be replaced by using it as the second element in the key tuple in `oauth_jwe_keys` (```oauth_jwe_keys(%w[RSA-OAEP A128CBC-HS256] => [privkey])```);
|
147
|
+
* `oauth_jwe_public_key`: can be replaced by using it as the value in `oauth_jwe_public_keys` (```oauth_jwt_public_keys(%w[RSA-OAEP A128CBC-HS256] => [pubkey])```);
|
148
|
+
* `oauth_jwt_legacy_algorithm`: can be replaced by adding it as a key to `oauth_jwt_public_keys`;
|
149
|
+
* `oauth_jwt_legacy_public_key`: can be replaced by adding it to the value set of `oauth_jwt_public_keys`, after the current key (```oauth_jwt_public_keys("RS256" => [pubkey, legacy_pubkey])```);
|
150
|
+
|
151
|
+
#### `oauth_jwt_key`
|
152
|
+
|
153
|
+
## `oauth_device_grant` feature becomes `oauth_device_code_grant`
|
154
|
+
|
155
|
+
In case you were using it directly, you should rename it.
|
156
|
+
|
157
|
+
## `use_oauth_*_grant` options were removed
|
158
|
+
|
159
|
+
One of the main changes in v1.0.0 is that one should enable the features one needs, explicitly. So when you used to have:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
rodauth do
|
163
|
+
enable :oauth
|
164
|
+
end
|
165
|
+
```
|
166
|
+
|
167
|
+
you should now load the grants you use:
|
168
|
+
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
rodauth do
|
172
|
+
enable :oauth_authorization_code_grant, :oauth_pkce, :oauth_credentials_grant, :oauth_token_introspection
|
173
|
+
# or
|
174
|
+
enable :oidc, :oauth_implicit_grant
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
Now that the features are explicitly enable, there's is no more use for config methods for unsetting them, such as `use_oauth_implicit_grant_type?` or `use_oauth_pkce?`.
|
179
|
+
|
180
|
+
|
181
|
+
## `oauth_jwt_audience` and `oauth_jwt_issuer` repurposed as functions
|
182
|
+
|
183
|
+
To maintain legacy behaviour, you can return them in the function body.
|
184
|
+
|
185
|
+
```diff
|
186
|
+
- oauth_jwt_audience legacy_aud
|
187
|
+
- oauth_jwt_issuer legacy_iss
|
188
|
+
+ oauth_jwt_audience { legacy_aud }
|
189
|
+
+ oauth_jwt_issuer { legacy_iss }
|
190
|
+
```
|
191
|
+
|
192
|
+
## JAR (Secured authorization request) segregated in its plugin
|
193
|
+
|
194
|
+
It was previously being loaded in the `:oauth_jwt` plugin by default. If you require this funtionality, enable the plugin:
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
enable :oauth_jwt_secured_authorization_request
|
198
|
+
```
|
199
|
+
|
200
|
+
## `oauth_jwt_jwks` plugin
|
201
|
+
|
202
|
+
JWKs URI endpoint has been moved to its plugin. If you require this functionality, make sure you enable it:
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
enable :oauth_jwt, :oauth_jwt_jwks
|
206
|
+
```
|
207
|
+
|
208
|
+
## routing functions renamed
|
209
|
+
|
210
|
+
Previously, loading well-known routes, the oauth server metadata, or oauth application/tokens (now grants) management dashboard implied calling a function on roda to load those routes. These have been renamed:
|
211
|
+
|
212
|
+
```diff
|
213
|
+
plugin :rodauth do
|
214
|
+
enable :oidc, :oauth_application_management, :oauth_grant_management
|
215
|
+
end
|
216
|
+
|
217
|
+
roda do |r|
|
218
|
+
- rodauth.oauth_applications
|
219
|
+
- rodauth.oauth_grants
|
220
|
+
- rodauth.openid_configuration
|
221
|
+
- rodauth.webfinger
|
222
|
+
+ rodauth.load_oauth_application_management_routes
|
223
|
+
+ rodauth.load_oauth_grant_management_routes
|
224
|
+
+ rodauth.load_openid_configuration_route
|
225
|
+
+ rodauth.load_webfinger_route
|
226
|
+
end
|
227
|
+
```
|
228
|
+
|
229
|
+
## resource server mode via `oauth_resource_server` feature
|
230
|
+
|
231
|
+
You should now be able to set a resource server just using this plugin, instead of the combination of config tweaks previously suggested.
|
232
|
+
|
233
|
+
```diff
|
234
|
+
plugin :rodauth do
|
235
|
+
- enable :oauth
|
236
|
+
- is_authorization_server? false
|
237
|
+
enable :oauth_resource_server
|
238
|
+
authorization_server_url "https://external-auth-server"
|
239
|
+
end
|
240
|
+
```
|
241
|
+
|
242
|
+
## `oauth_token_subject` returns client id for client-application tokens
|
243
|
+
|
244
|
+
Previously, if a token from a client credentials grant would be used, calling `oauth_token_subject` would return the oauth application primary key. It now returns the application client id.
|
245
|
+
|
246
|
+
## `oauth_jwt_subject*` family of options moved to `oidc` feature
|
247
|
+
|
248
|
+
Previously, they were in the `oauth_jwt` feature; however, they're not specced for use in general purpose JWT Access Tokens, but rather in OIDC ID tokens.
|
249
|
+
|
250
|
+
## `oauth` feature removed
|
251
|
+
|
252
|
+
The `oauth` plugin, which is how this gem started, was a giant "god" feature, which has been gradually broken down into sub-features, each implementing an RFC or specific feature, and building on top of each other. In its last state, it was just loading all those sub-features, for backwards-compatibility; and when you need to turn off a particular feature, you'd have to set a `use_oauth_implicit_grant_type?` type of config to `false`.
|
253
|
+
|
254
|
+
That is now over, and you'll need to explicitly load all features you need yourself.
|
255
|
+
|
256
|
+
```diff
|
257
|
+
plugin :rodauth
|
258
|
+
- enable :oauth
|
259
|
+
- use_oauth_implicit_grant_type? false
|
260
|
+
+ enable :oauth_authorization_code_grant, :oauth_client_credentials_grant
|
261
|
+
end
|
262
|
+
```
|
263
|
+
|
264
|
+
## `require_oauth_application` will not support "none" strategy by default
|
265
|
+
|
266
|
+
Unless explicitly set in oauth application config, or `oauth_token_endpoint_auth_methods_supported` config.
|
267
|
+
|
268
|
+
## `jwt_bearer_grant` feature exposes `client_secret_jwt` and `private_key_jwt` as token endpoint auth methods
|
269
|
+
|
270
|
+
While the latter is a new feature, the former was already implemented, but not declared.
|
271
|
+
|
272
|
+
## Webfinger options
|
273
|
+
|
274
|
+
`webfinger_relation` has been removed. If you were overriding it, you can override `json_webfinger_payload` to backport this behaviour.
|
275
|
+
|
276
|
+
## PKCE is strict by default
|
277
|
+
|
278
|
+
This was a security improvement. However, if you were relying on, set `oauth_require_pkce` to `false`.
|
279
|
+
|
280
|
+
## refresh token policy set to "rotation" by default
|
281
|
+
|
282
|
+
This was a security improvement. However, if you were relying on, set `oauth_refresh_token_protection_policy` to `"none"`.
|
283
|
+
|
284
|
+
## using access tokens won't set rodauth session
|
285
|
+
|
286
|
+
Which means that rodauth won't identify you as "logged in". If you were relying on this behaviour, you'll have to tweak one of the available `rodauth` options.
|
data/README.md
CHANGED
@@ -16,13 +16,13 @@ This gem implements the following RFCs and features of OAuth:
|
|
16
16
|
* `oauth_authorization_code_grant` - [Authorization code grant](https://tools.ietf.org/html/rfc6749#section-1.3);
|
17
17
|
* `oauth_implicit_grant` - [Implicit grant (off by default)](https://tools.ietf.org/html/rfc6749#section-4.2);
|
18
18
|
* `oauth_client_credentials_grant` - [Client credentials grant (off by default)](https://tools.ietf.org/html/rfc6749#section-4.4);
|
19
|
-
* `
|
19
|
+
* `oauth_device_code_grant` - [Device code grant (off by default)](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-device-flow-15);
|
20
20
|
* `oauth_token_revocation` - [Token revocation](https://tools.ietf.org/html/rfc7009);
|
21
21
|
* `oauth_token_introspection` - [Token introspection](https://tools.ietf.org/html/rfc7662);
|
22
22
|
* [Authorization Server Metadata](https://tools.ietf.org/html/rfc8414);
|
23
23
|
* `oauth_pkce` - [PKCE](https://tools.ietf.org/html/rfc7636);
|
24
24
|
* `oauth_jwt` - [JWT Access Tokens](https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-07);
|
25
|
-
|
25
|
+
* `oauth_jwt_secured_authorization_request` - [JWT Secured Authorization Request](https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-20);
|
26
26
|
* `oauth_resource_indicators` - [Resource Indicators](https://datatracker.ietf.org/doc/html/rfc8707);
|
27
27
|
* Access Type (Token refresh online and offline);
|
28
28
|
* `oauth_http_mac` - [MAC Authentication Scheme](https://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-02);
|
@@ -35,11 +35,12 @@ This gem implements the following RFCs and features of OAuth:
|
|
35
35
|
|
36
36
|
It also implements the [OpenID Connect layer](https://openid.net/connect/) (via the `openid` feature) on top of the OAuth features it provides, including:
|
37
37
|
|
38
|
-
*
|
39
|
-
* [OpenID Connect
|
40
|
-
* [OpenID
|
41
|
-
* [OpenID
|
42
|
-
* [
|
38
|
+
* `oidc`
|
39
|
+
* [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html);
|
40
|
+
* [OpenID Connect Discovery](https://openid.net/specs/openid-connect-discovery-1_0-29.html);
|
41
|
+
* [OpenID Multiple Response Types](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html);
|
42
|
+
* `oidc_dynamic_client_registration` - [OpenID Connect Dynamic Client Registration](https://openid.net/specs/openid-connect-registration-1_0.html);
|
43
|
+
* `oidc_rp_initiated_logout` - [RP Initiated Logout](https://openid.net/specs/openid-connect-rpinitiated-1_0.html);
|
43
44
|
|
44
45
|
This gem supports also rails (through [rodauth-rails]((https://github.com/janko/rodauth-rails))).
|
45
46
|
|
@@ -81,8 +82,7 @@ This tutorial assumes you already read the documentation and know how to set up
|
|
81
82
|
```ruby
|
82
83
|
plugin :rodauth do
|
83
84
|
# enable it in the plugin
|
84
|
-
enable :login, :
|
85
|
-
oauth_application_default_scope %w[profile.read]
|
85
|
+
enable :login, :oauth_authorization_code_grant
|
86
86
|
oauth_application_scopes %w[profile.read profile.write]
|
87
87
|
end
|
88
88
|
|
@@ -111,6 +111,7 @@ route do |r|
|
|
111
111
|
r.is "books" do
|
112
112
|
rodauth.require_oauth_authorization("books.read", "books.research")
|
113
113
|
r.get do
|
114
|
+
@books = Book.where(user_id: rodauth.current_oauth_account[:id]).all
|
114
115
|
# ...
|
115
116
|
end
|
116
117
|
end
|
@@ -124,7 +125,6 @@ For OpenID, it's very similar to the example above:
|
|
124
125
|
plugin :rodauth do
|
125
126
|
# enable it in the plugin
|
126
127
|
enable :login, :oidc
|
127
|
-
oauth_application_default_scope %w[openid]
|
128
128
|
oauth_application_scopes %w[openid email profile]
|
129
129
|
end
|
130
130
|
```
|
@@ -144,10 +144,10 @@ You can change column names or even use existing tables, however, be aware that
|
|
144
144
|
```ruby
|
145
145
|
plugin :rodauth do
|
146
146
|
# enable it in the plugin
|
147
|
-
enable :login, :
|
147
|
+
enable :login, :oauth_authorization_code_grant
|
148
148
|
# ...
|
149
|
-
oauth_grants_table
|
150
|
-
oauth_grants_code_column
|
149
|
+
oauth_grants_table :access_grants
|
150
|
+
oauth_grants_code_column :authorization_code
|
151
151
|
end
|
152
152
|
```
|
153
153
|
|
@@ -195,9 +195,8 @@ You can then enable this feature in `lib/rodauth_app.rb` and set up any options
|
|
195
195
|
|
196
196
|
```ruby
|
197
197
|
# lib/roudauth_app.rb
|
198
|
-
enable :
|
198
|
+
enable :oauth_authorization_code_grant
|
199
199
|
# OAuth
|
200
|
-
oauth_application_default_scope "profile.read"
|
201
200
|
oauth_application_scopes %w[profile.read profile.write books.read books.write]
|
202
201
|
```
|
203
202
|
|
@@ -242,34 +241,28 @@ In this section, the non-standard features are going to be described in more det
|
|
242
241
|
|
243
242
|
### Token / Secrets Hashing
|
244
243
|
|
245
|
-
|
244
|
+
Access tokens, refresh tokens and client secrets are hashed before being stored in the database (using `bcrypt`), by default.
|
246
245
|
|
247
|
-
|
248
|
-
# in migration
|
249
|
-
String :token_hash, null: false, token: true
|
250
|
-
String :refresh_token_hash, token, true
|
251
|
-
# and you DO NOT NEED the token and refresh_token columns anymore!
|
252
|
-
```
|
253
|
-
|
254
|
-
And declare them in the plugin:
|
246
|
+
Disabling this behaviour is a matter of nullifying the hash column option:
|
255
247
|
|
256
248
|
```ruby
|
257
249
|
plugin :rodauth do
|
258
|
-
enable :
|
259
|
-
oauth_tokens_token_hash_column :token_hash
|
260
|
-
oauth_tokens_token_hash_column :refresh_token_hash
|
261
|
-
```
|
250
|
+
enable :oauth_authorization_code_grant
|
262
251
|
|
263
|
-
|
264
|
-
|
265
|
-
|
252
|
+
# storing access token, refresh token and client secret in plaintext:
|
253
|
+
oauth_grants_token_hash_column nil
|
254
|
+
oauth_grants_refresh_token_hash_column nil
|
255
|
+
oauth_applications_client_secret_hash_column nil
|
256
|
+
```
|
266
257
|
|
267
|
-
|
258
|
+
If you'd like to replace the hashing function (for, let's say, [argon2](https://github.com/technion/ruby-argon2)), you'll need to perform the following overrides:
|
268
259
|
|
269
260
|
```ruby
|
270
261
|
plugin :rodauth do
|
271
|
-
enable :
|
272
|
-
|
262
|
+
enable :oauth_authorization_code_grant
|
263
|
+
|
264
|
+
secret_matches? { |oauth_application, secret| Argon2::Password.verify_password(secret, oauth_application[oauth_applications_client_secret_hash_column]) }
|
265
|
+
secret_hash { |secret| Argon2::Password.create(secret) }
|
273
266
|
end
|
274
267
|
```
|
275
268
|
|
@@ -284,7 +277,7 @@ Default translations shipping with `rodauth-oauth` can be found [in this directo
|
|
284
277
|
|
285
278
|
## Ruby support policy
|
286
279
|
|
287
|
-
The minimum Ruby version required to run `rodauth-oauth` is 2.
|
280
|
+
The minimum Ruby version required to run `rodauth-oauth` is 2.5 . Besides that, it should support all rubies that rodauth and roda support, including JRuby and truffleruby.
|
288
281
|
|
289
282
|
### Rails
|
290
283
|
|
@@ -0,0 +1,38 @@
|
|
1
|
+
## 1.0.0-beta1 (21/10/2022)
|
2
|
+
|
3
|
+
### Breaking changes
|
4
|
+
|
5
|
+
The full description of breaking changes, and suggestions on how to make the migration smoother, can be found in the [migration guide](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/blob/6465b8522a78cf0037a55d3d4b81f68f7811be68/MIGRATION-GUIDE-v1.md).
|
6
|
+
|
7
|
+
A short list of the main highlights:
|
8
|
+
|
9
|
+
|
10
|
+
* Ruby 2.5 or higher is required.
|
11
|
+
* `oauth_http_mac` feature removed.
|
12
|
+
* `oauth_tokens` table (and resource) were removed (only `oauth_applications` and `oauth_grants`, access and refresh tokens are now properties of the latter).
|
13
|
+
* access and refresh tokens hashed by default when stored in the database.
|
14
|
+
* default oauth response mode is `"form_post"`.
|
15
|
+
* oauth specific features require explicit enablement of respective features (no more `enable :oauth`)
|
16
|
+
* refresh token policy is "rotation" by default
|
17
|
+
|
18
|
+
### Features
|
19
|
+
|
20
|
+
The following helpers are exposed in the `rodauth` object:
|
21
|
+
|
22
|
+
* `current_oauth_account` - returns the dataset row for the `rodauth` account associated to an oauth access token in the "authorization" header.
|
23
|
+
* `current_oauth_application` - returns the dataset row for the oauth application associated to an oauth access token in the "authorization" header.
|
24
|
+
|
25
|
+
When used in `rails` via `rodauth-rails`, both are exposed directly as controller helpers.
|
26
|
+
|
27
|
+
#### `oauth_resource_server` plugin
|
28
|
+
|
29
|
+
This plugin can be used as a convenience when configuring resource servers.
|
30
|
+
|
31
|
+
### Improvements
|
32
|
+
|
33
|
+
* `:oauth_introspect` plugin: OAuth introspection endpoint exposes the token's `"username"` claim.
|
34
|
+
* endpoint client authentication supports "client credentials grant" access tokens.
|
35
|
+
|
36
|
+
### Bugfixes
|
37
|
+
|
38
|
+
* fixed `oidc` calculation of `"auth_time"` claim.
|
@@ -0,0 +1,34 @@
|
|
1
|
+
This version passes the conformance tests for the following OpenID Connect certification profiles:
|
2
|
+
|
3
|
+
* Basic certification
|
4
|
+
* Form-post basic certification
|
5
|
+
* Config certification
|
6
|
+
* Dynamic Config certification (`response_type=code`)
|
7
|
+
|
8
|
+
## Breaking Changes
|
9
|
+
|
10
|
+
* homepage url is no longer a client application required property.
|
11
|
+
* OIDC RP-initiated logout extracted into `oidc_rp_initiated_logout` feature.
|
12
|
+
|
13
|
+
## Features
|
14
|
+
|
15
|
+
* `oauth_jwt_secured_authorization_request` now supports a `request_uri` query param as well.
|
16
|
+
* `oidc` supports essential claims, via the `claims` authorization request query parameter.
|
17
|
+
|
18
|
+
## Improvements
|
19
|
+
|
20
|
+
* exposing `acr_values_supported` in the openid configuration.
|
21
|
+
* `oauth_request_object_signing_alg_allow_none` enables `"none"` as an accepted request object signing alg when `true` (`false` by default).
|
22
|
+
* OIDC `offline_access` supported.
|
23
|
+
|
24
|
+
## Bugfixes
|
25
|
+
|
26
|
+
* JWT: "sub" is now always a string.
|
27
|
+
* `response_type` is now an authorization request required parameter (as per the RFC).
|
28
|
+
* `state` is now passed along when redirecting from authorization requeests with `error`;
|
29
|
+
* access token can now be read from POST body or GET quety params (as per the RFC).
|
30
|
+
* id token no longer shipping with claims with `null` value;
|
31
|
+
* id token no longer encoding claims by default (only when `response_type=id_token`, as per the RFC).
|
32
|
+
* support "JWT without kid" when doing jwt decoding for JWT tokens not generated in the provider (such as request objects).
|
33
|
+
* Set `iss` and `aud` claims in the Userinfo JWT response.
|
34
|
+
* Make sure errors are also delivered via form POST, when `response_mode=form_post`.
|
@@ -2,7 +2,9 @@
|
|
2
2
|
<% if rodauth.oauth_application[rodauth.oauth_applications_logo_uri_column] %>
|
3
3
|
<%= image_tag rodauth.oauth_application[rodauth.oauth_applications_logo_uri_column] %>
|
4
4
|
<% end %>
|
5
|
-
|
5
|
+
<% application_uri = rodauth.oauth_application[rodauth.oauth_applications_homepage_url_column] %>
|
6
|
+
<% application_name = application_uri ? link_to(rodauth.oauth_application[rodauth.oauth_applications_name_column], application_uri) : rodauth.oauth_application[rodauth.oauth_applications_name_column] %>
|
7
|
+
<p class="lead"><%= rodauth.authorize_page_lead(name: application_name).html_safe %></p>
|
6
8
|
|
7
9
|
<div class="list-group">
|
8
10
|
<% if rodauth.oauth_application[rodauth.oauth_applications_tos_uri_column] %>
|
@@ -23,15 +25,17 @@
|
|
23
25
|
<% end %>
|
24
26
|
|
25
27
|
<div class="form-group">
|
26
|
-
<h1 class="display-6"><%= rodauth.
|
28
|
+
<h1 class="display-6"><%= rodauth.oauth_grants_scopes_label %></h1>
|
27
29
|
|
28
|
-
<% rodauth.
|
29
|
-
<%
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
<% rodauth.authorize_scopes.each do |scope| %>
|
31
|
+
<% if rodauth.features.include?(:oidc) && scope == "offline_access" %>
|
32
|
+
<%= hidden_field_tag "scope[]", scope %>
|
33
|
+
<% else %>
|
34
|
+
<div class="form-check">
|
35
|
+
<%= check_box_tag "scope[]", scope, id: scope, class: "form-check-input" %>
|
36
|
+
<%= label_tag scope, scope, class: "form-check-label" %>
|
37
|
+
</div>
|
38
|
+
<% end %>
|
35
39
|
<% end %>
|
36
40
|
<%= hidden_field_tag :client_id, params[:client_id] %>
|
37
41
|
<% %i[access_type response_type response_mode state redirect_uri].each do |oauth_param| %>
|
@@ -53,6 +57,9 @@
|
|
53
57
|
<% end %>
|
54
58
|
<% end %>
|
55
59
|
<% if rodauth.features.include?(:oidc) %>
|
60
|
+
<% if params[:prompt] %>
|
61
|
+
<%= hidden_field_tag :prompt, params[:prompt] %>
|
62
|
+
<% end %>
|
56
63
|
<% if params[:nonce] %>
|
57
64
|
<%= hidden_field_tag :nonce, params[:nonce] %>
|
58
65
|
<% end %>
|
@@ -62,13 +69,16 @@
|
|
62
69
|
<% if params[:claims_locales] %>
|
63
70
|
<%= hidden_field_tag :claims_locales, params[:claims_locales] %>
|
64
71
|
<% end %>
|
72
|
+
<% if params[:claims] %>
|
73
|
+
<%= hidden_field_tag :claims, sanitize(params[:claims]) %>
|
74
|
+
<% end %>
|
65
75
|
<% if params[:acr_values] %>
|
66
|
-
<%= hidden_field_tag :
|
76
|
+
<%= hidden_field_tag :acr_values, params[:acr_values] %>
|
67
77
|
<% end %>
|
68
78
|
<% end %>
|
69
79
|
</div>
|
70
80
|
<p class="text-center">
|
71
81
|
<%= submit_tag rodauth.oauth_authorize_button, class: "btn btn-outline-primary" %>
|
72
|
-
<%= link_to rodauth.oauth_cancel_button, "#{rodauth.redirect_uri}?error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request#{"&state=\#{rodauth.state}" if params[:state] }", class: "btn btn-outline-danger" %>
|
82
|
+
<%= link_to rodauth.oauth_cancel_button, "#{rodauth.redirect_uri}?error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request#{"&state=\#{CGI.escape(rodauth.state)}" if params[:state] }", class: "btn btn-outline-danger" %>
|
73
83
|
</p>
|
74
84
|
<% end %>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<%= form_tag rodauth.device_path, method: :get, class: "form-horizontal", id: "device-search-form" do %>
|
2
|
-
<p class="lead"
|
2
|
+
<p class="lead"><%= rodauth.oauth_device_search_page_lead %></p>
|
3
3
|
|
4
4
|
<div class="form-group">
|
5
5
|
<%= label_tag "user_code", rodauth.oauth_grant_user_code_label %>
|
@@ -1,9 +1,9 @@
|
|
1
1
|
<% oauth_grant = rodauth.scope.instance_variable_get(:@oauth_grant) %>
|
2
2
|
<%= form_tag rodauth.device_path, method: :post, class: "form-horizontal", id: "device-verification-form" do %>
|
3
|
-
<p class="lead"
|
3
|
+
<p class="lead"><%= rodauth.oauth_device_verification_page_lead(user_code: @oauth_grant[rodauth.oauth_grants_user_code_column]) %></p>
|
4
4
|
|
5
5
|
<div class="form-group">
|
6
|
-
<h1 class="display-6"><%= rodauth.
|
6
|
+
<h1 class="display-6"><%= rodauth.oauth_grants_scopes_label %></h1>
|
7
7
|
|
8
8
|
<ul class="list-group">
|
9
9
|
<% oauth_grant[rodauth.oauth_grants_scopes_column].split(rodauth.oauth_scope_separator).each do |scope| %>
|
data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb
CHANGED
@@ -37,15 +37,10 @@
|
|
37
37
|
<%= text_field_tag "jwks_uri", rodauth.param('jwks_uri'), id: "jwks-uri", class: "form-control#{' is-invalid' if rodauth.field_error('jwks_uri')}" %>
|
38
38
|
<%= rodauth.field_error('jwks_uri') %>
|
39
39
|
</div>
|
40
|
-
<div class="form-group">
|
41
|
-
<%= label_tag "jwt_public_key", rodauth.oauth_applications_jwt_public_key_label %>
|
42
|
-
<%= text_field_tag "jwt_public_key", rodauth.param('jwt_public_key'), id: "jwt-public-key", class: "form-control#{' is-invalid' if rodauth.field_error('jwt_public_key')}" %>
|
43
|
-
<%= rodauth.field_error('jwt_public_key') %>
|
44
|
-
</div>
|
45
40
|
<% end %>
|
46
41
|
<% rodauth.oauth_application_scopes.each do |scope| %>
|
47
42
|
<div class="form-check">
|
48
|
-
<%= check_box_tag "scopes[]", scope,
|
43
|
+
<%= check_box_tag "scopes[]", scope, id: scope, class: "form-check-input" %>
|
49
44
|
<%= scope %>
|
50
45
|
</div>
|
51
46
|
<% end %>
|
@@ -22,8 +22,6 @@
|
|
22
22
|
<dt><%= rodauth.oauth_applications_jwks_uri_label %>: </dt>
|
23
23
|
<dd><%= oauth_application[rodauth.oauth_applications_jwks_uri_column] %></dd>
|
24
24
|
<% end %>
|
25
|
-
<dt><%= rodauth.oauth_applications_jwt_public_key_label %>: </dt>
|
26
|
-
<dd><%= oauth_application[rodauth.oauth_applications_jwt_public_key_column] %></dd>
|
27
25
|
<% end %>
|
28
26
|
</dl>
|
29
27
|
</div>
|