rodauth-oauth 0.10.4 → 1.0.0.pre.beta1
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 +22 -30
- data/doc/release_notes/1_0_0_beta1.md +38 -0
- data/lib/generators/rodauth/oauth/install_generator.rb +0 -1
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +4 -6
- 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 +18 -29
- data/lib/rodauth/features/oauth_application_management.rb +59 -72
- data/lib/rodauth/features/oauth_assertion_base.rb +19 -23
- data/lib/rodauth/features/oauth_authorization_code_grant.rb +35 -88
- data/lib/rodauth/features/oauth_authorize_base.rb +103 -20
- data/lib/rodauth/features/oauth_base.rb +365 -302
- 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 +46 -28
- data/lib/rodauth/features/oauth_grant_management.rb +70 -0
- data/lib/rodauth/features/oauth_implicit_grant.rb +25 -24
- data/lib/rodauth/features/oauth_jwt.rb +52 -688
- data/lib/rodauth/features/oauth_jwt_base.rb +435 -0
- data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +45 -17
- data/lib/rodauth/features/oauth_jwt_jwks.rb +47 -0
- data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +62 -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 -21
- data/lib/rodauth/features/oauth_resource_server.rb +59 -0
- data/lib/rodauth/features/oauth_saml_bearer_grant.rb +5 -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 +188 -95
- data/lib/rodauth/features/oidc_dynamic_client_registration.rb +89 -53
- data/lib/rodauth/oauth/database_extensions.rb +8 -6
- data/lib/rodauth/oauth/http_extensions.rb +61 -0
- data/lib/rodauth/oauth/railtie.rb +20 -0
- data/lib/rodauth/oauth/version.rb +1 -1
- data/lib/rodauth/oauth.rb +29 -1
- data/locales/en.yml +32 -22
- data/locales/pt.yml +32 -22
- data/templates/authorize.str +19 -24
- 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 +20 -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: 299307b4879c519f6bcf7e5cfb875b75ac1d6b73b90371d3c9925baaf50dee08
|
4
|
+
data.tar.gz: 05a87d3e473b514f7cbb67d841a4a9185154dd1d15933d3321a3e4946c9c28ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2600d4236957c98ece6db0b06d280675d0e64c203a42e59e52063fea851bb42ec782063da5e8598211ae2a20faf9282616ffba14de8333f4e3a49ba04c97d154
|
7
|
+
data.tar.gz: 7a3b5a0b1f9979c92329848d57c6de9a68f372f2357d4ade96538aea884124235efeeb3bd28d2336d3dad92344ce59cb61958c868ffa8b12f4771d17b2e1f0f7
|
@@ -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);
|
@@ -81,8 +81,7 @@ This tutorial assumes you already read the documentation and know how to set up
|
|
81
81
|
```ruby
|
82
82
|
plugin :rodauth do
|
83
83
|
# enable it in the plugin
|
84
|
-
enable :login, :
|
85
|
-
oauth_application_default_scope %w[profile.read]
|
84
|
+
enable :login, :oauth_authorization_code_grant
|
86
85
|
oauth_application_scopes %w[profile.read profile.write]
|
87
86
|
end
|
88
87
|
|
@@ -111,6 +110,7 @@ route do |r|
|
|
111
110
|
r.is "books" do
|
112
111
|
rodauth.require_oauth_authorization("books.read", "books.research")
|
113
112
|
r.get do
|
113
|
+
@books = Book.where(user_id: rodauth.current_oauth_account[:id]).all
|
114
114
|
# ...
|
115
115
|
end
|
116
116
|
end
|
@@ -124,7 +124,6 @@ For OpenID, it's very similar to the example above:
|
|
124
124
|
plugin :rodauth do
|
125
125
|
# enable it in the plugin
|
126
126
|
enable :login, :oidc
|
127
|
-
oauth_application_default_scope %w[openid]
|
128
127
|
oauth_application_scopes %w[openid email profile]
|
129
128
|
end
|
130
129
|
```
|
@@ -144,10 +143,10 @@ You can change column names or even use existing tables, however, be aware that
|
|
144
143
|
```ruby
|
145
144
|
plugin :rodauth do
|
146
145
|
# enable it in the plugin
|
147
|
-
enable :login, :
|
146
|
+
enable :login, :oauth_authorization_code_grant
|
148
147
|
# ...
|
149
|
-
oauth_grants_table
|
150
|
-
oauth_grants_code_column
|
148
|
+
oauth_grants_table :access_grants
|
149
|
+
oauth_grants_code_column :authorization_code
|
151
150
|
end
|
152
151
|
```
|
153
152
|
|
@@ -195,9 +194,8 @@ You can then enable this feature in `lib/rodauth_app.rb` and set up any options
|
|
195
194
|
|
196
195
|
```ruby
|
197
196
|
# lib/roudauth_app.rb
|
198
|
-
enable :
|
197
|
+
enable :oauth_authorization_code_grant
|
199
198
|
# OAuth
|
200
|
-
oauth_application_default_scope "profile.read"
|
201
199
|
oauth_application_scopes %w[profile.read profile.write books.read books.write]
|
202
200
|
```
|
203
201
|
|
@@ -242,34 +240,28 @@ In this section, the non-standard features are going to be described in more det
|
|
242
240
|
|
243
241
|
### Token / Secrets Hashing
|
244
242
|
|
245
|
-
|
243
|
+
Access tokens, refresh tokens and client secrets are hashed before being stored in the database (using `bcrypt`), by default.
|
246
244
|
|
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:
|
245
|
+
Disabling this behaviour is a matter of nullifying the hash column option:
|
255
246
|
|
256
247
|
```ruby
|
257
248
|
plugin :rodauth do
|
258
|
-
enable :
|
259
|
-
oauth_tokens_token_hash_column :token_hash
|
260
|
-
oauth_tokens_token_hash_column :refresh_token_hash
|
261
|
-
```
|
249
|
+
enable :oauth_authorization_code_grant
|
262
250
|
|
263
|
-
|
264
|
-
|
265
|
-
|
251
|
+
# storing access token, refresh token and client secret in plaintext:
|
252
|
+
oauth_grants_token_hash_column nil
|
253
|
+
oauth_grants_refresh_token_hash_column nil
|
254
|
+
oauth_applications_client_secret_hash_column nil
|
255
|
+
```
|
266
256
|
|
267
|
-
|
257
|
+
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
258
|
|
269
259
|
```ruby
|
270
260
|
plugin :rodauth do
|
271
|
-
enable :
|
272
|
-
|
261
|
+
enable :oauth_authorization_code_grant
|
262
|
+
|
263
|
+
secret_matches? { |oauth_application, secret| Argon2::Password.verify_password(secret, oauth_application[oauth_applications_client_secret_hash_column]) }
|
264
|
+
secret_hash { |secret| Argon2::Password.create(secret) }
|
273
265
|
end
|
274
266
|
```
|
275
267
|
|
@@ -284,7 +276,7 @@ Default translations shipping with `rodauth-oauth` can be found [in this directo
|
|
284
276
|
|
285
277
|
## Ruby support policy
|
286
278
|
|
287
|
-
The minimum Ruby version required to run `rodauth-oauth` is 2.
|
279
|
+
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
280
|
|
289
281
|
### Rails
|
290
282
|
|
@@ -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.
|
@@ -2,7 +2,7 @@
|
|
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
|
-
<p class="lead"
|
5
|
+
<p class="lead"><%= rodauth.authorize_page_lead(name: link_to(rodauth.oauth_application[rodauth.oauth_applications_name_column], rodauth.oauth_application[rodauth.oauth_applications_homepage_url_column])).html_safe %></p>
|
6
6
|
|
7
7
|
<div class="list-group">
|
8
8
|
<% if rodauth.oauth_application[rodauth.oauth_applications_tos_uri_column] %>
|
@@ -23,14 +23,12 @@
|
|
23
23
|
<% end %>
|
24
24
|
|
25
25
|
<div class="form-group">
|
26
|
-
<h1 class="display-6"><%= rodauth.
|
26
|
+
<h1 class="display-6"><%= rodauth.oauth_grants_scopes_label %></h1>
|
27
27
|
|
28
|
-
<% rodauth.
|
29
|
-
<% is_default = scope == rodauth.oauth_application_default_scope %>
|
28
|
+
<% rodauth.authorize_scopes.each do |scope| %>
|
30
29
|
<div class="form-check">
|
31
|
-
<%= check_box_tag "scope[]", scope,
|
30
|
+
<%= check_box_tag "scope[]", scope, id: scope, class: "form-check-input" %>
|
32
31
|
<%= label_tag scope, scope, class: "form-check-label" %>
|
33
|
-
<%= hidden_field_tag "scope[]", scope if is_default %>
|
34
32
|
</div>
|
35
33
|
<% end %>
|
36
34
|
<%= hidden_field_tag :client_id, params[:client_id] %>
|
@@ -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>
|
@@ -0,0 +1,41 @@
|
|
1
|
+
<% oauth_grants = rodauth.scope.instance_variable_get(:@oauth_grants) %>
|
2
|
+
<% grants_count = oauth_grants.count %>
|
3
|
+
<% if grants_count.zero? %>
|
4
|
+
<p><%= rodauth.oauth_no_grants_text %></p>
|
5
|
+
<% else %>
|
6
|
+
<table class="table">
|
7
|
+
<thead>
|
8
|
+
<tr>
|
9
|
+
<th scope="col"><=% rodauth.oauth_grants_type_label %></th>
|
10
|
+
<th scope="col"><=% rodauth.oauth_grants_token_label %></th>
|
11
|
+
<th scope="col"><=% rodauth.oauth_grants_refresh_token_label %></th>
|
12
|
+
<th scope="col"><=% rodauth.oauth_grants_expires_in_label %></th>
|
13
|
+
<th scope="col"><=% rodauth.oauth_grants_revoked_at_label %></th>
|
14
|
+
<th scope="col"><=% rodauth.oauth_grants_scopes_label %></th>
|
15
|
+
<th scope="col"><span class="badge badge-pill badge-dark"><%= grants_count %></span>
|
16
|
+
</tr>
|
17
|
+
</thead>
|
18
|
+
<tbody>
|
19
|
+
<% oauth_grants.each do |oauth_grant| %>
|
20
|
+
<tr>
|
21
|
+
<td><%= oauth_grant[rodauth.oauth_grants_type_column] %></td>
|
22
|
+
<td><code class="token"><%= oauth_grant[rodauth.oauth_grants_token_column] %></code></td>
|
23
|
+
<td><code class="token"><%= oauth_grant[rodauth.oauth_grants_refresh_token_column] %></code></td>
|
24
|
+
<td><%= oauth_grant[rodauth.oauth_grants_expires_in_column] %></td>
|
25
|
+
<td><%= oauth_grant[rodauth.oauth_grants_revoked_at_column] %></td>
|
26
|
+
<td><%= oauth_grant[rodauth.oauth_grants_scopes_column] %></td>
|
27
|
+
<td>
|
28
|
+
<% if !oauth_grant[rodauth.oauth_grants_revoked_at_column] %>
|
29
|
+
<%= form_tag rodauth.revoke_path, method: :post do %>
|
30
|
+
<%= hidden_field_tag :token_type_hint, "access_token" %>
|
31
|
+
<%= hidden_field_tag :token, oauth_grant[rodauth.oauth_grants_token_column] %>
|
32
|
+
<%= submit_tag rodauth.oauth_grant_revoke_button, class: "btn btn-danger" %>
|
33
|
+
<% end %>
|
34
|
+
<% end %>
|
35
|
+
</td>
|
36
|
+
</tr>
|
37
|
+
<% end %>
|
38
|
+
</tbody>
|
39
|
+
</table>
|
40
|
+
<%= rodauth.oauth_management_pagination_links(@oauth_grants) %>
|
41
|
+
<% end %>
|
@@ -4,7 +4,7 @@
|
|
4
4
|
<%= link_to rodauth.new_oauth_application_page_title, "#{rodauth.oauth_applications_path}/new", class: "btn btn-secondary" %>
|
5
5
|
</div>
|
6
6
|
<% if apps_count.zero? %>
|
7
|
-
<p
|
7
|
+
<p><%= rodauth.oauth_no_applications_text %></p>
|
8
8
|
<% else %>
|
9
9
|
<table class="table">
|
10
10
|
<thead>
|
@@ -26,5 +26,5 @@
|
|
26
26
|
<% end %>
|
27
27
|
</tbody>
|
28
28
|
</table>
|
29
|
-
<%= rodauth.oauth_management_pagination_links(oauth_applications_ds) %>
|
29
|
+
<%= rodauth.oauth_management_pagination_links(oauth_applications_ds).html_safe %>
|
30
30
|
<% end %>
|
@@ -0,0 +1,37 @@
|
|
1
|
+
<% oauth_grants = rodauth.scope.instance_variable_get(:@oauth_grants) %>
|
2
|
+
<% grants_count = oauth_grants.count %>
|
3
|
+
<% if grants_count.zero? %>
|
4
|
+
<p><%= rodauth.oauth_no_grants_text %></p>
|
5
|
+
<% else %>
|
6
|
+
<table class="table">
|
7
|
+
<thead>
|
8
|
+
<tr>
|
9
|
+
<th scope="col"><=% rodauth.oauth_applications_name_label %></th>
|
10
|
+
<th scope="col"><=% rodauth.oauth_grants_type_label %></th>
|
11
|
+
<th scope="col"><=% rodauth.oauth_grants_token_label %></th>
|
12
|
+
<th scope="col"><=% rodauth.oauth_grants_refresh_token_label %></th>
|
13
|
+
<th scope="col"><=% rodauth.oauth_grants_expires_in_label %></th>
|
14
|
+
<th scope="col"><=% rodauth.oauth_grants_scopes_label %></th>
|
15
|
+
<th scope="col"><span class="badge badge-pill badge-dark"><%= grants_count %></span>
|
16
|
+
</tr>
|
17
|
+
</thead>
|
18
|
+
<tbody>
|
19
|
+
<% oauth_grants.each do |oauth_grant| %>
|
20
|
+
<tr>
|
21
|
+
<td><%= oauth_grant[rodauth.oauth_applications_name_column] %></td>
|
22
|
+
<td><%= oauth_grant[rodauth.oauth_grants_type_column] %></td>
|
23
|
+
<td><code class="token"><%= oauth_grant[rodauth.oauth_grants_token_column] %></code></td>
|
24
|
+
<td><code class="token"><%= oauth_grant[rodauth.oauth_grants_refresh_token_column] %></code></td>
|
25
|
+
<td><%= oauth_grant[rodauth.oauth_grants_expires_in_column] %></td>
|
26
|
+
<td><%= oauth_grant[rodauth.oauth_grants_scopes_column] %></td>
|
27
|
+
<td>
|
28
|
+
<%= form_tag rodauth.oauth_grant_path(oauth_grant[rodauth.oauth_grants_id_column]), method: :post do %>
|
29
|
+
<%= submit_tag rodauth.oauth_grant_revoke_button, class: "btn btn-danger" %>
|
30
|
+
<% end %>
|
31
|
+
</td>
|
32
|
+
</tr>
|
33
|
+
<% end %>
|
34
|
+
</tbody>
|
35
|
+
</table>
|
36
|
+
<%= rodauth.oauth_management_pagination_links(oauth_grants) %>
|
37
|
+
<% end %>
|