rodauth-oauth 0.7.4 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -424
  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/doc/release_notes/0_9_1.md +9 -0
  29. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +25 -4
  30. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb +11 -0
  31. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb +20 -0
  32. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +27 -10
  33. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +17 -5
  34. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +39 -0
  35. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +6 -5
  36. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +12 -15
  37. data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +21 -1
  38. data/lib/rodauth/features/oauth.rb +3 -1418
  39. data/lib/rodauth/features/oauth_application_management.rb +225 -0
  40. data/lib/rodauth/features/oauth_assertion_base.rb +96 -0
  41. data/lib/rodauth/features/oauth_authorization_code_grant.rb +252 -0
  42. data/lib/rodauth/features/oauth_authorization_server.rb +0 -0
  43. data/lib/rodauth/features/oauth_base.rb +778 -0
  44. data/lib/rodauth/features/oauth_client_credentials_grant.rb +33 -0
  45. data/lib/rodauth/features/oauth_device_grant.rb +220 -0
  46. data/lib/rodauth/features/oauth_dynamic_client_registration.rb +252 -0
  47. data/lib/rodauth/features/oauth_http_mac.rb +3 -21
  48. data/lib/rodauth/features/oauth_implicit_grant.rb +59 -0
  49. data/lib/rodauth/features/oauth_jwt.rb +275 -100
  50. data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +59 -0
  51. data/lib/rodauth/features/oauth_management_base.rb +68 -0
  52. data/lib/rodauth/features/oauth_pkce.rb +98 -0
  53. data/lib/rodauth/features/oauth_resource_server.rb +21 -0
  54. data/lib/rodauth/features/oauth_saml_bearer_grant.rb +102 -0
  55. data/lib/rodauth/features/oauth_token_introspection.rb +108 -0
  56. data/lib/rodauth/features/oauth_token_management.rb +79 -0
  57. data/lib/rodauth/features/oauth_token_revocation.rb +109 -0
  58. data/lib/rodauth/features/oidc.rb +38 -9
  59. data/lib/rodauth/features/oidc_dynamic_client_registration.rb +147 -0
  60. data/lib/rodauth/oauth/database_extensions.rb +15 -2
  61. data/lib/rodauth/oauth/jwe_extensions.rb +64 -0
  62. data/lib/rodauth/oauth/refinements.rb +48 -0
  63. data/lib/rodauth/oauth/ttl_store.rb +9 -3
  64. data/lib/rodauth/oauth/version.rb +1 -1
  65. data/locales/en.yml +33 -12
  66. data/templates/authorize.str +57 -8
  67. data/templates/client_secret_field.str +2 -2
  68. data/templates/description_field.str +1 -1
  69. data/templates/device_search.str +11 -0
  70. data/templates/device_verification.str +24 -0
  71. data/templates/homepage_url_field.str +2 -2
  72. data/templates/jwks_field.str +4 -0
  73. data/templates/jwt_public_key_field.str +4 -0
  74. data/templates/name_field.str +1 -1
  75. data/templates/new_oauth_application.str +9 -0
  76. data/templates/oauth_application.str +7 -3
  77. data/templates/oauth_application_oauth_tokens.str +52 -0
  78. data/templates/oauth_applications.str +3 -2
  79. data/templates/oauth_tokens.str +10 -11
  80. data/templates/redirect_uri_field.str +2 -2
  81. metadata +80 -3
  82. 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")
@@ -211,8 +234,7 @@ module Rodauth
211
234
  href: authorization_server_url
212
235
  }]
213
236
  })
214
- response.write(json_payload)
215
- request.halt
237
+ return_response(json_payload)
216
238
  end
217
239
  end
218
240
  end
@@ -283,7 +305,7 @@ module Rodauth
283
305
  redirect_response_error("consent_required")
284
306
  end
285
307
  when "select-account"
286
- # obly works if select_account plugin is available
308
+ # only works if select_account plugin is available
287
309
  require_select_account if respond_to?(:require_select_account)
288
310
  else
289
311
  redirect_response_error("invalid_request")
@@ -302,7 +324,7 @@ module Rodauth
302
324
  super(oauth_grant, create_params.merge(oauth_tokens_nonce_column => oauth_grant[oauth_grants_nonce_column]))
303
325
  end
304
326
 
305
- def create_oauth_token
327
+ def create_oauth_token(*)
306
328
  oauth_token = super
307
329
  generate_id_token(oauth_token)
308
330
  oauth_token
@@ -330,7 +352,13 @@ module Rodauth
330
352
 
331
353
  fill_with_account_claims(id_token_claims, account, oauth_scopes)
332
354
 
333
- oauth_token[:id_token] = jwt_encode(id_token_claims)
355
+ params = {
356
+ jwks: oauth_application_jwks,
357
+ signing_algorithm: oauth_application[oauth_applications_id_token_signed_response_alg_column] || oauth_jwt_algorithm,
358
+ encryption_algorithm: oauth_application[oauth_applications_id_token_encrypted_response_alg_column],
359
+ encryption_method: oauth_application[oauth_applications_id_token_encrypted_response_enc_column]
360
+ }
361
+ oauth_token[:id_token] = jwt_encode(id_token_claims, **params)
334
362
  end
335
363
 
336
364
  # aka fill_with_standard_claims
@@ -443,7 +471,7 @@ module Rodauth
443
471
 
444
472
  # Metadata
445
473
 
446
- def openid_configuration_body(path)
474
+ def openid_configuration_body(path = nil)
447
475
  metadata = oauth_server_metadata_body(path).select do |k, _|
448
476
  VALID_METADATA_KEYS.include?(k)
449
477
  end
@@ -461,7 +489,8 @@ module Rodauth
461
489
  scope_claims.unshift("auth_time") if last_account_login_at
462
490
 
463
491
  response_types_supported = metadata[:response_types_supported]
464
- if use_oauth_implicit_grant_type?
492
+
493
+ if metadata[:grant_types_supported].include?("implicit")
465
494
  response_types_supported += ["none", "id_token", "code token", "code id_token", "id_token token", "code id_token token"]
466
495
  end
467
496
 
@@ -503,7 +532,7 @@ module Rodauth
503
532
  response["Access-Control-Allow-Methods"] = "GET, OPTIONS"
504
533
  response["Access-Control-Max-Age"] = "3600"
505
534
  response.status = 200
506
- request.halt
535
+ return_response
507
536
  end
508
537
  end
509
538
  end