rodauth-oauth 0.0.5 → 0.0.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a809caa12783f20af9ba7b6d4b8710f2b783d11c5c74c0be6cbb129841ebce19
4
- data.tar.gz: 928fd524dd2f9ec502deccc96b17eb232119543b2150363a398d04a5008b4d22
3
+ metadata.gz: 73f73e4143c1c646b3a6f2a5e87fb925f38f18e157366d7522328b5cbcd2fbb7
4
+ data.tar.gz: bce8a4532e365328bb46197e72b05b78beca19b00587630f234cff0c8d51bc35
5
5
  SHA512:
6
- metadata.gz: 15b98394108c73ba4888bd92835d264750c46194b77322b0b159b45134031f0ac9c264a2a8d0a9991fb0d36b05b85b0d127d94468d77d3f7fb45777ec5bb6002
7
- data.tar.gz: 282f53b5fd4eff08fdce56a42a7c42e3ef00cf888ced3acb2a692fd416b8dbb7250c982a53fc3c4a61a83884d8eb1068981580390e3bea2ba35d741165aa2c6a
6
+ metadata.gz: 46053f71e35baad7b3c217bfbca0a259d07fd6909713805db23ff92c0503c0907903483e659fe988af774b26a87b274f8b72006983b1acc191bccb7d00919a86
7
+ data.tar.gz: 35305a71ea2b4035933d93e3fa5a3e9b388f32b2301ff05b87a86af45defa494cefc4d6d9adb823822c82acb995e57794fb710b1935aaab10430c1898d1a55b0
@@ -2,6 +2,29 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ### 0.0.6
6
+
7
+ #### Features
8
+
9
+ 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).
10
+
11
+ ###### Options:
12
+
13
+ * `: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);
14
+ * `:oauth_jwt_jwe_key`: key used to decrypt the request JWT;
15
+ * `: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;
16
+
17
+
18
+ #### Improvements
19
+
20
+ * 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.
21
+ * Hitting the revoke endpoint with a JWT access token returns a 400 error;
22
+
23
+ #### Chore
24
+
25
+ Removed React Javascript from example applications.
26
+
27
+
5
28
  ### 0.0.5 (26/6/2020)
6
29
 
7
30
  #### Features
data/README.md CHANGED
@@ -21,6 +21,7 @@ This gem implements:
21
21
  * Access Type (Token refresh online and offline);
22
22
  * [MAC Authentication Scheme](https://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-02);
23
23
  * [JWT Acess Tokens](https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-07);
24
+ * [JWT Secured Authorization Requests](https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-20);
24
25
  * OAuth application and token management dashboards;
25
26
 
26
27
  This gem supports also rails (through [rodauth-rails]((https://github.com/janko/rodauth-rails))).
@@ -89,7 +90,7 @@ You'll have to do a bit more boilerplate, so here's the instructions.
89
90
 
90
91
  ### Example (TL;DR)
91
92
 
92
- If you're familiar with the technology and want to skip the next paragraphs, just [check our roda example](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/tree/master/examples/roda).
93
+ 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/).
93
94
 
94
95
 
95
96
  Generating tokens happens mostly server-to-server, so here's an example using:
@@ -189,20 +189,6 @@ module Rodauth
189
189
 
190
190
  auth_value_methods(:only_json?)
191
191
 
192
- redirect(:oauth_application) do |id|
193
- "/#{oauth_applications_path}/#{id}"
194
- end
195
-
196
- redirect(:require_authorization) do
197
- if logged_in?
198
- oauth_authorize_path
199
- elsif respond_to?(:login_redirect)
200
- login_redirect
201
- else
202
- default_redirect
203
- end
204
- end
205
-
206
192
  auth_value_method :json_request_regexp, %r{\bapplication/(?:vnd\.api\+)?json\b}i
207
193
 
208
194
  SERVER_METADATA = OAuth::TtlStore.new
@@ -246,18 +232,10 @@ module Rodauth
246
232
  @scope = scope
247
233
  end
248
234
 
249
- def state
250
- param_or_nil("state")
251
- end
252
-
253
235
  def scopes
254
236
  (param_or_nil("scope") || oauth_application_default_scope).split(" ")
255
237
  end
256
238
 
257
- def client_id
258
- param_or_nil("client_id")
259
- end
260
-
261
239
  def redirect_uri
262
240
  param_or_nil("redirect_uri") || begin
263
241
  return unless oauth_application
@@ -267,14 +245,6 @@ module Rodauth
267
245
  end
268
246
  end
269
247
 
270
- def token_type_hint
271
- param_or_nil("token_type_hint") || "access_token"
272
- end
273
-
274
- def token
275
- param_or_nil("token")
276
- end
277
-
278
248
  def oauth_application
279
249
  return @oauth_application if defined?(@oauth_application)
280
250
 
@@ -348,6 +318,8 @@ module Rodauth
348
318
 
349
319
  request.on(oauth_applications_id_pattern) do |id|
350
320
  oauth_application = db[oauth_applications_table].where(oauth_applications_id_column => id).first
321
+ next unless oauth_application
322
+
351
323
  scope.instance_variable_set(:@oauth_application, oauth_application)
352
324
 
353
325
  request.is do
@@ -359,7 +331,9 @@ module Rodauth
359
331
  request.on(oauth_tokens_path) do
360
332
  oauth_tokens = db[oauth_tokens_table].where(oauth_tokens_oauth_application_id_column => id)
361
333
  scope.instance_variable_set(:@oauth_tokens, oauth_tokens)
362
- oauth_tokens_view
334
+ request.get do
335
+ oauth_tokens_view
336
+ end
363
337
  end
364
338
  end
365
339
 
@@ -377,7 +351,7 @@ module Rodauth
377
351
  id = create_oauth_application
378
352
  after_create_oauth_application
379
353
  set_notice_flash create_oauth_application_notice_flash
380
- redirect oauth_application_redirect(id)
354
+ redirect "#{request.path}/#{id}"
381
355
  end
382
356
  end
383
357
  set_error_flash create_oauth_application_error_flash
@@ -877,8 +851,8 @@ module Rodauth
877
851
 
878
852
  def validate_oauth_introspect_params
879
853
  # check if valid token hint type
880
- if token_type_hint
881
- redirect_response_error("unsupported_token_type") unless TOKEN_HINT_TYPES.include?(token_type_hint)
854
+ if param_or_nil("token_type_hint")
855
+ redirect_response_error("unsupported_token_type") unless TOKEN_HINT_TYPES.include?(param("token_type_hint"))
882
856
  end
883
857
 
884
858
  redirect_response_error("invalid_request") unless param_or_nil("token")
@@ -906,17 +880,20 @@ module Rodauth
906
880
 
907
881
  def validate_oauth_revoke_params
908
882
  # check if valid token hint type
909
- redirect_response_error("unsupported_token_type") unless TOKEN_HINT_TYPES.include?(token_type_hint)
883
+ if param_or_nil("token_type_hint")
884
+ redirect_response_error("unsupported_token_type") unless TOKEN_HINT_TYPES.include?(param("token_type_hint"))
885
+ end
910
886
 
911
887
  redirect_response_error("invalid_request") unless param_or_nil("token")
912
888
  end
913
889
 
914
890
  def revoke_oauth_token
915
- oauth_token = case token_type_hint
916
- when "access_token"
917
- oauth_token_by_token(token)
918
- when "refresh_token"
891
+ token = param("token")
892
+
893
+ oauth_token = if param("token_type_hint") == "refresh_token"
919
894
  oauth_token_by_refresh_token(token)
895
+ else
896
+ oauth_token_by_token(token)
920
897
  end
921
898
 
922
899
  redirect_response_error("invalid_request") unless oauth_token
@@ -1018,7 +995,7 @@ module Rodauth
1018
995
  throw_json_response_error(authorization_required_error_status, "invalid_client")
1019
996
  else
1020
997
  set_redirect_error_flash(require_authorization_error_flash)
1021
- redirect(require_authorization_redirect)
998
+ redirect(oauth_authorize_path)
1022
999
  end
1023
1000
  end
1024
1001
 
@@ -1186,7 +1163,6 @@ module Rodauth
1186
1163
  route(:oauth_revoke) do |r|
1187
1164
  before_revoke
1188
1165
 
1189
- # access-token
1190
1166
  r.post do
1191
1167
  catch_error do
1192
1168
  validate_oauth_revoke_params
@@ -1208,7 +1184,7 @@ module Rodauth
1208
1184
  end
1209
1185
  end
1210
1186
 
1211
- throw_json_response_error(invalid_oauth_response_status, "invalid_request")
1187
+ redirect_response_error("invalid_request", request.referer || "/")
1212
1188
  end
1213
1189
  end
1214
1190
 
@@ -1253,7 +1229,7 @@ module Rodauth
1253
1229
  end
1254
1230
 
1255
1231
  redirect_url = URI.parse(redirect_uri)
1256
- query_params << "state=#{state}" if state
1232
+ query_params << "state=#{param('state')}" if param_or_nil("state")
1257
1233
  query_params << redirect_url.query if redirect_url.query
1258
1234
  redirect_url.query = query_params.join("&") unless query_params.empty?
1259
1235
  redirect_url.fragment = fragment_params.join("&") unless fragment_params.empty?
@@ -8,6 +8,8 @@ module Rodauth
8
8
 
9
9
  auth_value_method :oauth_jwt_token_issuer, "Example"
10
10
 
11
+ auth_value_method :oauth_application_jws_jwk_column, nil
12
+
11
13
  auth_value_method :oauth_jwt_key, nil
12
14
  auth_value_method :oauth_jwt_public_key, nil
13
15
  auth_value_method :oauth_jwt_algorithm, "HS256"
@@ -20,6 +22,9 @@ module Rodauth
20
22
  auth_value_method :oauth_jwt_jwe_copyright, nil
21
23
  auth_value_method :oauth_jwt_audience, nil
22
24
 
25
+ auth_value_method :request_uri_not_supported_message, "request uri is unsupported"
26
+ auth_value_method :invalid_request_object_message, "request object is invalid"
27
+
23
28
  auth_value_methods(
24
29
  :jwt_encode,
25
30
  :jwt_decode,
@@ -60,6 +65,48 @@ module Rodauth
60
65
  end
61
66
  end
62
67
 
68
+ # /authorize
69
+
70
+ def validate_oauth_grant_params
71
+ # TODO: add support for requst_uri
72
+ redirect_response_error("request_uri_not_supported") if param_or_nil("request_uri")
73
+
74
+ request_object = param_or_nil("request")
75
+
76
+ return super unless request_object && oauth_application
77
+
78
+ jws_jwk = if oauth_application[oauth_application_jws_jwk_column]
79
+ jwk = oauth_application[oauth_application_jws_jwk_column]
80
+
81
+ if jwk
82
+ jwk = JSON.parse(jwk, symbolize_names: true) if jwk.is_a?(String)
83
+ end
84
+ else
85
+ redirect_response_error("invalid_request_object")
86
+ end
87
+
88
+ claims = jwt_decode(request_object, jws_key: jwk_import(jws_jwk), jws_algorithm: jwk[:alg])
89
+
90
+ redirect_response_error("invalid_request_object") unless claims
91
+
92
+ # If signed, the Authorization Request
93
+ # Object SHOULD contain the Claims "iss" (issuer) and "aud" (audience)
94
+ # as members, with their semantics being the same as defined in the JWT
95
+ # [RFC7519] specification. The value of "aud" should be the value of
96
+ # the Authorization Server (AS) "issuer" as defined in RFC8414
97
+ # [RFC8414].
98
+ claims.delete(:iss)
99
+ audience = claims.delete(:aud)
100
+
101
+ redirect_response_error("invalid_request_object") if audience && audience != authorization_server_url
102
+
103
+ claims.each do |k, v|
104
+ request.params[k.to_s] = v
105
+ end
106
+
107
+ super
108
+ end
109
+
63
110
  # /token
64
111
 
65
112
  def before_token
@@ -234,6 +281,10 @@ module Rodauth
234
281
  if defined?(JSON::JWT)
235
282
  # :nocov:
236
283
 
284
+ def jwk_import(data)
285
+ JSON::JWK.new(data)
286
+ end
287
+
237
288
  # json-jwt
238
289
  def jwt_encode(payload)
239
290
  jwt = JSON::JWT.new(payload)
@@ -251,15 +302,11 @@ module Rodauth
251
302
  jwt.to_s
252
303
  end
253
304
 
254
- def jwt_decode(token)
255
- return @jwt_token if defined?(@jwt_token)
256
-
305
+ def jwt_decode(token, jws_key: oauth_jwt_public_key || _jwt_key, **)
257
306
  token = JSON::JWT.decode(token, oauth_jwt_jwe_key).plain_text if oauth_jwt_jwe_key
258
307
 
259
- jwk = oauth_jwt_public_key || _jwt_key
260
-
261
- @jwt_token = if jwk
262
- JSON::JWT.decode(token, jwk)
308
+ @jwt_token = if jws_key
309
+ JSON::JWT.decode(token, jws_key)
263
310
  elsif !is_authorization_server? && auth_server_jwks_set
264
311
  JSON::JWT.decode(token, JSON::JWK::Set.new(auth_server_jwks_set))
265
312
  end
@@ -279,6 +326,10 @@ module Rodauth
279
326
 
280
327
  # ruby-jwt
281
328
 
329
+ def jwk_import(data)
330
+ JWT::JWK.import(data).keypair
331
+ end
332
+
282
333
  def jwt_encode(payload)
283
334
  headers = {}
284
335
 
@@ -312,17 +363,13 @@ module Rodauth
312
363
  token
313
364
  end
314
365
 
315
- def jwt_decode(token)
316
- return @jwt_token if defined?(@jwt_token)
317
-
366
+ def jwt_decode(token, jws_key: oauth_jwt_public_key || _jwt_key, jws_algorithm: oauth_jwt_algorithm)
318
367
  # decrypt jwe
319
368
  token = JWE.decrypt(token, oauth_jwt_jwe_key) if oauth_jwt_jwe_key
320
369
 
321
370
  # decode jwt
322
- key = oauth_jwt_public_key || _jwt_key
323
-
324
- @jwt_token = if key
325
- JWT.decode(token, key, true, algorithms: [oauth_jwt_algorithm]).first
371
+ @jwt_token = if jws_key
372
+ JWT.decode(token, jws_key, true, algorithms: [jws_algorithm]).first
326
373
  elsif !is_authorization_server? && auth_server_jwks_set
327
374
  algorithms = auth_server_jwks_set[:keys].select { |k| k[:use] == "sig" }.map { |k| k[:alg] }
328
375
  JWT.decode(token, nil, true, jwks: auth_server_jwks_set, algorithms: algorithms).first
@@ -339,11 +386,15 @@ module Rodauth
339
386
  end
340
387
  else
341
388
  # :nocov:
389
+ def jwk_import(_data)
390
+ raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\""
391
+ end
392
+
342
393
  def jwt_encode(_token)
343
394
  raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\""
344
395
  end
345
396
 
346
- def jwt_decode(_token)
397
+ def jwt_decode(_token, **)
347
398
  raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\""
348
399
  end
349
400
 
@@ -353,6 +404,14 @@ module Rodauth
353
404
  # :nocov:
354
405
  end
355
406
 
407
+ def validate_oauth_revoke_params
408
+ token_hint = param_or_nil("token_type_hint")
409
+
410
+ throw(:rodauth_error) if !token_hint || token_hint == "access_token"
411
+
412
+ super
413
+ end
414
+
356
415
  route(:oauth_jwks) do |r|
357
416
  r.get do
358
417
  json_response_success({ keys: jwks_set })
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rodauth
4
4
  module OAuth
5
- VERSION = "0.0.5"
5
+ VERSION = "0.0.6"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rodauth-oauth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago Cardoso
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-28 00:00:00.000000000 Z
11
+ date: 2020-07-06 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Implementation of the OAuth 2.0 protocol on top of rodauth.
14
14
  email: