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
@@ -47,9 +47,6 @@ module Rodauth
|
|
47
47
|
mac_attributes = parse_mac_authorization_header_props(token)
|
48
48
|
|
49
49
|
oauth_token = oauth_token_by_token(mac_attributes["id"])
|
50
|
-
.where(Sequel[oauth_tokens_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
|
51
|
-
.where(oauth_tokens_revoked_at_column => nil)
|
52
|
-
.first
|
53
50
|
|
54
51
|
return unless oauth_token && mac_signature_matches?(oauth_token, mac_attributes)
|
55
52
|
|
@@ -1,81 +1,207 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
|
+
require "rodauth/oauth/ttl_store"
|
4
|
+
|
3
5
|
module Rodauth
|
4
6
|
Feature.define(:oauth_jwt) do
|
5
7
|
depends :oauth
|
6
8
|
|
7
|
-
auth_value_method :
|
9
|
+
auth_value_method :oauth_jwt_subject_type, "public" # public, pairwise
|
10
|
+
auth_value_method :oauth_jwt_subject_secret, nil # salt for pairwise generation
|
11
|
+
|
12
|
+
auth_value_method :oauth_jwt_token_issuer, nil
|
13
|
+
|
14
|
+
auth_value_method :oauth_application_jws_jwk_column, nil
|
8
15
|
|
9
16
|
auth_value_method :oauth_jwt_key, nil
|
10
17
|
auth_value_method :oauth_jwt_public_key, nil
|
11
18
|
auth_value_method :oauth_jwt_algorithm, "HS256"
|
12
19
|
|
13
|
-
auth_value_method :oauth_jwt_jwk_key, nil
|
14
|
-
auth_value_method :oauth_jwt_jwk_public_key, nil
|
15
|
-
auth_value_method :oauth_jwt_jwk_algorithm, "RS256"
|
16
|
-
|
17
20
|
auth_value_method :oauth_jwt_jwe_key, nil
|
18
21
|
auth_value_method :oauth_jwt_jwe_public_key, nil
|
19
22
|
auth_value_method :oauth_jwt_jwe_algorithm, nil
|
20
23
|
auth_value_method :oauth_jwt_jwe_encryption_method, nil
|
21
24
|
|
25
|
+
# values used for rotating keys
|
26
|
+
auth_value_method :oauth_jwt_legacy_public_key, nil
|
27
|
+
auth_value_method :oauth_jwt_legacy_algorithm, nil
|
28
|
+
|
22
29
|
auth_value_method :oauth_jwt_jwe_copyright, nil
|
23
30
|
auth_value_method :oauth_jwt_audience, nil
|
24
31
|
|
32
|
+
auth_value_method :request_uri_not_supported_message, "request uri is unsupported"
|
33
|
+
auth_value_method :invalid_request_object_message, "request object is invalid"
|
34
|
+
|
25
35
|
auth_value_methods(
|
26
|
-
:generate_jti,
|
27
36
|
:jwt_encode,
|
28
|
-
:jwt_decode
|
37
|
+
:jwt_decode,
|
38
|
+
:jwks_set,
|
39
|
+
:last_account_login_at
|
29
40
|
)
|
30
41
|
|
42
|
+
JWKS = OAuth::TtlStore.new
|
43
|
+
|
31
44
|
def require_oauth_authorization(*scopes)
|
32
45
|
authorization_required unless authorization_token
|
33
46
|
|
34
47
|
scopes << oauth_application_default_scope if scopes.empty?
|
35
48
|
|
36
|
-
token_scopes = authorization_token["
|
49
|
+
token_scopes = authorization_token["scope"].split(" ")
|
37
50
|
|
38
51
|
authorization_required unless scopes.any? { |scope| token_scopes.include?(scope) }
|
39
52
|
end
|
40
53
|
|
41
54
|
private
|
42
55
|
|
56
|
+
unless method_defined?(:last_account_login_at)
|
57
|
+
def last_account_login_at
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
43
62
|
def authorization_token
|
44
63
|
return @authorization_token if defined?(@authorization_token)
|
45
64
|
|
46
65
|
@authorization_token = begin
|
47
|
-
|
66
|
+
bearer_token = fetch_access_token
|
67
|
+
|
68
|
+
return unless bearer_token
|
69
|
+
|
70
|
+
jwt_token = jwt_decode(bearer_token)
|
71
|
+
|
72
|
+
return unless jwt_token
|
73
|
+
|
74
|
+
return if jwt_token["iss"] != (oauth_jwt_token_issuer || authorization_server_url) ||
|
75
|
+
(oauth_jwt_audience && jwt_token["aud"] != oauth_jwt_audience) ||
|
76
|
+
!jwt_token["sub"]
|
77
|
+
|
78
|
+
jwt_token
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# /authorize
|
83
|
+
|
84
|
+
def validate_oauth_grant_params
|
85
|
+
# TODO: add support for requst_uri
|
86
|
+
redirect_response_error("request_uri_not_supported") if param_or_nil("request_uri")
|
87
|
+
|
88
|
+
request_object = param_or_nil("request")
|
89
|
+
|
90
|
+
return super unless request_object && oauth_application
|
91
|
+
|
92
|
+
jws_jwk = if oauth_application[oauth_application_jws_jwk_column]
|
93
|
+
jwk = oauth_application[oauth_application_jws_jwk_column]
|
94
|
+
|
95
|
+
jwk = JSON.parse(jwk, symbolize_names: true) if jwk && jwk.is_a?(String)
|
96
|
+
else
|
97
|
+
redirect_response_error("invalid_request_object")
|
98
|
+
end
|
99
|
+
|
100
|
+
claims = jwt_decode(request_object, jws_key: jwk_import(jws_jwk), jws_algorithm: jwk[:alg])
|
101
|
+
|
102
|
+
redirect_response_error("invalid_request_object") unless claims
|
103
|
+
|
104
|
+
# If signed, the Authorization Request
|
105
|
+
# Object SHOULD contain the Claims "iss" (issuer) and "aud" (audience)
|
106
|
+
# as members, with their semantics being the same as defined in the JWT
|
107
|
+
# [RFC7519] specification. The value of "aud" should be the value of
|
108
|
+
# the Authorization Server (AS) "issuer" as defined in RFC8414
|
109
|
+
# [RFC8414].
|
110
|
+
claims.delete("iss")
|
111
|
+
audience = claims.delete("aud")
|
112
|
+
|
113
|
+
redirect_response_error("invalid_request_object") if audience && audience != authorization_server_url
|
48
114
|
|
49
|
-
|
115
|
+
claims.each do |k, v|
|
116
|
+
request.params[k.to_s] = v
|
117
|
+
end
|
118
|
+
|
119
|
+
super
|
120
|
+
end
|
121
|
+
|
122
|
+
# /token
|
50
123
|
|
51
|
-
|
124
|
+
def require_oauth_application
|
125
|
+
# requset authentication optional for assertions
|
126
|
+
return super unless param("grant_type") == "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
52
127
|
|
53
|
-
|
128
|
+
claims = jwt_decode(param("assertion"))
|
129
|
+
|
130
|
+
redirect_response_error("invalid_grant") unless claims
|
131
|
+
|
132
|
+
@oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => claims["client_id"]).first
|
133
|
+
|
134
|
+
authorization_required unless @oauth_application
|
135
|
+
end
|
136
|
+
|
137
|
+
def validate_oauth_token_params
|
138
|
+
if param("grant_type") == "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
139
|
+
redirect_response_error("invalid_client") unless param_or_nil("assertion")
|
140
|
+
else
|
141
|
+
super
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def create_oauth_token
|
146
|
+
if param("grant_type") == "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
147
|
+
create_oauth_token_from_assertion
|
148
|
+
else
|
149
|
+
super
|
54
150
|
end
|
55
151
|
end
|
56
152
|
|
153
|
+
def create_oauth_token_from_assertion
|
154
|
+
claims = jwt_decode(param("assertion"))
|
155
|
+
|
156
|
+
account = account_ds(claims["sub"]).first
|
157
|
+
|
158
|
+
redirect_response_error("invalid_client") unless oauth_application && account
|
159
|
+
|
160
|
+
create_params = {
|
161
|
+
oauth_tokens_account_id_column => claims["sub"],
|
162
|
+
oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
|
163
|
+
oauth_tokens_scopes_column => claims["scope"]
|
164
|
+
}
|
165
|
+
|
166
|
+
generate_oauth_token(create_params, false)
|
167
|
+
end
|
168
|
+
|
57
169
|
def generate_oauth_token(params = {}, should_generate_refresh_token = true)
|
58
170
|
create_params = {
|
59
171
|
oauth_grants_expires_in_column => Time.now + oauth_token_expires_in
|
60
172
|
}.merge(params)
|
61
173
|
|
62
|
-
|
63
|
-
|
174
|
+
oauth_token = rescue_from_uniqueness_error do
|
175
|
+
if should_generate_refresh_token
|
176
|
+
refresh_token = oauth_unique_id_generator
|
64
177
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
178
|
+
if oauth_tokens_refresh_token_hash_column
|
179
|
+
create_params[oauth_tokens_refresh_token_hash_column] = generate_token_hash(refresh_token)
|
180
|
+
else
|
181
|
+
create_params[oauth_tokens_refresh_token_column] = refresh_token
|
182
|
+
end
|
69
183
|
end
|
184
|
+
|
185
|
+
_generate_oauth_token(create_params)
|
70
186
|
end
|
71
187
|
|
72
|
-
|
188
|
+
claims = jwt_claims(oauth_token)
|
189
|
+
|
190
|
+
# one of the points of using jwt is avoiding database lookups, so we put here all relevant
|
191
|
+
# token data.
|
192
|
+
claims[:scope] = oauth_token[oauth_tokens_scopes_column]
|
73
193
|
|
74
|
-
|
194
|
+
token = jwt_encode(claims)
|
75
195
|
|
76
|
-
|
77
|
-
|
78
|
-
|
196
|
+
oauth_token[oauth_tokens_token_column] = token
|
197
|
+
oauth_token
|
198
|
+
end
|
199
|
+
|
200
|
+
def jwt_claims(oauth_token)
|
201
|
+
issued_at = Time.now.utc.to_i
|
202
|
+
|
203
|
+
claims = {
|
204
|
+
iss: (oauth_jwt_token_issuer || authorization_server_url), # issuer
|
79
205
|
iat: issued_at, # issued at
|
80
206
|
#
|
81
207
|
# sub REQUIRED - as defined in section 4.1.2 of [RFC7519]. In case of
|
@@ -86,40 +212,117 @@ module Rodauth
|
|
86
212
|
# owner is involved, such as the client credentials grant, the value
|
87
213
|
# of "sub" SHOULD correspond to an identifier the authorization
|
88
214
|
# server uses to indicate the client application.
|
89
|
-
|
215
|
+
sub: jwt_subject(oauth_token),
|
216
|
+
client_id: oauth_application[oauth_applications_client_id_column],
|
90
217
|
|
91
218
|
exp: issued_at + oauth_token_expires_in,
|
92
|
-
aud: oauth_jwt_audience
|
93
|
-
|
94
|
-
# one of the points of using jwt is avoiding database lookups, so we put here all relevant
|
95
|
-
# token data.
|
96
|
-
scopes: oauth_token[oauth_tokens_scopes_column]
|
219
|
+
aud: (oauth_jwt_audience || oauth_application[oauth_applications_client_id_column])
|
97
220
|
}
|
98
221
|
|
99
|
-
|
222
|
+
claims[:auth_time] = last_account_login_at.utc.to_i if last_account_login_at
|
100
223
|
|
101
|
-
|
102
|
-
|
224
|
+
claims
|
225
|
+
end
|
226
|
+
|
227
|
+
def jwt_subject(oauth_token)
|
228
|
+
case oauth_jwt_subject_type
|
229
|
+
when "public"
|
230
|
+
oauth_token[oauth_tokens_account_id_column]
|
231
|
+
when "pairwise"
|
232
|
+
id = oauth_token[oauth_tokens_account_id_column]
|
233
|
+
application_id = oauth_token[oauth_tokens_oauth_application_id_column]
|
234
|
+
Digest::SHA256.hexdigest("#{id}#{application_id}#{oauth_jwt_subject_secret}")
|
235
|
+
else
|
236
|
+
raise StandardError, "unexpected subject (#{oauth_jwt_subject_type})"
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def oauth_token_by_token(token, *)
|
241
|
+
jwt_decode(token)
|
242
|
+
end
|
243
|
+
|
244
|
+
def json_token_introspect_payload(oauth_token)
|
245
|
+
return { active: false } unless oauth_token
|
246
|
+
|
247
|
+
return super unless oauth_token["sub"] # naive check on whether it's a jwt token
|
248
|
+
|
249
|
+
{
|
250
|
+
active: true,
|
251
|
+
scope: oauth_token["scope"],
|
252
|
+
client_id: oauth_token["client_id"],
|
253
|
+
# username
|
254
|
+
token_type: "access_token",
|
255
|
+
exp: oauth_token["exp"],
|
256
|
+
iat: oauth_token["iat"],
|
257
|
+
nbf: oauth_token["nbf"],
|
258
|
+
sub: oauth_token["sub"],
|
259
|
+
aud: oauth_token["aud"],
|
260
|
+
iss: oauth_token["iss"],
|
261
|
+
jti: oauth_token["jti"]
|
262
|
+
}
|
263
|
+
end
|
264
|
+
|
265
|
+
def oauth_server_metadata_body(path)
|
266
|
+
metadata = super
|
267
|
+
metadata.merge! \
|
268
|
+
jwks_uri: jwks_url,
|
269
|
+
token_endpoint_auth_signing_alg_values_supported: [oauth_jwt_algorithm]
|
270
|
+
metadata
|
103
271
|
end
|
104
272
|
|
105
273
|
def _jwt_key
|
106
|
-
@_jwt_key ||= oauth_jwt_key || oauth_application[oauth_applications_client_secret_column]
|
274
|
+
@_jwt_key ||= oauth_jwt_key || (oauth_application[oauth_applications_client_secret_column] if oauth_application)
|
275
|
+
end
|
276
|
+
|
277
|
+
# Resource Server only!
|
278
|
+
#
|
279
|
+
# returns the jwks set from the authorization server.
|
280
|
+
def auth_server_jwks_set
|
281
|
+
metadata = authorization_server_metadata
|
282
|
+
|
283
|
+
return unless metadata && (jwks_uri = metadata[:jwks_uri])
|
284
|
+
|
285
|
+
jwks_uri = URI(jwks_uri)
|
286
|
+
|
287
|
+
jwks = JWKS[jwks_uri]
|
288
|
+
|
289
|
+
return jwks if jwks
|
290
|
+
|
291
|
+
JWKS.set(jwks_uri) do
|
292
|
+
http = Net::HTTP.new(jwks_uri.host, jwks_uri.port)
|
293
|
+
http.use_ssl = jwks_uri.scheme == "https"
|
294
|
+
|
295
|
+
request = Net::HTTP::Get.new(jwks_uri.request_uri)
|
296
|
+
request["accept"] = json_response_content_type
|
297
|
+
response = http.request(request)
|
298
|
+
authorization_required unless response.code.to_i == 200
|
299
|
+
|
300
|
+
# time-to-live
|
301
|
+
ttl = if response.key?("cache-control")
|
302
|
+
cache_control = response["cache-control"]
|
303
|
+
cache_control[/max-age=(\d+)/, 1]
|
304
|
+
elsif response.key?("expires")
|
305
|
+
DateTime.httpdate(response["expires"]).utc.to_i - Time.now.utc.to_i
|
306
|
+
end
|
307
|
+
|
308
|
+
[JSON.parse(response.body, symbolize_names: true), ttl]
|
309
|
+
end
|
107
310
|
end
|
108
311
|
|
109
312
|
if defined?(JSON::JWT)
|
110
|
-
|
313
|
+
|
314
|
+
def jwk_import(data)
|
315
|
+
JSON::JWK.new(data)
|
316
|
+
end
|
111
317
|
|
112
318
|
# json-jwt
|
113
319
|
def jwt_encode(payload)
|
114
320
|
jwt = JSON::JWT.new(payload)
|
321
|
+
jwk = JSON::JWK.new(_jwt_key)
|
322
|
+
|
323
|
+
jwt = jwt.sign(jwk, oauth_jwt_algorithm)
|
324
|
+
jwt.kid = jwk.thumbprint
|
115
325
|
|
116
|
-
jwt = if oauth_jwt_jwk_key
|
117
|
-
jwk = JSON::JWK.new(oauth_jwt_jwk_key)
|
118
|
-
jwt.kid = jwk.thumbprint
|
119
|
-
jwt.sign(oauth_jwt_jwk_key, oauth_jwt_jwk_algorithm)
|
120
|
-
else
|
121
|
-
jwt.sign(_jwt_key, oauth_jwt_algorithm)
|
122
|
-
end
|
123
326
|
if oauth_jwt_jwe_key
|
124
327
|
algorithm = oauth_jwt_jwe_algorithm.to_sym if oauth_jwt_jwe_algorithm
|
125
328
|
jwt = jwt.encrypt(oauth_jwt_jwe_public_key || oauth_jwt_jwe_key,
|
@@ -129,45 +332,57 @@ module Rodauth
|
|
129
332
|
jwt.to_s
|
130
333
|
end
|
131
334
|
|
132
|
-
def jwt_decode(token)
|
335
|
+
def jwt_decode(token, jws_key: oauth_jwt_public_key || _jwt_key, **)
|
133
336
|
token = JSON::JWT.decode(token, oauth_jwt_jwe_key).plain_text if oauth_jwt_jwe_key
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
337
|
+
|
338
|
+
if is_authorization_server?
|
339
|
+
if oauth_jwt_legacy_public_key
|
340
|
+
JSON::JWT.decode(token, JSON::JWK::Set.new({ keys: jwks_set }))
|
341
|
+
elsif jws_key
|
342
|
+
JSON::JWT.decode(token, jws_key)
|
343
|
+
end
|
344
|
+
elsif (jwks = auth_server_jwks_set)
|
345
|
+
JSON::JWT.decode(token, JSON::JWK::Set.new(jwks))
|
139
346
|
end
|
140
347
|
rescue JSON::JWT::Exception
|
141
348
|
nil
|
142
349
|
end
|
143
|
-
|
350
|
+
|
351
|
+
def jwks_set
|
352
|
+
@jwks_set ||= [
|
353
|
+
(JSON::JWK.new(oauth_jwt_public_key).merge(use: "sig", alg: oauth_jwt_algorithm) if oauth_jwt_public_key),
|
354
|
+
(JSON::JWK.new(oauth_jwt_legacy_public_key).merge(use: "sig", alg: oauth_jwt_legacy_algorithm) if oauth_jwt_legacy_public_key),
|
355
|
+
(JSON::JWK.new(oauth_jwt_jwe_public_key).merge(use: "enc", alg: oauth_jwt_jwe_algorithm) if oauth_jwt_jwe_public_key)
|
356
|
+
].compact
|
357
|
+
end
|
358
|
+
|
144
359
|
elsif defined?(JWT)
|
145
360
|
|
146
361
|
# ruby-jwt
|
147
362
|
|
363
|
+
def jwk_import(data)
|
364
|
+
JWT::JWK.import(data).keypair
|
365
|
+
end
|
366
|
+
|
148
367
|
def jwt_encode(payload)
|
149
368
|
headers = {}
|
150
369
|
|
151
|
-
key
|
152
|
-
jwk_key = JWT::JWK.new(oauth_jwt_jwk_key)
|
153
|
-
# JWK
|
154
|
-
# Currently only supports RSA public keys.
|
155
|
-
headers[:kid] = jwk_key.kid
|
370
|
+
key = _jwt_key
|
156
371
|
|
157
|
-
|
158
|
-
|
159
|
-
|
372
|
+
if key.is_a?(OpenSSL::PKey::RSA)
|
373
|
+
jwk = JWT::JWK.new(_jwt_key)
|
374
|
+
headers[:kid] = jwk.kid
|
160
375
|
|
161
|
-
|
162
|
-
|
376
|
+
key = jwk.keypair
|
377
|
+
end
|
163
378
|
|
164
379
|
# Use the key and iat to create a unique key per request to prevent replay attacks
|
165
380
|
jti_raw = [key, payload[:iat]].join(":").to_s
|
166
|
-
jti = Digest::
|
381
|
+
jti = Digest::SHA256.hexdigest(jti_raw)
|
167
382
|
|
168
383
|
# @see JWT reserved claims - https://tools.ietf.org/html/draft-jones-json-web-token-07#page-7
|
169
384
|
payload[:jti] = jti
|
170
|
-
token = JWT.encode(payload, key,
|
385
|
+
token = JWT.encode(payload, key, oauth_jwt_algorithm, headers)
|
171
386
|
|
172
387
|
if oauth_jwt_jwe_key
|
173
388
|
params = {
|
@@ -182,47 +397,70 @@ module Rodauth
|
|
182
397
|
token
|
183
398
|
end
|
184
399
|
|
185
|
-
def jwt_decode(token)
|
400
|
+
def jwt_decode(token, jws_key: oauth_jwt_public_key || _jwt_key, jws_algorithm: oauth_jwt_algorithm)
|
186
401
|
# decrypt jwe
|
187
402
|
token = JWE.decrypt(token, oauth_jwt_jwe_key) if oauth_jwt_jwe_key
|
188
|
-
|
189
403
|
# decode jwt
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
headers[:jwks] = jwk_loader
|
203
|
-
|
204
|
-
nil
|
205
|
-
else
|
206
|
-
# JWS
|
207
|
-
# worst case scenario, the key is the application key
|
208
|
-
oauth_jwt_public_key || _jwt_key
|
209
|
-
end
|
210
|
-
token, = JWT.decode(token, key, true, headers)
|
211
|
-
token
|
212
|
-
rescue JWT::DecodeError
|
404
|
+
if is_authorization_server?
|
405
|
+
if oauth_jwt_legacy_public_key
|
406
|
+
algorithms = jwks_set.select { |k| k[:use] == "sig" }.map { |k| k[:alg] }
|
407
|
+
JWT.decode(token, nil, true, jwks: { keys: jwks_set }, algorithms: algorithms).first
|
408
|
+
elsif jws_key
|
409
|
+
JWT.decode(token, jws_key, true, algorithms: [jws_algorithm]).first
|
410
|
+
end
|
411
|
+
elsif (jwks = auth_server_jwks_set)
|
412
|
+
algorithms = jwks[:keys].select { |k| k[:use] == "sig" }.map { |k| k[:alg] }
|
413
|
+
JWT.decode(token, nil, true, jwks: jwks, algorithms: algorithms).first
|
414
|
+
end
|
415
|
+
rescue JWT::DecodeError, JWT::JWKError
|
213
416
|
nil
|
214
417
|
end
|
215
418
|
|
419
|
+
def jwks_set
|
420
|
+
@jwks_set ||= [
|
421
|
+
(JWT::JWK.new(oauth_jwt_public_key).export.merge(use: "sig", alg: oauth_jwt_algorithm) if oauth_jwt_public_key),
|
422
|
+
(
|
423
|
+
if oauth_jwt_legacy_public_key
|
424
|
+
JWT::JWK.new(oauth_jwt_legacy_public_key).export.merge(use: "sig", alg: oauth_jwt_legacy_algorithm)
|
425
|
+
end
|
426
|
+
),
|
427
|
+
(JWT::JWK.new(oauth_jwt_jwe_public_key).export.merge(use: "enc", alg: oauth_jwt_jwe_algorithm) if oauth_jwt_jwe_public_key)
|
428
|
+
].compact
|
429
|
+
end
|
216
430
|
else
|
217
431
|
# :nocov:
|
432
|
+
def jwk_import(_data)
|
433
|
+
raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\""
|
434
|
+
end
|
435
|
+
|
218
436
|
def jwt_encode(_token)
|
219
437
|
raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\""
|
220
438
|
end
|
221
439
|
|
222
|
-
def jwt_decode(_token)
|
440
|
+
def jwt_decode(_token, **)
|
441
|
+
raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\""
|
442
|
+
end
|
443
|
+
|
444
|
+
def jwks_set
|
223
445
|
raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\""
|
224
446
|
end
|
225
447
|
# :nocov:
|
226
448
|
end
|
449
|
+
|
450
|
+
def validate_oauth_revoke_params
|
451
|
+
token_hint = param_or_nil("token_type_hint")
|
452
|
+
|
453
|
+
throw(:rodauth_error) if !token_hint || token_hint == "access_token"
|
454
|
+
|
455
|
+
super
|
456
|
+
end
|
457
|
+
|
458
|
+
route(:jwks) do |r|
|
459
|
+
next unless is_authorization_server?
|
460
|
+
|
461
|
+
r.get do
|
462
|
+
json_response_success({ keys: jwks_set })
|
463
|
+
end
|
464
|
+
end
|
227
465
|
end
|
228
466
|
end
|