rodauth-oauth 0.7.3 → 0.9.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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -418
  3. data/README.md +30 -390
  4. data/doc/release_notes/0_0_1.md +3 -0
  5. data/doc/release_notes/0_0_2.md +15 -0
  6. data/doc/release_notes/0_0_3.md +31 -0
  7. data/doc/release_notes/0_0_4.md +36 -0
  8. data/doc/release_notes/0_0_5.md +36 -0
  9. data/doc/release_notes/0_0_6.md +21 -0
  10. data/doc/release_notes/0_1_0.md +44 -0
  11. data/doc/release_notes/0_2_0.md +43 -0
  12. data/doc/release_notes/0_3_0.md +28 -0
  13. data/doc/release_notes/0_4_0.md +18 -0
  14. data/doc/release_notes/0_4_1.md +9 -0
  15. data/doc/release_notes/0_4_2.md +5 -0
  16. data/doc/release_notes/0_4_3.md +3 -0
  17. data/doc/release_notes/0_5_0.md +11 -0
  18. data/doc/release_notes/0_5_1.md +13 -0
  19. data/doc/release_notes/0_6_0.md +9 -0
  20. data/doc/release_notes/0_6_1.md +6 -0
  21. data/doc/release_notes/0_7_0.md +20 -0
  22. data/doc/release_notes/0_7_1.md +10 -0
  23. data/doc/release_notes/0_7_2.md +21 -0
  24. data/doc/release_notes/0_7_3.md +10 -0
  25. data/doc/release_notes/0_7_4.md +5 -0
  26. data/doc/release_notes/0_8_0.md +37 -0
  27. data/doc/release_notes/0_9_0.md +56 -0
  28. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +50 -0
  29. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb +11 -0
  30. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb +20 -0
  31. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +55 -0
  32. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +29 -0
  33. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +39 -0
  34. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +30 -0
  35. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +35 -0
  36. data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +21 -1
  37. data/lib/rodauth/features/oauth.rb +3 -1418
  38. data/lib/rodauth/features/oauth_application_management.rb +225 -0
  39. data/lib/rodauth/features/oauth_assertion_base.rb +96 -0
  40. data/lib/rodauth/features/oauth_authorization_code_grant.rb +252 -0
  41. data/lib/rodauth/features/oauth_authorization_server.rb +0 -0
  42. data/lib/rodauth/features/oauth_base.rb +771 -0
  43. data/lib/rodauth/features/oauth_client_credentials_grant.rb +33 -0
  44. data/lib/rodauth/features/oauth_device_grant.rb +220 -0
  45. data/lib/rodauth/features/oauth_dynamic_client_registration.rb +252 -0
  46. data/lib/rodauth/features/oauth_http_mac.rb +3 -21
  47. data/lib/rodauth/features/oauth_implicit_grant.rb +59 -0
  48. data/lib/rodauth/features/oauth_jwt.rb +276 -100
  49. data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +59 -0
  50. data/lib/rodauth/features/oauth_management_base.rb +68 -0
  51. data/lib/rodauth/features/oauth_pkce.rb +98 -0
  52. data/lib/rodauth/features/oauth_resource_server.rb +21 -0
  53. data/lib/rodauth/features/oauth_saml_bearer_grant.rb +102 -0
  54. data/lib/rodauth/features/oauth_token_introspection.rb +108 -0
  55. data/lib/rodauth/features/oauth_token_management.rb +79 -0
  56. data/lib/rodauth/features/oauth_token_revocation.rb +109 -0
  57. data/lib/rodauth/features/oidc.rb +36 -6
  58. data/lib/rodauth/features/oidc_dynamic_client_registration.rb +147 -0
  59. data/lib/rodauth/oauth/database_extensions.rb +15 -2
  60. data/lib/rodauth/oauth/jwe_extensions.rb +64 -0
  61. data/lib/rodauth/oauth/refinements.rb +48 -0
  62. data/lib/rodauth/oauth/ttl_store.rb +9 -3
  63. data/lib/rodauth/oauth/version.rb +1 -1
  64. data/locales/en.yml +33 -12
  65. data/templates/authorize.str +57 -8
  66. data/templates/client_secret_field.str +2 -2
  67. data/templates/description_field.str +1 -1
  68. data/templates/device_search.str +11 -0
  69. data/templates/device_verification.str +24 -0
  70. data/templates/homepage_url_field.str +2 -2
  71. data/templates/jwks_field.str +4 -0
  72. data/templates/jwt_public_key_field.str +4 -0
  73. data/templates/name_field.str +1 -1
  74. data/templates/new_oauth_application.str +9 -0
  75. data/templates/oauth_application.str +7 -3
  76. data/templates/oauth_application_oauth_tokens.str +52 -0
  77. data/templates/oauth_applications.str +3 -2
  78. data/templates/oauth_tokens.str +10 -11
  79. data/templates/redirect_uri_field.str +2 -2
  80. metadata +84 -4
  81. data/lib/rodauth/features/oauth_saml.rb +0 -104
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rodauth/oauth/refinements"
4
+
5
+ module Rodauth
6
+ Feature.define(:oauth_pkce, :OauthPkce) do
7
+ using PrefixExtensions
8
+
9
+ depends :oauth_authorization_code_grant
10
+
11
+ auth_value_method :use_oauth_pkce?, true
12
+
13
+ auth_value_method :oauth_require_pkce, false
14
+ auth_value_method :oauth_pkce_challenge_method, "S256"
15
+
16
+ auth_value_method :oauth_grants_code_challenge_column, :code_challenge
17
+ auth_value_method :oauth_grants_code_challenge_method_column, :code_challenge_method
18
+
19
+ auth_value_method :code_challenge_required_error_code, "invalid_request"
20
+ translatable_method :code_challenge_required_message, "code challenge required"
21
+ auth_value_method :unsupported_transform_algorithm_error_code, "invalid_request"
22
+ translatable_method :unsupported_transform_algorithm_message, "transform algorithm not supported"
23
+
24
+ private
25
+
26
+ def authorized_oauth_application?(oauth_application, client_secret, _)
27
+ return true if use_oauth_pkce? && param_or_nil("code_verifier")
28
+
29
+ super
30
+ end
31
+
32
+ def validate_oauth_grant_params
33
+ validate_pkce_challenge_params if use_oauth_pkce?
34
+
35
+ super
36
+ end
37
+
38
+ def create_oauth_grant(create_params = {})
39
+ # PKCE flow
40
+ if use_oauth_pkce? && (code_challenge = param_or_nil("code_challenge"))
41
+ code_challenge_method = param_or_nil("code_challenge_method")
42
+
43
+ create_params[oauth_grants_code_challenge_column] = code_challenge
44
+ create_params[oauth_grants_code_challenge_method_column] = code_challenge_method
45
+ end
46
+
47
+ super
48
+ end
49
+
50
+ def create_oauth_token_from_authorization_code(oauth_grant, create_params)
51
+ if use_oauth_pkce?
52
+ if oauth_grant[oauth_grants_code_challenge_column]
53
+ code_verifier = param_or_nil("code_verifier")
54
+
55
+ redirect_response_error("invalid_request") unless code_verifier && check_valid_grant_challenge?(oauth_grant, code_verifier)
56
+ elsif oauth_require_pkce
57
+ redirect_response_error("code_challenge_required")
58
+ end
59
+ end
60
+
61
+ super
62
+ end
63
+
64
+ def validate_pkce_challenge_params
65
+ if param_or_nil("code_challenge")
66
+
67
+ challenge_method = param_or_nil("code_challenge_method")
68
+ redirect_response_error("code_challenge_required") unless oauth_pkce_challenge_method == challenge_method
69
+ else
70
+ return unless oauth_require_pkce
71
+
72
+ redirect_response_error("code_challenge_required")
73
+ end
74
+ end
75
+
76
+ def check_valid_grant_challenge?(grant, verifier)
77
+ challenge = grant[oauth_grants_code_challenge_column]
78
+
79
+ case grant[oauth_grants_code_challenge_method_column]
80
+ when "plain"
81
+ challenge == verifier
82
+ when "S256"
83
+ generated_challenge = Base64.urlsafe_encode64(Digest::SHA256.digest(verifier))
84
+ generated_challenge.delete_suffix!("=") while generated_challenge.end_with?("=")
85
+
86
+ challenge == generated_challenge
87
+ else
88
+ redirect_response_error("unsupported_transform_algorithm")
89
+ end
90
+ end
91
+
92
+ def oauth_server_metadata_body(*)
93
+ super.tap do |data|
94
+ data[:code_challenge_methods_supported] = oauth_pkce_challenge_method if use_oauth_pkce?
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rodauth
4
+ Feature.define(:oauth_resource_server, :OauthResourceServer) do
5
+ def authorization_token
6
+ return @authorization_token if defined?(@authorization_token)
7
+
8
+ # check if there is a token
9
+ bearer_token = fetch_access_token
10
+
11
+ return unless bearer_token
12
+
13
+ # where in resource server, NOT the authorization server.
14
+ payload = introspection_request("access_token", bearer_token)
15
+
16
+ return unless payload["active"]
17
+
18
+ @authorization_token = payload
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,102 @@
1
+ # frozen-string-literal: true
2
+
3
+ require "onelogin/ruby-saml"
4
+
5
+ module Rodauth
6
+ Feature.define(:oauth_saml_bearer_grant, :OauthSamlBearerGrant) do
7
+ depends :oauth_assertion_base
8
+
9
+ auth_value_method :oauth_saml_cert_fingerprint, "9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D"
10
+ auth_value_method :oauth_saml_cert, nil
11
+ auth_value_method :oauth_saml_cert_fingerprint_algorithm, nil
12
+ auth_value_method :oauth_saml_name_identifier_format, "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
13
+
14
+ auth_value_method :oauth_saml_security_authn_requests_signed, true
15
+ auth_value_method :oauth_saml_security_metadata_signed, true
16
+ auth_value_method :oauth_saml_security_digest_method, XMLSecurity::Document::SHA1
17
+ auth_value_method :oauth_saml_security_signature_method, XMLSecurity::Document::RSA_SHA1
18
+
19
+ auth_value_methods(
20
+ :require_oauth_application_from_saml2_bearer_assertion_issuer,
21
+ :require_oauth_application_from_saml2_bearer_assertion_subject,
22
+ :account_from_saml2_bearer_assertion
23
+ )
24
+
25
+ private
26
+
27
+ def require_oauth_application_from_saml2_bearer_assertion_issuer(assertion)
28
+ saml = saml_assertion(assertion)
29
+
30
+ return unless saml
31
+
32
+ db[oauth_applications_table].where(
33
+ oauth_applications_homepage_url_column => saml.issuers
34
+ ).first
35
+ end
36
+
37
+ def require_oauth_application_from_saml2_bearer_assertion_subject(assertion)
38
+ saml = saml_assertion(assertion)
39
+
40
+ return unless saml
41
+
42
+ db[oauth_applications_table].where(
43
+ oauth_applications_client_id_column => saml.nameid
44
+ ).first
45
+ end
46
+
47
+ def account_from_saml2_bearer_assertion(assertion)
48
+ saml = saml_assertion(assertion)
49
+
50
+ return unless saml
51
+
52
+ account_from_bearer_assertion_subject(saml.nameid)
53
+ end
54
+
55
+ def saml_assertion(assertion)
56
+ settings = OneLogin::RubySaml::Settings.new
57
+ settings.idp_cert = oauth_saml_cert
58
+ settings.idp_cert_fingerprint = oauth_saml_cert_fingerprint
59
+ settings.idp_cert_fingerprint_algorithm = oauth_saml_cert_fingerprint_algorithm
60
+ settings.name_identifier_format = oauth_saml_name_identifier_format
61
+ settings.security[:authn_requests_signed] = oauth_saml_security_authn_requests_signed
62
+ settings.security[:metadata_signed] = oauth_saml_security_metadata_signed
63
+ settings.security[:digest_method] = oauth_saml_security_digest_method
64
+ settings.security[:signature_method] = oauth_saml_security_signature_method
65
+
66
+ response = OneLogin::RubySaml::Response.new(assertion, settings: settings, skip_recipient_check: true)
67
+
68
+ # 3. he Assertion MUST have an expiry that limits the time window ...
69
+ # 4. The Assertion MUST have an expiry that limits the time window ...
70
+ # 5. The <Subject> element MUST contain at least one ...
71
+ # 6. The authorization server MUST reject the entire Assertion if the ...
72
+ # 7. If the Assertion issuer directly authenticated the subject, ...
73
+ redirect_response_error("invalid_grant") unless response.is_valid?
74
+
75
+ # In order to issue an access token response as described in OAuth 2.0
76
+ # [RFC6749] or to rely on an Assertion for client authentication, the
77
+ # authorization server MUST validate the Assertion according to the
78
+ # criteria below.
79
+
80
+ # 1. The Assertion's <Issuer> element MUST contain a unique identifier
81
+ # for the entity that issued the Assertion.
82
+ redirect_response_error("invalid_grant") unless response.issuers.size == 1
83
+
84
+ # 2. in addition to the URI references
85
+ # discussed there, the token endpoint URL of the authorization
86
+ # server MAY be used as a URI that identifies the authorization
87
+ # server as an intended audience. The authorization server MUST
88
+ # reject any Assertion that does not contain its own identity as
89
+ # the intended audience.
90
+ redirect_response_error("invalid_grant") if response.audiences && !response.audiences.include?(token_url)
91
+
92
+ response
93
+ end
94
+
95
+ def oauth_server_metadata_body(*)
96
+ super.tap do |data|
97
+ data[:grant_types_supported] << "urn:ietf:params:oauth:grant-type:saml2-bearer"
98
+ data[:token_endpoint_auth_methods_supported] << "urn:ietf:params:oauth:client-assertion-type:saml2-bearer"
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rodauth
4
+ Feature.define(:oauth_token_introspection, :OauthTokenIntrospection) do
5
+ depends :oauth_base
6
+
7
+ before "introspect"
8
+
9
+ auth_value_methods(
10
+ :before_introspection_request
11
+ )
12
+
13
+ # /introspect
14
+ route(:introspect) do |r|
15
+ next unless is_authorization_server?
16
+
17
+ before_introspect_route
18
+
19
+ r.post do
20
+ catch_error do
21
+ validate_oauth_introspect_params
22
+
23
+ before_introspect
24
+ oauth_token = case param("token_type_hint")
25
+ when "access_token"
26
+ oauth_token_by_token(param("token"))
27
+ when "refresh_token"
28
+ oauth_token_by_refresh_token(param("token"))
29
+ else
30
+ oauth_token_by_token(param("token")) || oauth_token_by_refresh_token(param("token"))
31
+ end
32
+
33
+ if oauth_application
34
+ redirect_response_error("invalid_request") if oauth_token && !token_from_application?(oauth_token, oauth_application)
35
+ elsif oauth_token
36
+ @oauth_application = db[oauth_applications_table].where(oauth_applications_id_column =>
37
+ oauth_token[oauth_tokens_oauth_application_id_column]).first
38
+ end
39
+
40
+ json_response_success(json_token_introspect_payload(oauth_token))
41
+ end
42
+
43
+ throw_json_response_error(invalid_oauth_response_status, "invalid_request")
44
+ end
45
+ end
46
+
47
+ # Token introspect
48
+
49
+ def validate_oauth_introspect_params(token_hint_types = %w[access_token refresh_token].freeze)
50
+ # check if valid token hint type
51
+ if param_or_nil("token_type_hint") && !token_hint_types.include?(param("token_type_hint"))
52
+ redirect_response_error("unsupported_token_type")
53
+ end
54
+
55
+ redirect_response_error("invalid_request") unless param_or_nil("token")
56
+ end
57
+
58
+ def json_token_introspect_payload(token)
59
+ return { active: false } unless token
60
+
61
+ {
62
+ active: true,
63
+ scope: token[oauth_tokens_scopes_column],
64
+ client_id: oauth_application[oauth_applications_client_id_column],
65
+ # username
66
+ token_type: oauth_token_type,
67
+ exp: token[oauth_tokens_expires_in_column].to_i
68
+ }
69
+ end
70
+
71
+ def check_csrf?
72
+ case request.path
73
+ when introspect_path
74
+ false
75
+ else
76
+ super
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def introspection_request(token_type_hint, token)
83
+ auth_url = URI(authorization_server_url)
84
+ http = Net::HTTP.new(auth_url.host, auth_url.port)
85
+ http.use_ssl = auth_url.scheme == "https"
86
+
87
+ request = Net::HTTP::Post.new(introspect_path)
88
+ request["content-type"] = "application/x-www-form-urlencoded"
89
+ request["accept"] = json_response_content_type
90
+ request.set_form_data({ "token_type_hint" => token_type_hint, "token" => token })
91
+
92
+ before_introspection_request(request)
93
+ response = http.request(request)
94
+ authorization_required unless response.code.to_i == 200
95
+
96
+ JSON.parse(response.body)
97
+ end
98
+
99
+ def before_introspection_request(request); end
100
+
101
+ def oauth_server_metadata_body(*)
102
+ super.tap do |data|
103
+ data[:introspection_endpoint] = introspect_url
104
+ data[:introspection_endpoint_auth_methods_supported] = %w[client_secret_basic]
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rodauth
4
+ Feature.define(:oauth_token_management, :OauthTokenManagement) do
5
+ using RegexpExtensions
6
+
7
+ depends :oauth_management_base
8
+
9
+ view "oauth_tokens", "My Oauth Tokens", "oauth_tokens"
10
+
11
+ button "Revoke", "oauth_token_revoke"
12
+
13
+ auth_value_method :oauth_tokens_path, "oauth-tokens"
14
+
15
+ %w[token refresh_token expires_in revoked_at].each do |param|
16
+ translatable_method :"oauth_tokens_#{param}_label", param.gsub("_", " ").capitalize
17
+ end
18
+
19
+ auth_value_method :oauth_tokens_route, "oauth-tokens"
20
+ auth_value_method :oauth_tokens_id_pattern, Integer
21
+ auth_value_method :oauth_tokens_per_page, 20
22
+
23
+ auth_value_methods(
24
+ :oauth_token_path
25
+ )
26
+
27
+ def oauth_tokens_path(opts = {})
28
+ route_path(oauth_tokens_route, opts)
29
+ end
30
+
31
+ def oauth_tokens_url(opts = {})
32
+ route_url(oauth_tokens_route, opts)
33
+ end
34
+
35
+ def oauth_token_path(id)
36
+ "#{oauth_tokens_path}/#{id}"
37
+ end
38
+
39
+ def oauth_tokens
40
+ request.on(oauth_tokens_route) do
41
+ require_account
42
+
43
+ request.get do
44
+ page = Integer(param_or_nil("page") || 1)
45
+ per_page = per_page_param(oauth_tokens_per_page)
46
+
47
+ scope.instance_variable_set(:@oauth_tokens, db[oauth_tokens_table]
48
+ .select(Sequel[oauth_tokens_table].*, Sequel[oauth_applications_table][oauth_applications_name_column])
49
+ .join(oauth_applications_table, Sequel[oauth_tokens_table][oauth_tokens_oauth_application_id_column] =>
50
+ Sequel[oauth_applications_table][oauth_applications_id_column])
51
+ .where(Sequel[oauth_tokens_table][oauth_tokens_account_id_column] => account_id)
52
+ .where(oauth_tokens_revoked_at_column => nil)
53
+ .order(Sequel.desc(oauth_tokens_id_column))
54
+ .paginate(page, per_page))
55
+ oauth_tokens_view
56
+ end
57
+
58
+ request.post(oauth_tokens_id_pattern) do |id|
59
+ db[oauth_tokens_table]
60
+ .where(oauth_tokens_id_column => id)
61
+ .where(oauth_tokens_account_id_column => account_id)
62
+ .update(oauth_tokens_revoked_at_column => Sequel::CURRENT_TIMESTAMP)
63
+
64
+ set_notice_flash revoke_oauth_token_notice_flash
65
+ redirect oauth_tokens_path || "/"
66
+ end
67
+ end
68
+ end
69
+
70
+ def check_csrf?
71
+ case request.path
72
+ when oauth_tokens_path
73
+ only_json? ? false : super
74
+ else
75
+ super
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rodauth
4
+ Feature.define(:oauth_token_revocation, :OauthTokenRevocation) do
5
+ depends :oauth_base
6
+
7
+ before "revoke"
8
+ after "revoke"
9
+
10
+ notice_flash "The oauth token has been revoked", "revoke_oauth_token"
11
+
12
+ # /revoke
13
+ route(:revoke) do |r|
14
+ next unless is_authorization_server?
15
+
16
+ before_revoke_route
17
+
18
+ if logged_in?
19
+ require_account
20
+ require_oauth_application_from_account
21
+ else
22
+ require_oauth_application
23
+ end
24
+
25
+ r.post do
26
+ catch_error do
27
+ validate_oauth_revoke_params
28
+
29
+ oauth_token = nil
30
+ transaction do
31
+ before_revoke
32
+ oauth_token = revoke_oauth_token
33
+ after_revoke
34
+ end
35
+
36
+ if accepts_json?
37
+ json_response_success \
38
+ "token" => oauth_token[oauth_tokens_token_column],
39
+ "refresh_token" => oauth_token[oauth_tokens_refresh_token_column],
40
+ "revoked_at" => convert_timestamp(oauth_token[oauth_tokens_revoked_at_column])
41
+ else
42
+ set_notice_flash revoke_oauth_token_notice_flash
43
+ redirect request.referer || "/"
44
+ end
45
+ end
46
+
47
+ redirect_response_error("invalid_request", request.referer || "/")
48
+ end
49
+ end
50
+
51
+ def validate_oauth_revoke_params(token_hint_types = %w[access_token refresh_token].freeze)
52
+ # check if valid token hint type
53
+ if param_or_nil("token_type_hint") && !token_hint_types.include?(param("token_type_hint"))
54
+ redirect_response_error("unsupported_token_type")
55
+ end
56
+
57
+ redirect_response_error("invalid_request") unless param_or_nil("token")
58
+ end
59
+
60
+ def check_csrf?
61
+ case request.path
62
+ when revoke_path
63
+ !json_request?
64
+ else
65
+ super
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def revoke_oauth_token
72
+ token = param("token")
73
+
74
+ oauth_token = if param("token_type_hint") == "refresh_token"
75
+ oauth_token_by_refresh_token(token)
76
+ else
77
+ oauth_token_by_token(token)
78
+ end
79
+
80
+ redirect_response_error("invalid_request") unless oauth_token
81
+
82
+ redirect_response_error("invalid_request") unless token_from_application?(oauth_token, oauth_application)
83
+
84
+ update_params = { oauth_tokens_revoked_at_column => Sequel::CURRENT_TIMESTAMP }
85
+
86
+ ds = db[oauth_tokens_table].where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
87
+
88
+ oauth_token = __update_and_return__(ds, update_params)
89
+
90
+ oauth_token[oauth_tokens_token_column] = token
91
+ oauth_token
92
+
93
+ # If the particular
94
+ # token is a refresh token and the authorization server supports the
95
+ # revocation of access tokens, then the authorization server SHOULD
96
+ # also invalidate all access tokens based on the same authorization
97
+ # grant
98
+ #
99
+ # we don't need to do anything here, as we revalidate existing tokens
100
+ end
101
+
102
+ def oauth_server_metadata_body(*)
103
+ super.tap do |data|
104
+ data[:revocation_endpoint] = revoke_url
105
+ data[:revocation_endpoint_auth_methods_supported] = nil # because it's client_secret_basic
106
+ end
107
+ end
108
+ end
109
+ end
@@ -65,6 +65,13 @@ module Rodauth
65
65
  auth_value_method :oauth_application_default_scope, "openid"
66
66
  auth_value_method :oauth_application_scopes, %w[openid]
67
67
 
68
+ auth_value_method :oauth_applications_id_token_signed_response_alg_column, :id_token_signed_response_alg
69
+ auth_value_method :oauth_applications_id_token_encrypted_response_alg_column, :id_token_encrypted_response_alg
70
+ auth_value_method :oauth_applications_id_token_encrypted_response_enc_column, :id_token_encrypted_response_enc
71
+ auth_value_method :oauth_applications_userinfo_signed_response_alg_column, :userinfo_signed_response_alg
72
+ auth_value_method :oauth_applications_userinfo_encrypted_response_alg_column, :userinfo_encrypted_response_alg
73
+ auth_value_method :oauth_applications_userinfo_encrypted_response_enc_column, :userinfo_encrypted_response_enc
74
+
68
75
  auth_value_method :oauth_grants_nonce_column, :nonce
69
76
  auth_value_method :oauth_tokens_nonce_column, :nonce
70
77
 
@@ -106,7 +113,23 @@ module Rodauth
106
113
 
107
114
  fill_with_account_claims(oidc_claims, account, oauth_scopes)
108
115
 
109
- json_response_success(oidc_claims)
116
+ @oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => oauth_token["client_id"]).first
117
+
118
+ if (algo = @oauth_application && @oauth_application[oauth_applications_userinfo_signed_response_alg_column])
119
+ params = {
120
+ jwks: oauth_application_jwks,
121
+ encryption_algorithm: @oauth_application[oauth_applications_userinfo_encrypted_response_alg_column],
122
+ encryption_method: @oauth_application[oauth_applications_userinfo_encrypted_response_enc_column]
123
+ }
124
+ jwt = jwt_encode(
125
+ oidc_claims,
126
+ signing_algorithm: algo,
127
+ **params
128
+ )
129
+ jwt_response_success(jwt)
130
+ else
131
+ json_response_success(oidc_claims)
132
+ end
110
133
  end
111
134
 
112
135
  throw_json_response_error(authorization_required_error_status, "invalid_token")
@@ -283,7 +306,7 @@ module Rodauth
283
306
  redirect_response_error("consent_required")
284
307
  end
285
308
  when "select-account"
286
- # obly works if select_account plugin is available
309
+ # only works if select_account plugin is available
287
310
  require_select_account if respond_to?(:require_select_account)
288
311
  else
289
312
  redirect_response_error("invalid_request")
@@ -302,7 +325,7 @@ module Rodauth
302
325
  super(oauth_grant, create_params.merge(oauth_tokens_nonce_column => oauth_grant[oauth_grants_nonce_column]))
303
326
  end
304
327
 
305
- def create_oauth_token
328
+ def create_oauth_token(*)
306
329
  oauth_token = super
307
330
  generate_id_token(oauth_token)
308
331
  oauth_token
@@ -330,7 +353,13 @@ module Rodauth
330
353
 
331
354
  fill_with_account_claims(id_token_claims, account, oauth_scopes)
332
355
 
333
- oauth_token[:id_token] = jwt_encode(id_token_claims)
356
+ params = {
357
+ jwks: oauth_application_jwks,
358
+ signing_algorithm: oauth_application[oauth_applications_id_token_signed_response_alg_column] || oauth_jwt_algorithm,
359
+ encryption_algorithm: oauth_application[oauth_applications_id_token_encrypted_response_alg_column],
360
+ encryption_method: oauth_application[oauth_applications_id_token_encrypted_response_enc_column]
361
+ }
362
+ oauth_token[:id_token] = jwt_encode(id_token_claims, **params)
334
363
  end
335
364
 
336
365
  # aka fill_with_standard_claims
@@ -443,7 +472,7 @@ module Rodauth
443
472
 
444
473
  # Metadata
445
474
 
446
- def openid_configuration_body(path)
475
+ def openid_configuration_body(path = nil)
447
476
  metadata = oauth_server_metadata_body(path).select do |k, _|
448
477
  VALID_METADATA_KEYS.include?(k)
449
478
  end
@@ -461,7 +490,8 @@ module Rodauth
461
490
  scope_claims.unshift("auth_time") if last_account_login_at
462
491
 
463
492
  response_types_supported = metadata[:response_types_supported]
464
- if use_oauth_implicit_grant_type?
493
+
494
+ if metadata[:grant_types_supported].include?("implicit")
465
495
  response_types_supported += ["none", "id_token", "code token", "code id_token", "id_token token", "code id_token token"]
466
496
  end
467
497