rodauth-oauth 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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: