rodauth-oauth 0.10.4 → 1.0.0.pre.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/MIGRATION-GUIDE-v1.md +286 -0
  3. data/README.md +22 -30
  4. data/doc/release_notes/1_0_0_beta1.md +38 -0
  5. data/lib/generators/rodauth/oauth/install_generator.rb +0 -1
  6. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +4 -6
  7. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb +1 -1
  8. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb +2 -2
  9. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +1 -6
  10. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +0 -2
  11. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_grants.html.erb +41 -0
  12. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +2 -2
  13. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_grants.html.erb +37 -0
  14. data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +18 -29
  15. data/lib/rodauth/features/oauth_application_management.rb +59 -72
  16. data/lib/rodauth/features/oauth_assertion_base.rb +19 -23
  17. data/lib/rodauth/features/oauth_authorization_code_grant.rb +35 -88
  18. data/lib/rodauth/features/oauth_authorize_base.rb +103 -20
  19. data/lib/rodauth/features/oauth_base.rb +365 -302
  20. data/lib/rodauth/features/oauth_client_credentials_grant.rb +20 -18
  21. data/lib/rodauth/features/{oauth_device_grant.rb → oauth_device_code_grant.rb} +62 -73
  22. data/lib/rodauth/features/oauth_dynamic_client_registration.rb +46 -28
  23. data/lib/rodauth/features/oauth_grant_management.rb +70 -0
  24. data/lib/rodauth/features/oauth_implicit_grant.rb +25 -24
  25. data/lib/rodauth/features/oauth_jwt.rb +52 -688
  26. data/lib/rodauth/features/oauth_jwt_base.rb +435 -0
  27. data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +45 -17
  28. data/lib/rodauth/features/oauth_jwt_jwks.rb +47 -0
  29. data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +62 -0
  30. data/lib/rodauth/features/oauth_management_base.rb +2 -0
  31. data/lib/rodauth/features/oauth_pkce.rb +22 -26
  32. data/lib/rodauth/features/oauth_resource_indicators.rb +33 -21
  33. data/lib/rodauth/features/oauth_resource_server.rb +59 -0
  34. data/lib/rodauth/features/oauth_saml_bearer_grant.rb +5 -1
  35. data/lib/rodauth/features/oauth_token_introspection.rb +76 -46
  36. data/lib/rodauth/features/oauth_token_revocation.rb +46 -33
  37. data/lib/rodauth/features/oidc.rb +188 -95
  38. data/lib/rodauth/features/oidc_dynamic_client_registration.rb +89 -53
  39. data/lib/rodauth/oauth/database_extensions.rb +8 -6
  40. data/lib/rodauth/oauth/http_extensions.rb +61 -0
  41. data/lib/rodauth/oauth/railtie.rb +20 -0
  42. data/lib/rodauth/oauth/version.rb +1 -1
  43. data/lib/rodauth/oauth.rb +29 -1
  44. data/locales/en.yml +32 -22
  45. data/locales/pt.yml +32 -22
  46. data/templates/authorize.str +19 -24
  47. data/templates/device_search.str +1 -1
  48. data/templates/device_verification.str +2 -2
  49. data/templates/jwks_field.str +1 -0
  50. data/templates/new_oauth_application.str +1 -2
  51. data/templates/oauth_application.str +2 -2
  52. data/templates/oauth_application_oauth_grants.str +54 -0
  53. data/templates/oauth_applications.str +2 -2
  54. data/templates/oauth_grants.str +52 -0
  55. metadata +20 -16
  56. data/lib/generators/rodauth/oauth/templates/app/models/oauth_token.rb +0 -4
  57. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +0 -39
  58. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +0 -35
  59. data/lib/rodauth/features/oauth.rb +0 -9
  60. data/lib/rodauth/features/oauth_http_mac.rb +0 -86
  61. data/lib/rodauth/features/oauth_token_management.rb +0 -81
  62. data/lib/rodauth/oauth/refinements.rb +0 -48
  63. data/templates/jwt_public_key_field.str +0 -4
  64. data/templates/oauth_application_oauth_tokens.str +0 -52
  65. data/templates/oauth_tokens.str +0 -50
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa43bf0d9c1f6d8ac7a9e2c05b9f9751a68bf0720d47bcdcd92090cdc80b8ec6
4
- data.tar.gz: 14101a64005ea99770b2548c3dda8589b06c10cb8d26a4bb30a9e396c301ec17
3
+ metadata.gz: 299307b4879c519f6bcf7e5cfb875b75ac1d6b73b90371d3c9925baaf50dee08
4
+ data.tar.gz: 05a87d3e473b514f7cbb67d841a4a9185154dd1d15933d3321a3e4946c9c28ed
5
5
  SHA512:
6
- metadata.gz: d5ca40d77877ff713dad6bee5d6424d869a8e6d61101fe87ff18c97372e3c9d8d2acd6fc3450ea8f938582f42740ff5da1326a3501b46e43ada73d1d192592a8
7
- data.tar.gz: 4cd099ee6f8e8b5195fb62d8370e46063fa88a1eb3631cad0705fff076d627c891e6cbfc147e7f0c3c4ea651463f611e87c756e93f65b9475322d523f5b101b1
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
- * `oauth_device_grant` - [Device code grant (off by default)](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-device-flow-15);
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
- * Supports [JWT Secured Authorization Request](https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-20);
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, :oauth
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, :oauth
146
+ enable :login, :oauth_authorization_code_grant
148
147
  # ...
149
- oauth_grants_table "access_grants"
150
- oauth_grants_code_column "authorization_code"
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 :oauth
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
- Although not human-friendly as passwords, for security reasons, you might not want to store access (and refresh) tokens in the database. If that is the case, You'll have to add the respective hash columns in the table:
243
+ Access tokens, refresh tokens and client secrets are hashed before being stored in the database (using `bcrypt`), by default.
246
244
 
247
- ```ruby
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 :oauth
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
- #### Client Secret
264
-
265
- By default, it's expected that the "client secret" property from an OAuth application is only known by the owner, and only the hash is stored in the database; this way, the authorization server doesn't know what the client secret is, only the application owner. The provided [OAuth Applications Extensions](#oauth-applications) application form contains a "Client Secret" input field for this reason.
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
- However, this extension is optional, and you might want to generate the secrets and store them as is. In that case, you'll have to re-define some options:
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 :oauth
272
- secret_matches? ->(application, secret){ application[:client_secret] == secret }
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.3 . Besides that, it should support all rubies that rodauth and roda support, including JRuby and truffleruby.
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.
@@ -23,7 +23,6 @@ module Rodauth::OAuth::Rails
23
23
 
24
24
  template "app/models/oauth_application.rb"
25
25
  template "app/models/oauth_grant.rb"
26
- template "app/models/oauth_token.rb"
27
26
  end
28
27
 
29
28
  private
@@ -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">The application <%= link_to rodauth.oauth_application[rodauth.oauth_applications_name_column], rodauth.oauth_application[rodauth.oauth_applications_homepage_url_column] %> would like to access your data.</p>
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.oauth_tokens_scopes_label %></h1>
26
+ <h1 class="display-6"><%= rodauth.oauth_grants_scopes_label %></h1>
27
27
 
28
- <% rodauth.scopes.each do |scope| %>
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, is_default, disabled: is_default, id: scope, class: "form-check-input" %>
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">Insert the user code from the device you'd like to authorize.</p>
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">The device with user code <%= oauth_grant[rodauth.oauth_grants_user_code_column] %> would like to access your data.</p>
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.oauth_tokens_scopes_label %></h1>
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| %>
@@ -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, scope == rodauth.oauth_application_default_scope, id: scope, class: "form-check-input" %>
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>No oauth applications yet!</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 %>