rodauth-oauth 0.0.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +204 -3
- data/README.md +124 -27
- data/lib/generators/roda/oauth/templates/db/migrate/create_rodauth_oauth.rb +8 -5
- data/lib/rodauth/features/oauth.rb +597 -371
- data/lib/rodauth/features/oauth_http_mac.rb +0 -3
- data/lib/rodauth/features/oauth_jwt.rb +324 -86
- data/lib/rodauth/features/oauth_saml.rb +104 -0
- data/lib/rodauth/features/oidc.rb +267 -0
- data/lib/rodauth/oauth/database_extensions.rb +73 -0
- data/lib/rodauth/oauth/ttl_store.rb +59 -0
- data/lib/rodauth/oauth/version.rb +1 -1
- metadata +9 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 02d69464053b6809900da774b4c9957d642b003d0a0de7aa076e57a5eb8895bc
|
4
|
+
data.tar.gz: e1dd94b69aa4bdf051b1c28d684a0ec2a1435da9f99ca6bf77da9d537474f9a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cfe325a2e8daa96a72b4577566ae84772dcf114411ebbd9d0115f0f33f5b104f7c138a1b1ef7813d46f0fd2226c6560db5d974e51ec92b33ce7e393726005b2d
|
7
|
+
data.tar.gz: df5866c1cd089c0d361a00d1ffd1db02df9b6551940dbf70e7390657fa8558ae0fb3c8eb2fcff6ec6fb9fd52406a99615574305d5a3bacdbd20f5fc22bacd63f
|
data/CHANGELOG.md
CHANGED
@@ -2,7 +2,204 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
-
|
5
|
+
### 0.2.0
|
6
|
+
|
7
|
+
#### Features
|
8
|
+
|
9
|
+
##### SAML Assertion Grant Type
|
10
|
+
|
11
|
+
`rodauth-auth` now supports using a SAML Assertion to request for an Access token.In order to enable, you have to:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
plugin :rodauth do
|
15
|
+
enable :oauth_saml
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
For more info about integrating it, [check the wiki](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/wikis/SAML-Assertion-Access-Tokens).
|
20
|
+
|
21
|
+
##### Supporting rotating keys
|
22
|
+
|
23
|
+
At some point, you'll want to replace the pkeys and algorithm used to generate and verify the JWT access tokens, but you want to keep validating previously-distributed JWT tokens, at least until they expire. Now you can, via two new options, `oauth_jwt_legacy_public_key` and `oauth_jwt_legacy_algorithm`, which will be declared in the JWKs URI and used to verify access tokens.
|
24
|
+
|
25
|
+
|
26
|
+
##### Reuse access tokens
|
27
|
+
|
28
|
+
If the `oauth_reuse_access_token` is set, if there's already an existing valid access token, any new grant for the same application / account / scope will keep the same access token. This can be helpful in scenarios where one wants the same access token distributed across devices.
|
29
|
+
|
30
|
+
##### require_authorizable_account
|
31
|
+
|
32
|
+
The method used to verify access to the authorize flow is called `require_authorizable_account`. By default, it checks if a user is logged in by using rodauth's own `require_account`. This is the method you'd want to redefine in order to augment these requirements, i.e. request 2fa authentication.
|
33
|
+
|
34
|
+
#### Improvements
|
35
|
+
|
36
|
+
Expired and revoked access tokens end up generating a lot of garbage, which will have to be periodically cleaned up. You can mitigate this now by setting a uniqueness index for a group of columns, i.e. if you set a uniqueness index for the `oauth_application_id/account_id/scopes` column, `rodauth-oauth` will transparently reuse the same db entry to store the new access token. If setting some other type of uniqueness index, make sure to update the option `oauth_tokens_unique_columns` (the array of columns from the uniqueness index).
|
37
|
+
|
38
|
+
#### Bugfixes
|
39
|
+
|
40
|
+
Calling `before_*_route` callbacks appropriately.
|
41
|
+
|
42
|
+
Fixed some mishandling of HTTP headers when in in resource-server mode.
|
43
|
+
|
44
|
+
#### Chore
|
45
|
+
|
46
|
+
* 97.7% test coverage;
|
47
|
+
* `rodauth-oauth` CI tests run against sqlite, postgresql and mysql.
|
48
|
+
|
49
|
+
### 0.1.0
|
50
|
+
|
51
|
+
(31/7/2020)
|
52
|
+
|
53
|
+
#### Features
|
54
|
+
|
55
|
+
##### OpenID
|
56
|
+
|
57
|
+
`rodauth-oauth` now ships with support for [OpenID Connect](https://openid.net/connect/). In order to enable, you have to:
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
plugin :rodauth do
|
61
|
+
enable :oidc
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
For more info about integrating it, [check the wiki](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/wikis/home#openid-connect-since-v01).
|
66
|
+
|
67
|
+
It supports omniauth openID integrations out-of-the-box, [check the OpenID example, which integrates with omniauth_openid_connect](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/tree/master/examples).
|
68
|
+
|
69
|
+
#### Improvements
|
70
|
+
|
71
|
+
* JWT: `sub` claim now also handles "pairwise" subjects. For that, you have to set the `oauth_jwt_subject_type` option (`"public"` or `"pairwise"`) and `oauth_jwt_subject_secret` (will be used for salting the `sub` when the type is `"pairwise"`).
|
72
|
+
* JWT: `auth_time` claim is now supported; if your application uses the `rodauth` feature `:account_expiration`, it'll use the `last_account_login_at` method, otherwise you can set the `last_account_login_at` option:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
last_account_login_at do
|
76
|
+
convert_timestamp(db[accounts_table].where(account_id_column => account_id).get(:that_column_where_you_keep_the_data))
|
77
|
+
end
|
78
|
+
```
|
79
|
+
* JWT: `iss` claim now defaults to `authorization_server_url` when not defined;
|
80
|
+
* JWT: `aud` claim now defaults to the token application's client ID (`client_id` claim was removed as a result);
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
#### Breaking Changes
|
85
|
+
|
86
|
+
`rodauth-oauth` URLs no longer have the `oauth-` prefix, so make sure you update your integrations accordingly, i.e. where you used to rely on `/oauth-authorize`, you'll have to use `/authorize`.
|
87
|
+
|
88
|
+
URI schemes for client applications redirect URIs have to be `https`. In order to override this, set the `oauth_valid_uri_schemes` to an array of your expected URI schemes.
|
89
|
+
|
90
|
+
|
91
|
+
#### Bugfixes
|
92
|
+
|
93
|
+
* Authorization request submission can receive the `scope` as an array of values now, instead of only dealing with receiving a white-space separated list.
|
94
|
+
* fixed trailing "/" in the "issuer" value in server metadata (`https://server.com/` -> `https://server.com`).
|
95
|
+
|
96
|
+
|
97
|
+
### 0.0.6
|
98
|
+
|
99
|
+
(6/7/2020)
|
100
|
+
|
101
|
+
#### Features
|
102
|
+
|
103
|
+
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).
|
104
|
+
|
105
|
+
###### Options:
|
106
|
+
|
107
|
+
* `: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);
|
108
|
+
* `:oauth_jwt_jwe_key`: key used to decrypt the request JWT;
|
109
|
+
* `: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;
|
110
|
+
|
111
|
+
|
112
|
+
#### Improvements
|
113
|
+
|
114
|
+
* 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.
|
115
|
+
* Hitting the revoke endpoint with a JWT access token returns a 400 error;
|
116
|
+
|
117
|
+
#### Chore
|
118
|
+
|
119
|
+
Removed React Javascript from example applications.
|
120
|
+
|
121
|
+
|
122
|
+
### 0.0.5
|
123
|
+
|
124
|
+
(26/6/2020)
|
125
|
+
|
126
|
+
#### Features
|
127
|
+
|
128
|
+
* new option: `oauth_scope_separator` (default: `" "`), to define how scopes are stored;
|
129
|
+
|
130
|
+
##### Resource Server mode
|
131
|
+
|
132
|
+
`rodauth-oauth` can now be used in a resource server, i.e. only for authorizing access to resources:
|
133
|
+
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
plugin :rodauth do
|
137
|
+
enable :oauth
|
138
|
+
|
139
|
+
is_authorization_server? false
|
140
|
+
authorization_server_url "https://auth-server"
|
141
|
+
end
|
142
|
+
```
|
143
|
+
|
144
|
+
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).
|
145
|
+
|
146
|
+
#### Improvements
|
147
|
+
|
148
|
+
* 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.
|
149
|
+
* store scopes with whitespace instead of comma; set separator as `oauth_scope_separator` option, to keep backwards-compatibility;
|
150
|
+
* 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;
|
151
|
+
|
152
|
+
#### Bugfixes
|
153
|
+
|
154
|
+
* Fixed `RETURNING` support in the databases supporting it (such as postgres).
|
155
|
+
|
156
|
+
#### Chore
|
157
|
+
|
158
|
+
* option `scopes_param` renamed to `scope_param`;
|
159
|
+
*
|
160
|
+
|
161
|
+
## 0.0.4
|
162
|
+
|
163
|
+
(13/6/2020)
|
164
|
+
|
165
|
+
### Features
|
166
|
+
|
167
|
+
#### Token introspection
|
168
|
+
|
169
|
+
`rodauth-oauth` now ships with an introspection endpoint (`/oauth-introspect`).
|
170
|
+
|
171
|
+
#### Authorization Server Metadata
|
172
|
+
|
173
|
+
`rodauth-oauth` now allows to define an authorization metadata endpoint, which has to be defined at the route of the router:
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
route do |r|
|
177
|
+
r.rodauth
|
178
|
+
rodauth.oauth_server_metadata
|
179
|
+
...
|
180
|
+
```
|
181
|
+
|
182
|
+
#### JWKs URI
|
183
|
+
|
184
|
+
the `oauth_jwt` feature now ships with an endpoint, `/oauth-jwks`, where client applications can retrieve the JWK set to verify generated tokens.
|
185
|
+
|
186
|
+
#### JWT access tokens as authorization grants
|
187
|
+
|
188
|
+
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);
|
189
|
+
|
190
|
+
### Improvements
|
191
|
+
|
192
|
+
* using `client_secret_basic` authorization where client id/secret params were allowed (i.e. in the token and revoke endpoints, for example);
|
193
|
+
* improved JWK usage for both supported jwt libraries;
|
194
|
+
* 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...)
|
195
|
+
|
196
|
+
### Bugfixes
|
197
|
+
|
198
|
+
* Fixed scope claim of JWT ("scopes" -> "scope");
|
199
|
+
|
200
|
+
## 0.0.3
|
201
|
+
|
202
|
+
(5/6/2020)
|
6
203
|
|
7
204
|
### Features
|
8
205
|
|
@@ -34,7 +231,9 @@ end
|
|
34
231
|
* renamed the existing `use_oauth_implicit_grant_type` to `use_oauth_implicit_grant_type?`;
|
35
232
|
* It's now usable as JSON API (small caveat: POST authorize will still redirect on success...);
|
36
233
|
|
37
|
-
## 0.0.2
|
234
|
+
## 0.0.2
|
235
|
+
|
236
|
+
(29/5/2020)
|
38
237
|
|
39
238
|
### Features
|
40
239
|
|
@@ -50,6 +249,8 @@ end
|
|
50
249
|
|
51
250
|
* usage of client secret for authorizing the generation of tokens, as the spec mandates (and refraining from them when doing PKCE).
|
52
251
|
|
53
|
-
## 0.0.1
|
252
|
+
## 0.0.1
|
253
|
+
|
254
|
+
(14/5/2020)
|
54
255
|
|
55
256
|
Initial implementation of the Oauth 2.0 framework, with an example app done using roda.
|
data/README.md
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# Rodauth::Oauth
|
2
2
|
|
3
|
-
[](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/
|
4
|
-
[](https://gitlab.
|
3
|
+
[](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/pipelines?page=1&ref=master)
|
4
|
+
[](https://honeyryderchuck.gitlab.io/rodauth-oauth/coverage/#_AllFiles)
|
5
5
|
|
6
6
|
This is an extension to the `rodauth` gem which implements the [OAuth 2.0 framework](https://tools.ietf.org/html/rfc6749) for an authorization server.
|
7
7
|
|
8
8
|
## Features
|
9
9
|
|
10
|
-
This gem implements:
|
10
|
+
This gem implements the following RFCs and features of OAuth:
|
11
11
|
|
12
12
|
* [The OAuth 2.0 protocol framework](https://tools.ietf.org/html/rfc6749):
|
13
13
|
* [Authorization grant flow](https://tools.ietf.org/html/rfc6749#section-1.3);
|
@@ -15,12 +15,18 @@ This gem implements:
|
|
15
15
|
* [Access Token refresh](https://tools.ietf.org/html/rfc6749#section-1.5);
|
16
16
|
* [Implicit grant (off by default)[https://tools.ietf.org/html/rfc6749#section-4.2];
|
17
17
|
* [Token revocation](https://tools.ietf.org/html/rfc7009);
|
18
|
+
* [Token introspection](https://tools.ietf.org/html/rfc7662);
|
19
|
+
* [Authorization Server Metadata](https://tools.ietf.org/html/rfc8414);
|
18
20
|
* [PKCE](https://tools.ietf.org/html/rfc7636);
|
19
21
|
* Access Type (Token refresh online and offline);
|
20
22
|
* [MAC Authentication Scheme](https://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-02);
|
21
23
|
* [JWT Acess Tokens](https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-07);
|
24
|
+
* [SAML 2.0 Assertion Access Tokens](https://tools.ietf.org/html/draft-ietf-oauth-saml2-bearer-03);
|
25
|
+
* [JWT Secured Authorization Requests](https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-20);
|
22
26
|
* OAuth application and token management dashboards;
|
23
27
|
|
28
|
+
It also implements the [OpenID Connect layer](https://openid.net/connect/) on top of the OAuth features it provides.
|
29
|
+
|
24
30
|
This gem supports also rails (through [rodauth-rails]((https://github.com/janko/rodauth-rails))).
|
25
31
|
|
26
32
|
|
@@ -40,6 +46,15 @@ Or install it yourself as:
|
|
40
46
|
|
41
47
|
$ gem install rodauth-oauth
|
42
48
|
|
49
|
+
|
50
|
+
## Resources
|
51
|
+
| | |
|
52
|
+
| ------------- | ----------------------------------------------------------- |
|
53
|
+
| Website | https://honeyryderchuck.gitlab.io/rodauth-oauth/ |
|
54
|
+
| Documentation | https://honeyryderchuck.gitlab.io/rodauth-oauth/rdoc/ |
|
55
|
+
| Wiki | https://gitlab.com/honeyryderchuck/rodauth-oauth/wikis/home |
|
56
|
+
| CI | https://gitlab.com/honeyryderchuck/rodauth-oauth/pipelines |
|
57
|
+
|
43
58
|
## Usage
|
44
59
|
|
45
60
|
This tutorial assumes you already read the documentation and know how to set up `rodauth`. After that, integrating `roda-auth` will look like:
|
@@ -83,11 +98,22 @@ route do |r|
|
|
83
98
|
end
|
84
99
|
```
|
85
100
|
|
86
|
-
|
101
|
+
|
102
|
+
For OpenID, it's very similar to the example above:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
plugin :rodauth do
|
106
|
+
# enable it in the plugin
|
107
|
+
enable :login, :openid
|
108
|
+
oauth_application_default_scope %w[openid]
|
109
|
+
oauth_application_scopes %w[openid email profile]
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
87
113
|
|
88
114
|
### Example (TL;DR)
|
89
115
|
|
90
|
-
If you're familiar with the technology and want to skip the next paragraphs, just [check our
|
116
|
+
If you're familiar with the technology and want to skip the next paragraphs, just [check our example applications](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/tree/master/examples/).
|
91
117
|
|
92
118
|
|
93
119
|
Generating tokens happens mostly server-to-server, so here's an example using:
|
@@ -98,7 +124,7 @@ Generating tokens happens mostly server-to-server, so here's an example using:
|
|
98
124
|
|
99
125
|
```ruby
|
100
126
|
require "httpx"
|
101
|
-
response = HTTPX.post("https://auth_server/
|
127
|
+
response = HTTPX.post("https://auth_server/token",json: {
|
102
128
|
client_id: ENV["OAUTH_CLIENT_ID"],
|
103
129
|
client_secret: ENV["OAUTH_CLIENT_SECRET"],
|
104
130
|
grant_type: "authorization_code",
|
@@ -112,7 +138,7 @@ puts payload #=> {"access_token" => "awr23f3h8f9d2h89...", "refresh_token" => "2
|
|
112
138
|
##### cURL
|
113
139
|
|
114
140
|
```
|
115
|
-
> curl --data '{"client_id":"$OAUTH_CLIENT_ID","client_secret":"$OAUTH_CLIENT_SECRET","grant_type":"authorization_code","code":"oiweicnewdh32fhoi3hf3ihfo2ih3f2o3as"}' https://auth_server/
|
141
|
+
> curl --data '{"client_id":"$OAUTH_CLIENT_ID","client_secret":"$OAUTH_CLIENT_SECRET","grant_type":"authorization_code","code":"oiweicnewdh32fhoi3hf3ihfo2ih3f2o3as"}' https://auth_server/token
|
116
142
|
```
|
117
143
|
|
118
144
|
#### Refresh Token
|
@@ -123,7 +149,7 @@ Refreshing expired tokens also happens mostly server-to-server, here's an exampl
|
|
123
149
|
|
124
150
|
```ruby
|
125
151
|
require "httpx"
|
126
|
-
response = HTTPX.post("https://auth_server/
|
152
|
+
response = HTTPX.post("https://auth_server/token",json: {
|
127
153
|
client_id: ENV["OAUTH_CLIENT_ID"],
|
128
154
|
client_secret: ENV["OAUTH_CLIENT_SECRET"],
|
129
155
|
grant_type: "refresh_token",
|
@@ -137,7 +163,7 @@ puts payload #=> {"access_token" => "awr23f3h8f9d2h89...", "token_type" => "Bear
|
|
137
163
|
##### cURL
|
138
164
|
|
139
165
|
```
|
140
|
-
> 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/
|
166
|
+
> 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
|
141
167
|
```
|
142
168
|
|
143
169
|
#### Revoking tokens
|
@@ -146,10 +172,9 @@ Token revocation can be done both by the idenntity owner or the application owne
|
|
146
172
|
|
147
173
|
```ruby
|
148
174
|
require "httpx"
|
149
|
-
httpx = HTTPX.plugin(:
|
150
|
-
response = httpx.
|
151
|
-
.post("https://auth_server/
|
152
|
-
client_id: ENV["OAUTH_CLIENT_ID"],
|
175
|
+
httpx = HTTPX.plugin(:basic_authorization)
|
176
|
+
response = httpx.basic_authentication(ENV["CLIENT_ID"], ENV["CLIENT_SECRET"])
|
177
|
+
.post("https://auth_server/revoke",json: {
|
153
178
|
token_type_hint: "access_token", # can also be "refresh:tokn"
|
154
179
|
token: "2r89hfef4j9f90d2j2390jf390g"
|
155
180
|
})
|
@@ -161,7 +186,56 @@ puts payload #=> {"access_token" => "awr23f3h8f9d2h89...", "token_type" => "Bear
|
|
161
186
|
##### cURL
|
162
187
|
|
163
188
|
```
|
164
|
-
> curl -H "X-your-auth-scheme: $SERVER_KEY" --data '{"client_id":"$OAUTH_CLIENT_ID","token_type_hint":"access_token","token":"2r89hfef4j9f90d2j2390jf390g"}' https://auth_server/
|
189
|
+
> 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
|
190
|
+
```
|
191
|
+
|
192
|
+
#### Token introspection
|
193
|
+
|
194
|
+
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:
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
require "httpx"
|
198
|
+
httpx = HTTPX.plugin(:basic_authorization)
|
199
|
+
response = httpx.basic_authentication(ENV["CLIENT_ID"], ENV["CLIENT_SECRET"])
|
200
|
+
.post("https://auth_server/introspect",json: {
|
201
|
+
token_type_hint: "access_token", # can also be "refresh:tokn"
|
202
|
+
token: "2r89hfef4j9f90d2j2390jf390g"
|
203
|
+
})
|
204
|
+
response.raise_for_status
|
205
|
+
payload = JSON.parse(response.to_s)
|
206
|
+
puts payload #=> {"active" => true, "scope" => "read write" ....
|
207
|
+
```
|
208
|
+
|
209
|
+
##### cURL
|
210
|
+
|
211
|
+
```
|
212
|
+
> 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
|
213
|
+
```
|
214
|
+
|
215
|
+
### Authorization Server Metadata
|
216
|
+
|
217
|
+
The Authorization Server Metadata endpoint can be used by clients to obtain the information needed to interact with an
|
218
|
+
OAuth 2.0 authorization server, i.e. know which endpoint is used to authorize clients.
|
219
|
+
|
220
|
+
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:
|
221
|
+
|
222
|
+
```ruby
|
223
|
+
plugin :rodauth do
|
224
|
+
# enable it in the plugin
|
225
|
+
enable :login, :oauth
|
226
|
+
oauth_application_default_scope %w[profile.read]
|
227
|
+
oauth_application_scopes %w[profile.read profile.write]
|
228
|
+
end
|
229
|
+
|
230
|
+
# then, inside roda
|
231
|
+
|
232
|
+
route do |r|
|
233
|
+
r.rodauth
|
234
|
+
# server metadata endpoint
|
235
|
+
rodauth.oauth_server_metadata
|
236
|
+
|
237
|
+
# now, your oauth and app code...
|
238
|
+
|
165
239
|
```
|
166
240
|
|
167
241
|
### Database migrations
|
@@ -192,10 +266,10 @@ The rodauth default setup expects the roda `render` plugin to be activated; by d
|
|
192
266
|
|
193
267
|
Once you set it up, by default, the following endpoints will be available:
|
194
268
|
|
195
|
-
* `GET /
|
196
|
-
* `POST /
|
197
|
-
* `POST /
|
198
|
-
* `POST /
|
269
|
+
* `GET /authorize`: Loads the OAuth authorization HTML form;
|
270
|
+
* `POST /authorize`: Responds to an OAuth authorization request, as [per the spec](https://tools.ietf.org/html/rfc6749#section-4);
|
271
|
+
* `POST /token`: Generates OAuth tokens as [per the spec](https://tools.ietf.org/html/rfc6749#section-4.4.2);
|
272
|
+
* `POST /revoke`: Revokes OAuth tokens as [per the spec](https://tools.ietf.org/html/rfc7009);
|
199
273
|
|
200
274
|
### OAuth applications
|
201
275
|
|
@@ -375,7 +449,7 @@ The "Proof Key for Code Exchange by OAuth Public Clients" (aka PKCE) flow, which
|
|
375
449
|
```ruby
|
376
450
|
# with httpx
|
377
451
|
require "httpx"
|
378
|
-
response = HTTPX.post("https://auth_server/
|
452
|
+
response = HTTPX.post("https://auth_server/token",json: {
|
379
453
|
client_id: ENV["OAUTH_CLIENT_ID"],
|
380
454
|
grant_type: "authorization_code",
|
381
455
|
code: "oiweicnewdh32fhoi3hf3ihfo2ih3f2o3as",
|
@@ -426,14 +500,14 @@ Generating an access token will deliver the following fields:
|
|
426
500
|
```ruby
|
427
501
|
# with httpx
|
428
502
|
require "httpx"
|
429
|
-
response = httpx.post("https://auth_server/
|
430
|
-
client_id:
|
431
|
-
client_secret:
|
503
|
+
response = httpx.post("https://auth_server/token",json: {
|
504
|
+
client_id: env["oauth_client_id"],
|
505
|
+
client_secret: env["oauth_client_secret"],
|
432
506
|
grant_type: "authorization_code",
|
433
507
|
code: "oiweicnewdh32fhoi3hf3ihfo2ih3f2o3as"
|
434
508
|
})
|
435
509
|
response.raise_for_status
|
436
|
-
payload =
|
510
|
+
payload = json.parse(response.to_s)
|
437
511
|
puts payload #=> {
|
438
512
|
# "access_token" => ....
|
439
513
|
# "mac_key" => ....
|
@@ -469,7 +543,6 @@ This will, by default, use the OAuth application as HMAC signature and "HS256" a
|
|
469
543
|
```ruby
|
470
544
|
enable :oauth_jwt
|
471
545
|
oauth_jwt_secret "SECRET"
|
472
|
-
# or oauth_jwt_secret_path "path/to/file"
|
473
546
|
oauth_jwt_algorithm "HS512"
|
474
547
|
```
|
475
548
|
|
@@ -486,7 +559,7 @@ rsa_public = rsa_private.public_key
|
|
486
559
|
plugin :rodauth do
|
487
560
|
enable :oauth_jwt
|
488
561
|
oauth_jwt_key rsa_private
|
489
|
-
|
562
|
+
oauth_jwt_public_key rsa_public
|
490
563
|
oauth_jwt_algorithm "RS256"
|
491
564
|
end
|
492
565
|
```
|
@@ -496,10 +569,14 @@ end
|
|
496
569
|
One can further encode the JWT token using JSON Web Keys. Here's how you could enable the feature:
|
497
570
|
|
498
571
|
```ruby
|
572
|
+
rsa_private = OpenSSL::PKey::RSA.generate 2048
|
573
|
+
rsa_public = rsa_private.public_key
|
574
|
+
|
499
575
|
plugin :rodauth do
|
500
576
|
enable :oauth_jwt
|
501
|
-
oauth_jwt_jwk_key
|
502
|
-
|
577
|
+
oauth_jwt_jwk_key rsa_private
|
578
|
+
oauth_jwt_jwk_public_key rsa_public
|
579
|
+
oauth_jwt_jwk_algorithm "RS256"
|
503
580
|
end
|
504
581
|
```
|
505
582
|
|
@@ -520,6 +597,26 @@ end
|
|
520
597
|
|
521
598
|
which adds an extra layer of protection.
|
522
599
|
|
600
|
+
#### JWKS URI
|
601
|
+
|
602
|
+
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`.
|
603
|
+
|
604
|
+
#### JWT Bearer as authorization grant
|
605
|
+
|
606
|
+
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:
|
607
|
+
|
608
|
+
```ruby
|
609
|
+
# with httpx
|
610
|
+
require "httpx"
|
611
|
+
response = httpx.post("https://auth_server/token",json: {
|
612
|
+
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
613
|
+
assertion: "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImlzcyI6IkV4YW1wbGUiLCJpYXQiOjE1OTIwMDk1MDEsImNsaWVudF9pZCI6IkNMSUVOVF9JRCIsImV4cCI6MTU5MjAxMzEwMSwiYXVkIjpudWxsLCJzY29wZSI6InVzZXIucmVhZCB1c2VyLndyaXRlIiwianRpIjoiOGM1NTVjMjdiOWRjNDdmOTcyNWRkYzBhMjk0NzA1ZTA4NzFkY2JlN2Q5ZTNlMmVkNGE1ZTBiOGZlNTZlYzcxMSJ9.AlxKRtE3ec0mtyBSDx4VseND4eC6cH5ubtv8gfYxxsc"
|
614
|
+
})
|
615
|
+
response.raise_for_status
|
616
|
+
payload = json.parse(response.to_s)
|
617
|
+
puts payload #=> {
|
618
|
+
# "access_token" => "ey....
|
619
|
+
```
|
523
620
|
|
524
621
|
#### DB Schema
|
525
622
|
|