rodauth-oauth 0.0.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![pipeline status](https://gitlab.com/honeyryderchuck/rodauth-oauth/badges/master/pipeline.svg)](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/
|
4
|
-
[![coverage report](https://gitlab.com/honeyryderchuck/rodauth-oauth/badges/master/coverage.svg)](https://gitlab.
|
3
|
+
[![pipeline status](https://gitlab.com/honeyryderchuck/rodauth-oauth/badges/master/pipeline.svg)](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/pipelines?page=1&ref=master)
|
4
|
+
[![coverage report](https://gitlab.com/honeyryderchuck/rodauth-oauth/badges/master/coverage.svg)](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
|
|