rodauth-oauth 0.7.4 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -424
- data/README.md +30 -390
- data/doc/release_notes/0_0_1.md +3 -0
- data/doc/release_notes/0_0_2.md +15 -0
- data/doc/release_notes/0_0_3.md +31 -0
- data/doc/release_notes/0_0_4.md +36 -0
- data/doc/release_notes/0_0_5.md +36 -0
- data/doc/release_notes/0_0_6.md +21 -0
- data/doc/release_notes/0_1_0.md +44 -0
- data/doc/release_notes/0_2_0.md +43 -0
- data/doc/release_notes/0_3_0.md +28 -0
- data/doc/release_notes/0_4_0.md +18 -0
- data/doc/release_notes/0_4_1.md +9 -0
- data/doc/release_notes/0_4_2.md +5 -0
- data/doc/release_notes/0_4_3.md +3 -0
- data/doc/release_notes/0_5_0.md +11 -0
- data/doc/release_notes/0_5_1.md +13 -0
- data/doc/release_notes/0_6_0.md +9 -0
- data/doc/release_notes/0_6_1.md +6 -0
- data/doc/release_notes/0_7_0.md +20 -0
- data/doc/release_notes/0_7_1.md +10 -0
- data/doc/release_notes/0_7_2.md +21 -0
- data/doc/release_notes/0_7_3.md +10 -0
- data/doc/release_notes/0_7_4.md +5 -0
- data/doc/release_notes/0_8_0.md +37 -0
- data/doc/release_notes/0_9_0.md +56 -0
- data/doc/release_notes/0_9_1.md +9 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +25 -4
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb +11 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb +20 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +27 -10
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +17 -5
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +39 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +6 -5
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +12 -15
- data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +21 -1
- data/lib/rodauth/features/oauth.rb +3 -1418
- data/lib/rodauth/features/oauth_application_management.rb +225 -0
- data/lib/rodauth/features/oauth_assertion_base.rb +96 -0
- data/lib/rodauth/features/oauth_authorization_code_grant.rb +252 -0
- data/lib/rodauth/features/oauth_authorization_server.rb +0 -0
- data/lib/rodauth/features/oauth_base.rb +778 -0
- data/lib/rodauth/features/oauth_client_credentials_grant.rb +33 -0
- data/lib/rodauth/features/oauth_device_grant.rb +220 -0
- data/lib/rodauth/features/oauth_dynamic_client_registration.rb +252 -0
- data/lib/rodauth/features/oauth_http_mac.rb +3 -21
- data/lib/rodauth/features/oauth_implicit_grant.rb +59 -0
- data/lib/rodauth/features/oauth_jwt.rb +275 -100
- data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +59 -0
- data/lib/rodauth/features/oauth_management_base.rb +68 -0
- data/lib/rodauth/features/oauth_pkce.rb +98 -0
- data/lib/rodauth/features/oauth_resource_server.rb +21 -0
- data/lib/rodauth/features/oauth_saml_bearer_grant.rb +102 -0
- data/lib/rodauth/features/oauth_token_introspection.rb +108 -0
- data/lib/rodauth/features/oauth_token_management.rb +79 -0
- data/lib/rodauth/features/oauth_token_revocation.rb +109 -0
- data/lib/rodauth/features/oidc.rb +38 -9
- data/lib/rodauth/features/oidc_dynamic_client_registration.rb +147 -0
- data/lib/rodauth/oauth/database_extensions.rb +15 -2
- data/lib/rodauth/oauth/jwe_extensions.rb +64 -0
- data/lib/rodauth/oauth/refinements.rb +48 -0
- data/lib/rodauth/oauth/ttl_store.rb +9 -3
- data/lib/rodauth/oauth/version.rb +1 -1
- data/locales/en.yml +33 -12
- data/templates/authorize.str +57 -8
- data/templates/client_secret_field.str +2 -2
- data/templates/description_field.str +1 -1
- data/templates/device_search.str +11 -0
- data/templates/device_verification.str +24 -0
- data/templates/homepage_url_field.str +2 -2
- data/templates/jwks_field.str +4 -0
- data/templates/jwt_public_key_field.str +4 -0
- data/templates/name_field.str +1 -1
- data/templates/new_oauth_application.str +9 -0
- data/templates/oauth_application.str +7 -3
- data/templates/oauth_application_oauth_tokens.str +52 -0
- data/templates/oauth_applications.str +3 -2
- data/templates/oauth_tokens.str +10 -11
- data/templates/redirect_uri_field.str +2 -2
- metadata +80 -3
- data/lib/rodauth/features/oauth_saml.rb +0 -104
data/README.md
CHANGED
@@ -9,27 +9,33 @@ This is an extension to the `rodauth` gem which implements the [OAuth 2.0 framew
|
|
9
9
|
|
10
10
|
This gem implements the following RFCs and features of OAuth:
|
11
11
|
|
12
|
-
* [The OAuth 2.0 protocol framework](https://tools.ietf.org/html/rfc6749):
|
13
|
-
* [Authorization grant flow](https://tools.ietf.org/html/rfc6749#section-1.3);
|
12
|
+
* `oauth` - [The OAuth 2.0 protocol framework](https://tools.ietf.org/html/rfc6749):
|
14
13
|
* [Access Token generation](https://tools.ietf.org/html/rfc6749#section-1.4);
|
15
|
-
* [Access Token refresh](https://tools.ietf.org/html/rfc6749#section-1.5);
|
16
|
-
* [
|
17
|
-
* [
|
18
|
-
* [
|
19
|
-
* [
|
20
|
-
* [
|
21
|
-
*
|
22
|
-
* [
|
23
|
-
*
|
24
|
-
*
|
14
|
+
* [Access Token refresh token grant](https://tools.ietf.org/html/rfc6749#section-1.5);
|
15
|
+
* `oauth_authorization_code_grant` - [Authorization code grant](https://tools.ietf.org/html/rfc6749#section-1.3);
|
16
|
+
* `oauth_implicit_grant` - [Implicit grant (off by default)](https://tools.ietf.org/html/rfc6749#section-4.2);
|
17
|
+
* `oauth_client_credentials_grant` - [Client credentials grant (off by default)](https://tools.ietf.org/html/rfc6749#section-4.4);
|
18
|
+
* `oauth_device_grant` - [Device code grant (off by default)](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-device-flow-15);
|
19
|
+
* `oauth_token_revocation` - [Token revocation](https://tools.ietf.org/html/rfc7009);
|
20
|
+
* `oauth_token_introspection` - [Token introspection](https://tools.ietf.org/html/rfc7662);
|
21
|
+
* [Authorization Server Metadata](https://tools.ietf.org/html/rfc8414);
|
22
|
+
* `oauth_pkce` - [PKCE](https://tools.ietf.org/html/rfc7636);
|
23
|
+
* Access Type (Token refresh online and offline);
|
24
|
+
* `oauth_jwt` - [JWT Access Tokens](https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-07);
|
25
|
+
* `oauth_http_mac` - [MAC Authentication Scheme](https://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-02);
|
26
|
+
* `oauth_assertion_base` - [Assertion Framework](https://datatracker.ietf.org/doc/html/rfc7521);
|
27
|
+
* `oauth_saml_bearer_grant` - [SAML 2.0 Bearer Assertion](https://datatracker.ietf.org/doc/html/rfc7522);
|
28
|
+
* `oauth_jwt_bearer_grant` - [JWT Bearer Assertion](https://datatracker.ietf.org/doc/html/rfc7523);
|
25
29
|
* [JWT Secured Authorization Requests](https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-20);
|
30
|
+
* [Dynamic Client Registration Protocol](https://datatracker.ietf.org/doc/html/rfc7591);
|
26
31
|
* OAuth application and token management dashboards;
|
27
32
|
|
28
|
-
It also implements the [OpenID Connect layer](https://openid.net/connect/) on top of the OAuth features it provides, including:
|
33
|
+
It also implements the [OpenID Connect layer](https://openid.net/connect/) (via the `openid` feature) on top of the OAuth features it provides, including:
|
29
34
|
|
30
35
|
* [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html);
|
31
36
|
* [OpenID Connect Discovery](https://openid.net/specs/openid-connect-discovery-1_0-29.html);
|
32
37
|
* [OpenID Multiple Response Types](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html);
|
38
|
+
* [OpenID Connect Dynamic Client Registration](https://openid.net/specs/openid-connect-registration-1_0.html);
|
33
39
|
* [RP Initiated Logout](https://openid.net/specs/openid-connect-rpinitiated-1_0.html);
|
34
40
|
|
35
41
|
This gem supports also rails (through [rodauth-rails]((https://github.com/janko/rodauth-rails))).
|
@@ -60,6 +66,11 @@ Or install it yourself as:
|
|
60
66
|
| Wiki | https://gitlab.com/honeyryderchuck/rodauth-oauth/wikis/home |
|
61
67
|
| CI | https://gitlab.com/honeyryderchuck/rodauth-oauth/pipelines |
|
62
68
|
|
69
|
+
## Articles
|
70
|
+
|
71
|
+
* [How to use rodauth-oauth with rails and rodauth](https://honeyryderchuck.gitlab.io/httpx/2021/03/15/oidc-provider-on-rails-using-rodauth-oauth.html)
|
72
|
+
* [How to use rodauth-oauth with rails and without rodauth](https://honeyryderchuck.gitlab.io/httpx/2021/09/08/using-rodauth-oauth-in-rails-without-rodauth-based-auth.html)
|
73
|
+
|
63
74
|
## Usage
|
64
75
|
|
65
76
|
This tutorial assumes you already read the documentation and know how to set up `rodauth`. After that, integrating `roda-auth` will look like:
|
@@ -118,134 +129,12 @@ end
|
|
118
129
|
|
119
130
|
### Example (TL;DR)
|
120
131
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
Generating tokens happens mostly server-to-server, so here's an example using:
|
125
|
-
|
126
|
-
#### Access Token Generation
|
127
|
-
|
128
|
-
##### HTTPX
|
129
|
-
|
130
|
-
```ruby
|
131
|
-
require "httpx"
|
132
|
-
response = HTTPX.post("https://auth_server/token",json: {
|
133
|
-
client_id: ENV["OAUTH_CLIENT_ID"],
|
134
|
-
client_secret: ENV["OAUTH_CLIENT_SECRET"],
|
135
|
-
grant_type: "authorization_code",
|
136
|
-
code: "oiweicnewdh32fhoi3hf3ihfo2ih3f2o3as"
|
137
|
-
})
|
138
|
-
response.raise_for_status
|
139
|
-
payload = JSON.parse(response.to_s)
|
140
|
-
puts payload #=> {"access_token" => "awr23f3h8f9d2h89...", "refresh_token" => "23fkop3kr290kc..." ....
|
141
|
-
```
|
142
|
-
|
143
|
-
##### cURL
|
144
|
-
|
145
|
-
```
|
146
|
-
> curl --data '{"client_id":"$OAUTH_CLIENT_ID","client_secret":"$OAUTH_CLIENT_SECRET","grant_type":"authorization_code","code":"oiweicnewdh32fhoi3hf3ihfo2ih3f2o3as"}' https://auth_server/token
|
147
|
-
```
|
148
|
-
|
149
|
-
#### Refresh Token
|
150
|
-
|
151
|
-
Refreshing expired tokens also happens mostly server-to-server, here's an example:
|
152
|
-
|
153
|
-
##### HTTPX
|
154
|
-
|
155
|
-
```ruby
|
156
|
-
require "httpx"
|
157
|
-
response = HTTPX.post("https://auth_server/token",json: {
|
158
|
-
client_id: ENV["OAUTH_CLIENT_ID"],
|
159
|
-
client_secret: ENV["OAUTH_CLIENT_SECRET"],
|
160
|
-
grant_type: "refresh_token",
|
161
|
-
token: "2r89hfef4j9f90d2j2390jf390g"
|
162
|
-
})
|
163
|
-
response.raise_for_status
|
164
|
-
payload = JSON.parse(response.to_s)
|
165
|
-
puts payload #=> {"access_token" => "awr23f3h8f9d2h89...", "token_type" => "Bearer" ....
|
166
|
-
```
|
167
|
-
|
168
|
-
##### cURL
|
169
|
-
|
170
|
-
```
|
171
|
-
> curl -H "X-your-auth-scheme: $SERVER_KEY" --data '{"client_id":"$OAUTH_CLIENT_ID","client_secret":"$OAUTH_CLIENT_SECRET","grant_type":"token","token":"2r89hfef4j9f90d2j2390jf390g"}' https://auth_server/token
|
172
|
-
```
|
173
|
-
|
174
|
-
#### Revoking tokens
|
175
|
-
|
176
|
-
Token revocation can be done both by the identity owner or the application owner, and can therefore be done either online (browser-based form) or server-to-server. Here's an example using server-to-server:
|
177
|
-
|
178
|
-
```ruby
|
179
|
-
require "httpx"
|
180
|
-
httpx = HTTPX.plugin(:basic_authorization)
|
181
|
-
response = httpx.basic_authentication(ENV["CLIENT_ID"], ENV["CLIENT_SECRET"])
|
182
|
-
.post("https://auth_server/revoke",json: {
|
183
|
-
token_type_hint: "access_token", # can also be "refresh:tokn"
|
184
|
-
token: "2r89hfef4j9f90d2j2390jf390g"
|
185
|
-
})
|
186
|
-
response.raise_for_status
|
187
|
-
payload = JSON.parse(response.to_s)
|
188
|
-
puts payload #=> {"access_token" => "awr23f3h8f9d2h89...", "token_type" => "Bearer" ....
|
189
|
-
```
|
190
|
-
|
191
|
-
##### cURL
|
192
|
-
|
193
|
-
```
|
194
|
-
> curl -H "X-your-auth-scheme: $SERVER_KEY" --data '{"client_id":"$OAUTH_CLIENT_ID","token_type_hint":"access_token","token":"2r89hfef4j9f90d2j2390jf390g"}' https://auth_server/revoke
|
195
|
-
```
|
196
|
-
|
197
|
-
#### Token introspection
|
198
|
-
|
199
|
-
Token revocation can be used to determine the state of a token (whether active, what's the scope...) . Here's an example using server-to-server:
|
200
|
-
|
201
|
-
```ruby
|
202
|
-
require "httpx"
|
203
|
-
httpx = HTTPX.plugin(:basic_authorization)
|
204
|
-
response = httpx.basic_authentication(ENV["CLIENT_ID"], ENV["CLIENT_SECRET"])
|
205
|
-
.post("https://auth_server/introspect",json: {
|
206
|
-
token_type_hint: "access_token", # can also be "refresh:tokn"
|
207
|
-
token: "2r89hfef4j9f90d2j2390jf390g"
|
208
|
-
})
|
209
|
-
response.raise_for_status
|
210
|
-
payload = JSON.parse(response.to_s)
|
211
|
-
puts payload #=> {"active" => true, "scope" => "read write" ....
|
212
|
-
```
|
213
|
-
|
214
|
-
##### cURL
|
215
|
-
|
216
|
-
```
|
217
|
-
> curl -H "X-your-auth-scheme: $SERVER_KEY" --data '{"client_id":"$OAUTH_CLIENT_ID","token_type_hint":"access_token","token":"2r89hfef4j9f90d2j2390jf390g"}' https://auth_server/revoke
|
218
|
-
```
|
219
|
-
|
220
|
-
### Authorization Server Metadata
|
221
|
-
|
222
|
-
The Authorization Server Metadata endpoint can be used by clients to obtain the information needed to interact with an
|
223
|
-
OAuth 2.0 authorization server, i.e. know which endpoint is used to authorize clients.
|
224
|
-
|
225
|
-
Because this endpoint **must be https://AUTHSERVER/.well-known/oauth-authorization-server**, you'll have to define it at the root-level of your app:
|
226
|
-
|
227
|
-
```ruby
|
228
|
-
plugin :rodauth do
|
229
|
-
# enable it in the plugin
|
230
|
-
enable :login, :oauth
|
231
|
-
oauth_application_default_scope %w[profile.read]
|
232
|
-
oauth_application_scopes %w[profile.read profile.write]
|
233
|
-
end
|
234
|
-
|
235
|
-
# then, inside roda
|
236
|
-
|
237
|
-
route do |r|
|
238
|
-
r.rodauth
|
239
|
-
# server metadata endpoint
|
240
|
-
rodauth.oauth_server_metadata
|
132
|
+
Just [check our example applications](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/tree/master/examples/).
|
241
133
|
|
242
|
-
# now, your oauth and app code...
|
243
|
-
|
244
|
-
```
|
245
134
|
|
246
135
|
### Database migrations
|
247
136
|
|
248
|
-
You have to generate database tables for
|
137
|
+
You have to generate database tables for accounts, oauth applications, grants and tokens. In order for you to hit the ground running, [here's a set of migrations (using `sequel`) to generate the needed tables](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/tree/master/test/migrate) (omit the first 2 if you already have account tables, and [follow recommendations from rodauth accordingly](https://github.com/jeremyevans/rodauth)).
|
249
138
|
|
250
139
|
You can change column names or even use existing tables, however, be aware that you'll have to define new column accessors at the `rodauth` plugin declaration level. Let's say, for instance, you'd like to change the `oauth_grants` table name to `access_grants`, and it's `code` column to `authorization_code`; then, you'd have to do the following:
|
251
140
|
|
@@ -267,16 +156,7 @@ You'll have to generate HTML templates for the Oauth Authorization form.
|
|
267
156
|
|
268
157
|
The rodauth default setup expects the roda `render` plugin to be activated; by default, it expects a `views` directory to be defined in the project root folder. The Oauth Authorization template must be therefore defined there, and it should be called `oauth_authorize.(erb|str|...)` (read the [roda `render` plugin documentation](http://roda.jeremyevans.net/rdoc/classes/Roda/RodaPlugins/Render.html) for more info about HTML templating).
|
269
158
|
|
270
|
-
###
|
271
|
-
|
272
|
-
Once you set it up, by default, the following endpoints will be available:
|
273
|
-
|
274
|
-
* `GET /authorize`: Loads the OAuth authorization HTML form;
|
275
|
-
* `POST /authorize`: Responds to an OAuth authorization request, as [per the spec](https://tools.ietf.org/html/rfc6749#section-4);
|
276
|
-
* `POST /token`: Generates OAuth tokens as [per the spec](https://tools.ietf.org/html/rfc6749#section-4.4.2);
|
277
|
-
* `POST /revoke`: Revokes OAuth tokens as [per the spec](https://tools.ietf.org/html/rfc7009);
|
278
|
-
|
279
|
-
### OAuth applications
|
159
|
+
### OAuth applications management
|
280
160
|
|
281
161
|
This feature is **optional**, as not all authorization servers will want a full oauth applications dashboard. However, if you do and you don't want to do the work yourself, you can set it up in your roda app like this:
|
282
162
|
|
@@ -290,24 +170,11 @@ route do |r|
|
|
290
170
|
end
|
291
171
|
```
|
292
172
|
|
293
|
-
|
294
|
-
|
295
|
-
* `GET /oauth-applications`: returns the OAuth applications HTML dashboard;
|
296
|
-
* `GET /oauth-applications/{application_id}`: returns an OAuth application HTML page;
|
297
|
-
* `GET /oauth-applications/{application_id}/oauth-tokens`: returns the OAuth tokens from an OAuth application HTML page;
|
298
|
-
* `GET /oauth-applications/new`: returns a new OAuth application form;
|
299
|
-
* `POST /oauth-applications`: processes a new OAuth application request;
|
300
|
-
|
301
|
-
As in the OAuth authorization form example, you'll have to define the following HTML templates in order to use this feature:
|
302
|
-
|
303
|
-
* `oauth_applications.(erb|str|...)`: the list of OAuth applications;
|
304
|
-
* `oauth_application.(erb|str|...)`: the OAuth application page;
|
305
|
-
* `new_oauth_application.(erb|str|...)`: the new OAuth application form;
|
306
|
-
* `oauth_tokens.(erb|str|...)`: the list of OAuth tokens from an application;
|
173
|
+
Navigate to `"http://your-app/oauth-applications"` and click around.
|
307
174
|
|
308
175
|
## Rails
|
309
176
|
|
310
|
-
|
177
|
+
Support for `rails` is achieved thanks to [rodauth-rails](https://github.com/janko/rodauth-rails). Therefore, the first step you'll have to take is to add it to your dependencies.
|
311
178
|
|
312
179
|
You'll have to run the generator task to create the necessary migrations and views:
|
313
180
|
|
@@ -316,7 +183,7 @@ You'll have to run the generator task to create the necessary migrations and vie
|
|
316
183
|
# create a migration file, db/migrate(*_create_rodauth_oauth.rb);
|
317
184
|
# Oauth Application, Grant and Token models into app/models;
|
318
185
|
> bundle exec rails generate rodauth:oauth:views
|
319
|
-
#
|
186
|
+
# copies default view files into app/views/rodauth
|
320
187
|
```
|
321
188
|
|
322
189
|
You are encouraged to check the output and adapt it to your needs.
|
@@ -403,230 +270,6 @@ plugin :rodauth do
|
|
403
270
|
end
|
404
271
|
```
|
405
272
|
|
406
|
-
### Access Type (default: "offline")
|
407
|
-
|
408
|
-
The "access_type" feature allows the authorization server to emit access tokens with no associated refresh token. This means that users with expired access tokens will have to go through the OAuth flow everytime they need a new one.
|
409
|
-
|
410
|
-
In order to enable this option, add "access_type=online" to the query params section of the authorization url.
|
411
|
-
|
412
|
-
#### Approval Prompt
|
413
|
-
|
414
|
-
When using "online grants", one can use an extra query param in the URL, "approval_prompt", which when set to "auto", will skip the authorization form (on the other hand, if one wants to force the authorization form for all grants, then you can set it to "force", or don't set it at all, as it's the default).
|
415
|
-
|
416
|
-
This will only work **if there was a previous successful online grant** for the same application, scopes and redirect URI.
|
417
|
-
|
418
|
-
#### DB schema
|
419
|
-
|
420
|
-
the "oauth_grants" table will have to include the "access_type" row:
|
421
|
-
|
422
|
-
```ruby
|
423
|
-
# in migration
|
424
|
-
String :access_type, null: false, default: "offline"
|
425
|
-
```
|
426
|
-
|
427
|
-
If you want to disable this flow altogether, you can:
|
428
|
-
|
429
|
-
```ruby
|
430
|
-
enable :oauth
|
431
|
-
use_oauth_access_type? false
|
432
|
-
```
|
433
|
-
|
434
|
-
|
435
|
-
### Implicit Grant (default: disabled)
|
436
|
-
|
437
|
-
The implicit grant flow is part of the original OAuth 2.0 RFC, however, if you care about security, you are **strongly recommended** not to enable it.
|
438
|
-
|
439
|
-
However, if you really need it, just pass the option when enabling the `rodauth` plugin:
|
440
|
-
|
441
|
-
```ruby
|
442
|
-
plugin :rodauth do
|
443
|
-
enable :oauth
|
444
|
-
use_oauth_implicit_grant_type true
|
445
|
-
end
|
446
|
-
```
|
447
|
-
|
448
|
-
And add "response_type=token" to the query params section of the authorization url.
|
449
|
-
|
450
|
-
### PKCE
|
451
|
-
|
452
|
-
The "Proof Key for Code Exchange by OAuth Public Clients" (aka PKCE) flow, which is **particularly recommended for OAuth integration in mobile apps**, is transparently supported by `rodauth-oauth`, by adding the `code_challenge_method=S256&code_challenge=$YOUR_CODE_CHALLENGE` query params to the authorization url. Once you do that, you'll have to pass the `code_verifier` when generating a token:
|
453
|
-
|
454
|
-
```ruby
|
455
|
-
# with httpx
|
456
|
-
require "httpx"
|
457
|
-
response = HTTPX.post("https://auth_server/token",json: {
|
458
|
-
client_id: ENV["OAUTH_CLIENT_ID"],
|
459
|
-
grant_type: "authorization_code",
|
460
|
-
code: "oiweicnewdh32fhoi3hf3ihfo2ih3f2o3as",
|
461
|
-
code_verifier: your_code_verifier_here
|
462
|
-
})
|
463
|
-
response.raise_for_status
|
464
|
-
payload = JSON.parse(response.to_s)
|
465
|
-
puts payload #=> {"access_token" => ....
|
466
|
-
```
|
467
|
-
|
468
|
-
By default, the pkce integration sets "S256" as the default challenge method. If you value security, you **should not use plain**. However, if you really need to, you can set it in the `rodauth` plugin:
|
469
|
-
|
470
|
-
```ruby
|
471
|
-
plugin :rodauth do
|
472
|
-
enable :oauth
|
473
|
-
oauth_pkce_challenge_method "plain"
|
474
|
-
end
|
475
|
-
```
|
476
|
-
|
477
|
-
Although PKCE flow is supported out-of-the-box, it's not enforced by default. If you want to, you can force it, thereby forcing clients to generate a challenge:
|
478
|
-
|
479
|
-
```ruby
|
480
|
-
plugin :rodauth do
|
481
|
-
enable :oauth
|
482
|
-
oauth_require_pkce true
|
483
|
-
end
|
484
|
-
```
|
485
|
-
|
486
|
-
If you want, on the other hand. to disable this flow altogether, you can:
|
487
|
-
|
488
|
-
```ruby
|
489
|
-
enable :oauth
|
490
|
-
use_oauth_pkce? false
|
491
|
-
```
|
492
|
-
|
493
|
-
### HTTP Mac Authentication
|
494
|
-
|
495
|
-
You can enable HTTP MAC authentication like this:
|
496
|
-
|
497
|
-
```ruby
|
498
|
-
plugin :rodauth do
|
499
|
-
enable :oauth_http_mac
|
500
|
-
end
|
501
|
-
```
|
502
|
-
|
503
|
-
Generating an access token will deliver the following fields:
|
504
|
-
|
505
|
-
```ruby
|
506
|
-
# with httpx
|
507
|
-
require "httpx"
|
508
|
-
response = httpx.post("https://auth_server/token",json: {
|
509
|
-
client_id: env["oauth_client_id"],
|
510
|
-
client_secret: env["oauth_client_secret"],
|
511
|
-
grant_type: "authorization_code",
|
512
|
-
code: "oiweicnewdh32fhoi3hf3ihfo2ih3f2o3as"
|
513
|
-
})
|
514
|
-
response.raise_for_status
|
515
|
-
payload = json.parse(response.to_s)
|
516
|
-
puts payload #=> {
|
517
|
-
# "access_token" => ....
|
518
|
-
# "mac_key" => ....
|
519
|
-
# "mac_algorithm" =>
|
520
|
-
```
|
521
|
-
|
522
|
-
which you'll be able to use to generate the mac signature to send in the "Authorization" header.
|
523
|
-
|
524
|
-
#### DB schema
|
525
|
-
|
526
|
-
the "oauth_tokens" table will have to include a column for the mac key:
|
527
|
-
|
528
|
-
```ruby
|
529
|
-
# in migration
|
530
|
-
String :mac_key, token: true
|
531
|
-
```
|
532
|
-
|
533
|
-
|
534
|
-
### JWT Access Tokens
|
535
|
-
|
536
|
-
JWT Acess Tokens are great to avoid DB lookups when validation the authorization token. Quoting the RFC, *The approach is particularly common in topologies where the authorization server and resource server are not co-located, are not run by the same entity, or are otherwise separated by some boundary.*
|
537
|
-
|
538
|
-
You can enable JWT Access tokens by doing:
|
539
|
-
|
540
|
-
```ruby
|
541
|
-
plugin :rodauth do
|
542
|
-
enable :oauth_jwt
|
543
|
-
end
|
544
|
-
```
|
545
|
-
|
546
|
-
This will, by default, use the OAuth application as HMAC signature and "HS256" as the algorithm to sign the resulting JWT access tokens. You can tweak those features by editing the following options:
|
547
|
-
|
548
|
-
```ruby
|
549
|
-
enable :oauth_jwt
|
550
|
-
oauth_jwt_secret "SECRET"
|
551
|
-
oauth_jwt_algorithm "HS512"
|
552
|
-
```
|
553
|
-
|
554
|
-
You can look for other options in [the jwt gem documentation](https://github.com/jwt/ruby-jwt), as this is used under the hood.
|
555
|
-
|
556
|
-
#### Pub/Priv key
|
557
|
-
|
558
|
-
You can decide to keep a private key to encode the JWT token, while other clients hace the public key to decode it. You can then do it like:
|
559
|
-
|
560
|
-
```ruby
|
561
|
-
rsa_private = OpenSSL::PKey::RSA.generate 2048
|
562
|
-
rsa_public = rsa_private.public_key
|
563
|
-
|
564
|
-
plugin :rodauth do
|
565
|
-
enable :oauth_jwt
|
566
|
-
oauth_jwt_key rsa_private
|
567
|
-
oauth_jwt_public_key rsa_public
|
568
|
-
oauth_jwt_algorithm "RS256"
|
569
|
-
end
|
570
|
-
```
|
571
|
-
|
572
|
-
#### JWK
|
573
|
-
|
574
|
-
One can further encode the JWT token using JSON Web Keys. Here's how you could enable the feature:
|
575
|
-
|
576
|
-
```ruby
|
577
|
-
rsa_private = OpenSSL::PKey::RSA.generate 2048
|
578
|
-
rsa_public = rsa_private.public_key
|
579
|
-
|
580
|
-
plugin :rodauth do
|
581
|
-
enable :oauth_jwt
|
582
|
-
oauth_jwt_jwk_key rsa_private
|
583
|
-
oauth_jwt_jwk_public_key rsa_public
|
584
|
-
oauth_jwt_jwk_algorithm "RS256"
|
585
|
-
end
|
586
|
-
```
|
587
|
-
|
588
|
-
#### JWE
|
589
|
-
|
590
|
-
You can further instruct the jwt feature to encrypt the encoded token using JSON Web Encryption standard:
|
591
|
-
|
592
|
-
```ruby
|
593
|
-
jwe_key = OpenSSL::PKey::RSA.new(2048)
|
594
|
-
|
595
|
-
plugin :rodauth do
|
596
|
-
oauth_jwt_secret "SECRET"
|
597
|
-
oauth_jwt_algorithm "HS256"
|
598
|
-
oauth_jwt_jwe_key jwe_key
|
599
|
-
oauth_jwt_jwe_encryption_method "A192GCM"
|
600
|
-
end
|
601
|
-
```
|
602
|
-
|
603
|
-
which adds an extra layer of protection.
|
604
|
-
|
605
|
-
#### JWKS URI
|
606
|
-
|
607
|
-
A route is defined for getting the JWK Set in a JSON format; this is typically used by client applications, who need the JWK set to decode the JWT token. This URL is typically `https://oauth-server/jwks`.
|
608
|
-
|
609
|
-
#### JWT Bearer as authorization grant
|
610
|
-
|
611
|
-
One can emit a new access token by using the bearer access token as grant. This can be done emitting a request similar to this:
|
612
|
-
|
613
|
-
```ruby
|
614
|
-
# with httpx
|
615
|
-
require "httpx"
|
616
|
-
response = httpx.post("https://auth_server/token",json: {
|
617
|
-
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
618
|
-
assertion: "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImlzcyI6IkV4YW1wbGUiLCJpYXQiOjE1OTIwMDk1MDEsImNsaWVudF9pZCI6IkNMSUVOVF9JRCIsImV4cCI6MTU5MjAxMzEwMSwiYXVkIjpudWxsLCJzY29wZSI6InVzZXIucmVhZCB1c2VyLndyaXRlIiwianRpIjoiOGM1NTVjMjdiOWRjNDdmOTcyNWRkYzBhMjk0NzA1ZTA4NzFkY2JlN2Q5ZTNlMmVkNGE1ZTBiOGZlNTZlYzcxMSJ9.AlxKRtE3ec0mtyBSDx4VseND4eC6cH5ubtv8gfYxxsc"
|
619
|
-
})
|
620
|
-
response.raise_for_status
|
621
|
-
payload = json.parse(response.to_s)
|
622
|
-
puts payload #=> {
|
623
|
-
# "access_token" => "ey....
|
624
|
-
```
|
625
|
-
|
626
|
-
#### DB Schema
|
627
|
-
|
628
|
-
You'll still need the "oauth_tokens" table, however you can remove the "token" column.
|
629
|
-
|
630
273
|
#### Internationalization (i18n)
|
631
274
|
|
632
275
|
`rodauth-oauth` supports translating all user-facing text found in all pages and forms, by integrating with [rodauth-i18n](https://github.com/janko/rodauth-i18n). Just set it up in your application and `rodauth` configuration.
|
@@ -635,9 +278,6 @@ Default translations shipping with `rodauth-oauth` can be found [in this directo
|
|
635
278
|
|
636
279
|
(This feature is available since `v0.7`.)
|
637
280
|
|
638
|
-
#### Caveats
|
639
|
-
|
640
|
-
Although very handy for the mentioned use case, one can't revoke a JWT token on demand (it must expire first).
|
641
281
|
|
642
282
|
## Ruby support policy
|
643
283
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
## 0.0.2 (29/5/2020)
|
2
|
+
|
3
|
+
### Features
|
4
|
+
|
5
|
+
* Implementation of PKCE by OAuth Public Clients (https://tools.ietf.org/html/rfc7636);
|
6
|
+
* Implementation of grants using "access_type" and "approval_prompt" ([similar to what Google OAuth 2.0 API does](https://wiki.scn.sap.com/wiki/display/Security/Access+Google+APIs+using+the+OAuth+2.0+Client+API));
|
7
|
+
|
8
|
+
### Improvements
|
9
|
+
|
10
|
+
* Store token/refresh token hashes in the database, instead of the "plain" tokens;
|
11
|
+
* Client secret hashed by default, and provided by the application owner;
|
12
|
+
|
13
|
+
### Fix
|
14
|
+
|
15
|
+
* usage of client secret for authorizing the generation of tokens, as the spec mandates (and refraining from them when doing PKCE).
|
@@ -0,0 +1,31 @@
|
|
1
|
+
## 0.0.3 (5/6/2020)
|
2
|
+
|
3
|
+
### Features
|
4
|
+
|
5
|
+
#### `:oauth_http_mac`
|
6
|
+
|
7
|
+
A new feature builds on top of `:oauth` to allow MAC authorization.
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
plugin :rodauth do
|
11
|
+
enable :oauth_http_mac
|
12
|
+
# options here...
|
13
|
+
end
|
14
|
+
```
|
15
|
+
|
16
|
+
#### `:oauth_jwt`
|
17
|
+
|
18
|
+
Another new feature, this time supporting the generation of JWT access tokens.
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
plugin :rodauth do
|
22
|
+
enable :oauth_jwt
|
23
|
+
# options here...
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
### Improvements
|
28
|
+
|
29
|
+
* added options for disabling pkce and access type (respectively, `use_oauth_pkce?` and `use_oauth_access_type?`);
|
30
|
+
* renamed the existing `use_oauth_implicit_grant_type` to `use_oauth_implicit_grant_type?`;
|
31
|
+
* It's now usable as JSON API (small caveat: POST authorize will still redirect on success...);
|
@@ -0,0 +1,36 @@
|
|
1
|
+
## 0.0.4 (13/6/2020)
|
2
|
+
|
3
|
+
### Features
|
4
|
+
|
5
|
+
#### Token introspection
|
6
|
+
|
7
|
+
`rodauth-oauth` now ships with an introspection endpoint (`/oauth-introspect`).
|
8
|
+
|
9
|
+
#### Authorization Server Metadata
|
10
|
+
|
11
|
+
`rodauth-oauth` now allows to define an authorization metadata endpoint, which has to be defined at the route of the router:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
route do |r|
|
15
|
+
r.rodauth
|
16
|
+
rodauth.oauth_server_metadata
|
17
|
+
...
|
18
|
+
```
|
19
|
+
|
20
|
+
#### JWKs URI
|
21
|
+
|
22
|
+
the `oauth_jwt` feature now ships with an endpoint, `/oauth-jwks`, where client applications can retrieve the JWK set to verify generated tokens.
|
23
|
+
|
24
|
+
#### JWT access tokens as authorization grants
|
25
|
+
|
26
|
+
The `oauth_jwt` feature now allows the usage of access tokens to authorize the generation of new tokens, [as per the RFC](https://tools.ietf.org/html/rfc7523#section-4);
|
27
|
+
|
28
|
+
### Improvements
|
29
|
+
|
30
|
+
* using `client_secret_basic` authorization where client id/secret params were allowed (i.e. in the token and revoke endpoints, for example);
|
31
|
+
* improved JWK usage for both supported jwt libraries;
|
32
|
+
* marked `fetch_access_token` as auth_value_method, thereby allowing users to fetch the access token from other sources than the "Authorization" header (i.e. form body, query params, etc...)
|
33
|
+
|
34
|
+
### Bugfixes
|
35
|
+
|
36
|
+
* Fixed scope claim of JWT ("scopes" -> "scope");
|
@@ -0,0 +1,36 @@
|
|
1
|
+
### 0.0.5 (26/6/2020)
|
2
|
+
|
3
|
+
#### Features
|
4
|
+
|
5
|
+
* new option: `oauth_scope_separator` (default: `" "`), to define how scopes are stored;
|
6
|
+
|
7
|
+
##### Resource Server mode
|
8
|
+
|
9
|
+
`rodauth-oauth` can now be used in a resource server, i.e. only for authorizing access to resources:
|
10
|
+
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
plugin :rodauth do
|
14
|
+
enable :oauth
|
15
|
+
|
16
|
+
is_authorization_server? false
|
17
|
+
authorization_server_url "https://auth-server"
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
It **requires** the authorization to implement the server metadata endpoint (`/.well-known/oauth-authorization-server`), and if using JWS, the JWKs URI endpoint (unless `oauth_jwt_public_key` is defined).
|
22
|
+
|
23
|
+
#### Improvements
|
24
|
+
|
25
|
+
* Multiple Redirect URIs are now allowed for client applications out-of-the-box. In order to use it in API mode, you can pass the `redirect_uri` with an array of strings (the URLs) as values; in the new client application form, you can add several input fields with name field as `redirect_uri[]`. **ATTENTION!!** When using multiple redirect URIs, passing the desired redirect URI to the authorize form becomes mandatory.
|
26
|
+
* store scopes with whitespace instead of comma; set separator as `oauth_scope_separator` option, to keep backwards-compatibility;
|
27
|
+
* client application can now store multiple redirect uris; the POST API parameters can accept the redirect_uri param value both as a string or an array of string; internally, they'll be stored in a whitespace-separated string;
|
28
|
+
|
29
|
+
#### Bugfixes
|
30
|
+
|
31
|
+
* Fixed `RETURNING` support in the databases supporting it (such as postgres).
|
32
|
+
|
33
|
+
#### Chore
|
34
|
+
|
35
|
+
* option `scopes_param` renamed to `scope_param`;
|
36
|
+
*
|
@@ -0,0 +1,21 @@
|
|
1
|
+
### 0.0.6 (6/7/2020)
|
2
|
+
|
3
|
+
#### Features
|
4
|
+
|
5
|
+
The `oauth_jwt` feature now supports JWT Secured Authorization Request (JAR) (see https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-20). This means that client applications can send the authorization parameters inside a signed JWT. The client applications keeps the private key, while the authorization server **must** store a public key for the client application. For encrypted JWTs, the client application should use one of the public encryption keys exposed in the JWKs URI, to encrypt the JWT. Remember, **tokens must be signed then encrypted** (or just signed).
|
6
|
+
|
7
|
+
###### Options:
|
8
|
+
|
9
|
+
* `:oauth_application_jws_jwk_column`: db column where the public key is stored; since it's stored in the JWS format, it can be stored either as a String (JSON-encoded), or as an hstore (if you're using postgresql);
|
10
|
+
* `:oauth_jwt_jwe_key`: key used to decrypt the request JWT;
|
11
|
+
* `:oauth_jwt_jwe_public_key`: key used to encrypt the request JWT, and which will be exposed in the JWKs URI in the JWK format;
|
12
|
+
|
13
|
+
|
14
|
+
#### Improvements
|
15
|
+
|
16
|
+
* Removing all `_param` options; these defined the URL params, however we're using protocol-defined params, so it's unlikely (and undesired) that these'll change.
|
17
|
+
* Hitting the revoke endpoint with a JWT access token returns a 400 error;
|
18
|
+
|
19
|
+
#### Chore
|
20
|
+
|
21
|
+
Removed React Javascript from example applications.
|