module Rodauth

  1. lib/generators/rodauth/oauth/install_generator.rb
  2. lib/generators/rodauth/oauth/views_generator.rb
  3. lib/rodauth/features/oauth_application_management.rb
  4. lib/rodauth/features/oauth_assertion_base.rb
  5. lib/rodauth/features/oauth_authorization_code_grant.rb
  6. lib/rodauth/features/oauth_authorize_base.rb
  7. lib/rodauth/features/oauth_base.rb
  8. lib/rodauth/features/oauth_client_credentials_grant.rb
  9. lib/rodauth/features/oauth_device_code_grant.rb
  10. lib/rodauth/features/oauth_dpop.rb
  11. lib/rodauth/features/oauth_dynamic_client_registration.rb
  12. lib/rodauth/features/oauth_grant_management.rb
  13. lib/rodauth/features/oauth_implicit_grant.rb
  14. lib/rodauth/features/oauth_jwt.rb
  15. lib/rodauth/features/oauth_jwt_base.rb
  16. lib/rodauth/features/oauth_jwt_bearer_grant.rb
  17. lib/rodauth/features/oauth_jwt_jwks.rb
  18. lib/rodauth/features/oauth_jwt_secured_authorization_request.rb
  19. lib/rodauth/features/oauth_jwt_secured_authorization_response_mode.rb
  20. lib/rodauth/features/oauth_management_base.rb
  21. lib/rodauth/features/oauth_pkce.rb
  22. lib/rodauth/features/oauth_pushed_authorization_request.rb
  23. lib/rodauth/features/oauth_resource_indicators.rb
  24. lib/rodauth/features/oauth_resource_server.rb
  25. lib/rodauth/features/oauth_saml_bearer_grant.rb
  26. lib/rodauth/features/oauth_tls_client_auth.rb
  27. lib/rodauth/features/oauth_token_introspection.rb
  28. lib/rodauth/features/oauth_token_revocation.rb
  29. lib/rodauth/features/oidc.rb
  30. lib/rodauth/features/oidc_backchannel_logout.rb
  31. lib/rodauth/features/oidc_dynamic_client_registration.rb
  32. lib/rodauth/features/oidc_frontchannel_logout.rb
  33. lib/rodauth/features/oidc_logout_base.rb
  34. lib/rodauth/features/oidc_rp_initiated_logout.rb
  35. lib/rodauth/features/oidc_self_issued.rb
  36. lib/rodauth/features/oidc_session_management.rb
  37. lib/rodauth/oauth.rb
  38. lib/rodauth/oauth/database_extensions.rb
  39. lib/rodauth/oauth/http_extensions.rb
  40. lib/rodauth/oauth/railtie.rb
  41. lib/rodauth/oauth/ttl_store.rb
  42. lib/rodauth/oauth/version.rb
  43. show all

:nocov:

Methods

Public Class

  1. included

Public Instance

  1. _before_register
  2. _do_authorize_code
  3. _do_authorize_token
  4. _generate_access_token
  5. _generate_jwt_access_token
  6. _generate_refresh_token
  7. _grant_with_access_token?
  8. _json_response_body
  9. _jwt_key
  10. _logout_response
  11. _oidc_logout_response
  12. _redirect_response_error
  13. accepts_json?
  14. account_from_bearer_assertion_subject
  15. account_from_jwt_bearer_assertion
  16. account_from_saml2_bearer_assertion
  17. active_sessions?
  18. allow_cors
  19. assertion_grant_type
  20. assertion_grant_type?
  21. auth_server_jwks_set
  22. authorization_encryption_alg_values_supported
  23. authorization_encryption_enc_values_supported
  24. authorization_required
  25. authorization_server_metadata
  26. authorization_server_url
  27. authorization_signing_alg_values_supported
  28. authorization_token
  29. authorize_response
  30. authorize_scopes
  31. before_authorize_route
  32. before_introspection_request
  33. check_csrf?
  34. check_valid_access_type?
  35. check_valid_approval_prompt?
  36. check_valid_grant_challenge?
  37. check_valid_no_fragment_uri?
  38. check_valid_response_type?
  39. check_valid_scopes?
  40. check_valid_uri?
  41. clear_session
  42. client_assertion_type
  43. client_assertion_type?
  44. client_certificate
  45. client_certificate_sans
  46. convert_to_boolean
  47. create_oauth_application
  48. create_oauth_grant
  49. create_oauth_grant_with_token
  50. create_token
  51. create_token_from_authorization_code
  52. create_token_from_token
  53. current_oauth_account
  54. current_oauth_application
  55. decode_access_token
  56. decode_request_object
  57. distinguished_name_match?
  58. do_authorize
  59. do_register
  60. dpop_bound_access_tokens_required?
  61. dpop_decode
  62. dpop_nonce_required
  63. dpop_use_nonce?
  64. fetch_access_token
  65. fetch_access_token_from_authorization_header
  66. fetch_dpop_token
  67. fill_with_account_claims
  68. form_post_error_response_html
  69. form_post_response_html
  70. generate_dpop_nonce
  71. generate_frontchannel_logout_urls
  72. generate_id_token
  73. generate_jti
  74. generate_logout_token
  75. generate_saml_settings
  76. generate_session_state
  77. generate_token
  78. generate_token_hash
  79. generate_user_code
  80. get_oidc_account_last_login_at
  81. grant_from_application?
  82. id_token_claims
  83. id_token_hash
  84. initialize_register_params
  85. introspection_request
  86. json_access_token_payload
  87. json_request?
  88. json_response_oauth_application
  89. json_response_success
  90. json_token_introspect_payload
  91. json_webfinger_payload
  92. jwk_export
  93. jwk_import
  94. jwk_key
  95. jwk_thumbprint
  96. jwks_set
  97. jwt_assertion
  98. jwt_claims
  99. jwt_decode
  100. jwt_decode_no_key
  101. jwt_decode_with_jwe
  102. jwt_encode
  103. jwt_encode_authorization_response_mode
  104. jwt_encode_with_jwe
  105. jwt_response_success
  106. jwt_subject
  107. key_to_jwk
  108. load_oauth_application_management_routes
  109. load_oauth_grant_management_routes
  110. load_oauth_server_metadata_route
  111. load_openid_configuration_route
  112. load_registration_client_uri_routes
  113. load_webfinger_route
  114. logout
  115. normalize_redirect_uri_for_comparison
  116. oauth_account_ds
  117. oauth_acr_values_supported
  118. oauth_application
  119. oauth_application_ds
  120. oauth_application_in_visited_sites
  121. oauth_application_jwks
  122. oauth_application_params
  123. oauth_application_path
  124. oauth_applications_path
  125. oauth_grant_by_refresh_token
  126. oauth_grant_by_refresh_token_ds
  127. oauth_grant_by_token
  128. oauth_grant_by_token_ds
  129. oauth_grant_path
  130. oauth_grant_types_supported
  131. oauth_grants_path
  132. oauth_grants_resource_owner_columns
  133. oauth_grants_unique_columns
  134. oauth_jwt_audience
  135. oauth_jwt_issuer
  136. oauth_jwt_jwe_algorithms_supported
  137. oauth_jwt_jwe_encryption_methods_supported
  138. oauth_management_pagination_link
  139. oauth_management_pagination_links
  140. oauth_oidc_session_management_salt
  141. oauth_response_modes_for_code_supported
  142. oauth_response_modes_for_token_supported
  143. oauth_response_modes_supported
  144. oauth_response_types_supported
  145. oauth_server_metadata_body
  146. oauth_token_endpoint_auth_methods_supported
  147. oauth_token_subject
  148. oauth_unique_id_generator
  149. oidc_authorize_on_prompt_none?
  150. oidc_grant_params
  151. openid_configuration_body
  152. parse_saml_assertion
  153. password_hash
  154. per_page_param
  155. perform_logout_requests
  156. post_configure
  157. private_jwk?
  158. proxy_get_param
  159. redirect_authorize_error
  160. redirect_logout_with_error
  161. redirect_response_error
  162. redirect_uri
  163. register_invalid_application_type_message
  164. register_invalid_client_metadata_message
  165. register_invalid_contacts_message
  166. register_invalid_jwks_param_message
  167. register_invalid_param_message
  168. register_invalid_response_type_for_grant_type_message
  169. register_invalid_response_type_message
  170. register_invalid_scopes_message
  171. register_invalid_uri_message
  172. register_oauth_invalid_grant_type_message
  173. register_required_param_message
  174. register_throw_json_response_error
  175. request_object_encryption_alg_values_supported
  176. request_object_encryption_enc_values_supported
  177. request_object_signing_alg_values_supported
  178. require_acr_value
  179. require_acr_value_phr
  180. require_acr_value_phrh
  181. require_authorizable_account
  182. require_oauth_application
  183. require_oauth_application_for_introspect
  184. require_oauth_application_from_account
  185. require_oauth_application_from_client_secret_basic
  186. require_oauth_application_from_client_secret_jwt
  187. require_oauth_application_from_client_secret_post
  188. require_oauth_application_from_jwt_bearer_assertion_issuer
  189. require_oauth_application_from_jwt_bearer_assertion_subject
  190. require_oauth_application_from_none
  191. require_oauth_application_from_private_key_jwt
  192. require_oauth_application_from_saml2_bearer_assertion_issuer
  193. require_oauth_application_from_saml2_bearer_assertion_subject
  194. require_oauth_authorization
  195. require_signed_request_object?
  196. requires_backchannel_logout_session?
  197. requires_frontchannel_logout_session?
  198. rescue_from_uniqueness_error
  199. resource_indicators
  200. resource_owner_identifier
  201. resource_owner_params
  202. resource_owner_params_from_jwt_claims
  203. response_error_params
  204. return_response
  205. revoke_oauth_grant
  206. scopes
  207. secret_hash
  208. secret_matches?
  209. session_id_in_claims
  210. set_client_secret
  211. should_set_oauth_application_in_visited_sites?
  212. should_set_sid_in_visited_sites?
  213. sid_in_visited_sites
  214. store_token
  215. supported_grant_type?
  216. supported_request_uri?
  217. supported_response_mode?
  218. supported_response_type?
  219. supports_auth_method?
  220. template_path
  221. throw_json_response_error
  222. translate
  223. try_acr_values
  224. try_approval_prompt
  225. try_prompt
  226. use_date_arithmetic?
  227. userinfo_encryption_alg_values_supported
  228. userinfo_encryption_enc_values_supported
  229. userinfo_signing_alg_values_supported
  230. valid_dpop_nonce?
  231. valid_dpop_proof_required
  232. valid_locked_oauth_grant
  233. valid_oauth_grant_ds
  234. validate_ath
  235. validate_authorize_params
  236. validate_client_registration_params
  237. validate_client_registration_response_type
  238. validate_dpop_jwt_claims
  239. validate_dpop_proof_usage
  240. validate_dpop_token
  241. validate_introspect_params
  242. validate_nonce
  243. validate_oauth_application_params
  244. validate_oidc_logout_params
  245. validate_par_params
  246. validate_pkce_challenge_params
  247. validate_revoke_params
  248. validate_token_params
  249. verify_access_token_headers
  250. verify_aud
  251. verify_dpop_jwt_headers
  252. verify_jti
  253. www_authenticate_header

Included modules

  1. OAuth::HTTPExtensions

Constants

ALLOWED_REQUEST_URI_CONTENT_TYPES = %w[application/jose application/oauth-authz-req+jwt].freeze  
APPLICATION_REQUIRED_PARAMS = %w[name scopes homepage_url redirect_uri client_secret].freeze  

Application

EMPTY_HASH = {}.freeze  
OAUTH_ACCESS_TYPES = %w[offline online].freeze  
OAUTH_APPROVAL_PROMPTS = %w[force auto].freeze  
OIDC_SCOPES_MAP = { "profile" => %i[name family_name given_name middle_name nickname preferred_username profile picture website gender birthdate zoneinfo locale updated_at].freeze, "email" => %i[email email_verified].freeze, "address" => %i[formatted street_address locality region postal_code country].freeze, "phone" => %i[phone_number phone_number_verified].freeze }.freeze  

openid.net/specs/openid-connect-core-1_0.html#StandardClaims

PROTECTED_APPLICATION_ATTRIBUTES = %w[account_id client_id].freeze  
REQUIRED_METADATA_KEYS = %i[ issuer authorization_endpoint token_endpoint jwks_uri response_types_supported subject_types_supported id_token_signing_alg_values_supported ].freeze  
SELF_ISSUED_DEFAULT_APPLICATION_PARAMS = { "scope" => "openid profile email address phone", "response_types" => ["id_token"], "subject_type" => "pairwise", "id_token_signed_response_alg" => "RS256", "request_object_signing_alg" => "RS256", "grant_types" => %w[implicit] }.freeze  
VALID_METADATA_KEYS = %i[ issuer authorization_endpoint end_session_endpoint backchannel_logout_session_supported token_endpoint userinfo_endpoint jwks_uri registration_endpoint scopes_supported response_types_supported response_modes_supported grant_types_supported acr_values_supported subject_types_supported id_token_signing_alg_values_supported id_token_encryption_alg_values_supported id_token_encryption_enc_values_supported userinfo_signing_alg_values_supported userinfo_encryption_alg_values_supported userinfo_encryption_enc_values_supported request_object_signing_alg_values_supported request_object_encryption_alg_values_supported request_object_encryption_enc_values_supported token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported display_values_supported claim_types_supported claims_supported service_documentation claims_locales_supported ui_locales_supported claims_parameter_supported request_parameter_supported request_uri_parameter_supported require_request_uri_registration op_policy_uri op_tos_uri check_session_iframe frontchannel_logout_supported frontchannel_logout_session_supported backchannel_logout_supported backchannel_logout_session_supported ].freeze  

Public Class methods

included(rodauth)
[show source]
    # File lib/rodauth/features/oauth_resource_indicators.rb
159 def self.included(rodauth)
160   super
161   rodauth.send(:include, IndicatorAuthorizationCodeGrant) if rodauth.features.include?(:oauth_authorization_code_grant)
162   rodauth.send(:include, IndicatorIntrospection) if rodauth.features.include?(:oauth_token_introspection)
163   rodauth.send(:include, IndicatorJwt) if rodauth.features.include?(:oauth_jwt)
164 end

Public Instance methods

_before_register()
[show source]
    # File lib/rodauth/features/oauth_dynamic_client_registration.rb
108 def _before_register
109   raise %{dynamic client registration requires authentication.
110     Override ´before_register` to perform it.
111     example:
112 
113       before_register do
114         account = _account_from_login(request.env["HTTP_X_USER_EMAIL"])
115         authorization_required unless account
116         @oauth_application_params[:account_id] = account[:id]
117       end
118   }
119 end
_do_authorize_code()
[show source]
   # File lib/rodauth/features/oauth_authorization_code_grant.rb
69 def _do_authorize_code
70   create_params = {
71     oauth_grants_type_column => "authorization_code",
72     **resource_owner_params
73   }
74 
75   { "code" => create_oauth_grant(create_params) }
76 end
_do_authorize_token(grant_params = {})
[show source]
   # File lib/rodauth/features/oauth_implicit_grant.rb
58 def _do_authorize_token(grant_params = {})
59   grant_params = {
60     oauth_grants_type_column => "implicit",
61     oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
62     oauth_grants_scopes_column => scopes,
63     **resource_owner_params
64   }.merge(grant_params)
65 
66   generate_token(grant_params, false)
67 end
_generate_access_token(params = {})
[show source]
    # File lib/rodauth/features/oauth_base.rb
494 def _generate_access_token(params = {})
495   token = oauth_unique_id_generator
496 
497   if oauth_grants_token_hash_column
498     params[oauth_grants_token_hash_column] = generate_token_hash(token)
499   else
500     params[oauth_grants_token_column] = token
501   end
502 
503   token
504 end
_generate_jwt_access_token(oauth_grant)
[show source]
    # File lib/rodauth/features/oauth_jwt.rb
 97 def _generate_jwt_access_token(oauth_grant)
 98   claims = jwt_claims(oauth_grant)
 99 
100   # one of the points of using jwt is avoiding database lookups, so we put here all relevant
101   # token data.
102   claims[:scope] = oauth_grant[oauth_grants_scopes_column]
103 
104   # RFC8725 section 3.11: Use Explicit Typing
105   # RFC9068 section 2.1 : The "typ" value used SHOULD be "at+jwt".
106   jwt_encode(claims, headers: { typ: "at+jwt" })
107 end
_generate_refresh_token(params)
[show source]
    # File lib/rodauth/features/oauth_base.rb
506 def _generate_refresh_token(params)
507   token = oauth_unique_id_generator
508 
509   if oauth_grants_refresh_token_hash_column
510     params[oauth_grants_refresh_token_hash_column] = generate_token_hash(token)
511   else
512     params[oauth_grants_refresh_token_column] = token
513   end
514 
515   token
516 end
_grant_with_access_token?(oauth_grant)
[show source]
    # File lib/rodauth/features/oauth_base.rb
518 def _grant_with_access_token?(oauth_grant)
519   if oauth_grants_token_hash_column
520     oauth_grant[oauth_grants_token_hash_column]
521   else
522     oauth_grant[oauth_grants_token_column]
523   end
524 end
_json_response_body(hash)
[show source]
    # File lib/rodauth/features/oauth_base.rb
836 def _json_response_body(hash)
837   return super if features.include?(:json)
838 
839   if request.respond_to?(:convert_to_json)
840     request.send(:convert_to_json, hash)
841   else
842     JSON.dump(hash)
843   end
844 end
_jwt_key()
[show source]
   # File lib/rodauth/features/oauth_jwt_base.rb
81 def _jwt_key
82   @_jwt_key ||= (oauth_application_jwks(oauth_application) if oauth_application)
83 end
_logout_response()
[show source]
   # File lib/rodauth/features/oidc_frontchannel_logout.rb
35 def _logout_response
36   visited_sites = @visited_sites
37 
38   return super unless visited_sites
39 
40   logout_urls = db[oauth_applications_table]
41                 .where(oauth_applications_client_id_column => visited_sites.map(&:first))
42                 .as_hash(oauth_applications_client_id_column, oauth_applications_frontchannel_logout_uri_column)
43 
44   return super if logout_urls.empty?
45 
46   generate_frontchannel_logout_urls(visited_sites, logout_urls)
47 
48   @frontchannel_logout_redirect = logout_redirect
49 
50   set_notice_flash logout_notice_flash
51   return_response frontchannel_logout_view
52 end
_oidc_logout_response()

overrides rp-initiate logout response

[show source]
   # File lib/rodauth/features/oidc_frontchannel_logout.rb
55 def _oidc_logout_response
56   visited_sites = @visited_sites
57 
58   return super unless visited_sites
59 
60   logout_urls = db[oauth_applications_table]
61                 .where(oauth_applications_client_id_column => visited_sites.map(&:first))
62                 .as_hash(oauth_applications_client_id_column, oauth_applications_frontchannel_logout_uri_column)
63 
64   return super if logout_urls.empty?
65 
66   generate_frontchannel_logout_urls(visited_sites, logout_urls)
67 
68   @frontchannel_logout_redirect = oidc_logout_redirect
69 
70   set_notice_flash logout_notice_flash
71   return_response frontchannel_logout_view
72 end
_redirect_response_error(redirect_url, params)
[show source]
    # File lib/rodauth/features/oauth_authorization_code_grant.rb
 96 def _redirect_response_error(redirect_url, params)
 97   response_mode = param_or_nil("response_mode") || oauth_response_mode
 98 
 99   case response_mode
100   when "form_post"
101     response["Content-Type"] = "text/html"
102     error_body = form_post_error_response_html(redirect_url) do
103       params.map do |name, value|
104         "<input type=\"hidden\" name=\"#{name}\" value=\"#{scope.h(value)}\" />"
105       end.join
106     end
107     response.write(error_body)
108     request.halt
109   else
110     super
111   end
112 end
accepts_json?()
[show source]
    # File lib/rodauth/features/oauth_base.rb
188 def accepts_json?
189   return true if only_json?
190 
191   (accept = request.env["HTTP_ACCEPT"]) && accept =~ json_request_regexp
192 end
account_from_bearer_assertion_subject(subject)
[show source]
   # File lib/rodauth/features/oauth_assertion_base.rb
43 def account_from_bearer_assertion_subject(subject)
44   __insert_or_do_nothing_and_return__(
45     db[accounts_table],
46     account_id_column,
47     [login_column],
48     login_column => subject
49   )
50 end
account_from_jwt_bearer_assertion(assertion)
[show source]
   # File lib/rodauth/features/oauth_jwt_bearer_grant.rb
75 def account_from_jwt_bearer_assertion(assertion)
76   claims = jwt_assertion(assertion)
77 
78   return unless claims
79 
80   account_from_bearer_assertion_subject(claims["sub"])
81 end
account_from_saml2_bearer_assertion(assertion)
[show source]
   # File lib/rodauth/features/oauth_saml_bearer_grant.rb
64 def account_from_saml2_bearer_assertion(assertion)
65   parse_saml_assertion(assertion)
66 
67   return unless @assertion
68 
69   account_from_bearer_assertion_subject(@assertion.nameid)
70 end
active_sessions?(session_id)
[show source]
   # File lib/rodauth/features/oidc_logout_base.rb
20 def active_sessions?(session_id)
21   !active_sessions_ds.where(active_sessions_session_id_column => session_id).empty?
22 end
allow_cors(request)
[show source]
    # File lib/rodauth/features/oidc.rb
839 def allow_cors(request)
840   return unless request.request_method == "OPTIONS"
841 
842   response["Access-Control-Allow-Origin"] = "*"
843   response["Access-Control-Allow-Methods"] = "GET, OPTIONS"
844   response["Access-Control-Max-Age"] = "3600"
845   response.status = 200
846   return_response
847 end
assertion_grant_type(grant_type = param("grant_type"))
[show source]
   # File lib/rodauth/features/oauth_assertion_base.rb
84 def assertion_grant_type(grant_type = param("grant_type"))
85   grant_type.delete_prefix("urn:ietf:params:oauth:grant-type:").tr("-", "_")
86 end
assertion_grant_type?(grant_type = param("grant_type"))
[show source]
   # File lib/rodauth/features/oauth_assertion_base.rb
76 def assertion_grant_type?(grant_type = param("grant_type"))
77   grant_type.start_with?("urn:ietf:params:oauth:grant-type:")
78 end
auth_server_jwks_set()

Resource Server only!

returns the jwks set from the authorization server.

[show source]
   # File lib/rodauth/features/oauth_jwt_base.rb
88 def auth_server_jwks_set
89   metadata = authorization_server_metadata
90 
91   return unless metadata && (jwks_uri = metadata[:jwks_uri])
92 
93   jwks_uri = URI(jwks_uri)
94 
95   http_request_with_cache(jwks_uri)
96 end
authorization_encryption_alg_values_supported()
[show source]
   # File lib/rodauth/features/oauth_jwt_secured_authorization_response_mode.rb
33 def authorization_encryption_alg_values_supported
34   oauth_jwt_jwe_algorithms_supported
35 end
authorization_encryption_enc_values_supported()
[show source]
   # File lib/rodauth/features/oauth_jwt_secured_authorization_response_mode.rb
37 def authorization_encryption_enc_values_supported
38   oauth_jwt_jwe_encryption_methods_supported
39 end
authorization_required()
[show source]
    # File lib/rodauth/features/oauth_authorize_base.rb
166 def authorization_required
167   if accepts_json?
168     throw_json_response_error(oauth_authorization_required_error_status, "invalid_client")
169   else
170     set_redirect_error_flash(require_authorization_error_flash)
171     redirect(authorize_path)
172   end
173 end
authorization_server_metadata()

Resource server mode

[show source]
    # File lib/rodauth/features/oauth_base.rb
873 def authorization_server_metadata
874   auth_url = URI(authorization_server_url).dup
875   auth_url.path = "/.well-known/oauth-authorization-server"
876 
877   http_request_with_cache(auth_url)
878 end
authorization_server_url()
[show source]
    # File lib/rodauth/features/oauth_base.rb
346 def authorization_server_url
347   base_url
348 end
authorization_signing_alg_values_supported()
[show source]
   # File lib/rodauth/features/oauth_jwt_secured_authorization_response_mode.rb
29 def authorization_signing_alg_values_supported
30   oauth_jwt_jws_algorithms_supported
31 end
authorization_token()
[show source]
    # File lib/rodauth/features/oauth_base.rb
260 def authorization_token
261   return @authorization_token if defined?(@authorization_token)
262 
263   # check if there is a token
264   access_token = fetch_access_token
265 
266   return unless access_token
267 
268   @authorization_token = oauth_grant_by_token(access_token)
269 end
authorize_response(params, mode)
[show source]
   # File lib/rodauth/features/oauth_authorization_code_grant.rb
78 def authorize_response(params, mode)
79   redirect_url = URI.parse(redirect_uri)
80   case mode
81   when "query"
82     params = [URI.encode_www_form(params)]
83     params << redirect_url.query if redirect_url.query
84     redirect_url.query = params.join("&")
85     redirect(redirect_url.to_s)
86   when "form_post"
87     inline_html = form_post_response_html(redirect_uri) do
88       params.map do |name, value|
89         "<input type=\"hidden\" name=\"#{scope.h(name)}\" value=\"#{scope.h(value)}\" />"
90       end.join
91     end
92     scope.view layout: false, inline: inline_html
93   end
94 end
authorize_scopes()
[show source]
   # File lib/rodauth/features/oauth_authorize_base.rb
70 def authorize_scopes
71   scopes || begin
72     oauth_application[oauth_applications_scopes_column].split(oauth_scope_separator)
73   end
74 end
before_authorize_route()
[show source]
    # File lib/rodauth/features/oidc.rb
239 def before_authorize_route
240   if (ui_locales = param_or_nil("ui_locales"))
241     ui_locales = ui_locales.split(" ").map(&:to_sym)
242     ui_locales &= ::I18n.available_locales
243 
244     ::I18n.locale = ui_locales.first unless ui_locales.empty?
245   end
246 
247   super
248 end
before_introspection_request(request)
[show source]
   # File lib/rodauth/features/oauth_resource_server.rb
57 def before_introspection_request(request); end
check_csrf?()
[show source]
   # File lib/rodauth/features/oauth_authorize_base.rb
61 def check_csrf?
62   case request.path
63   when authorize_path
64     only_json? ? false : super
65   else
66     super
67   end
68 end
check_valid_access_type?()
[show source]
    # File lib/rodauth/features/oauth_authorize_base.rb
113 def check_valid_access_type?
114   return true unless use_oauth_access_type?
115 
116   access_type = param_or_nil("access_type")
117   !access_type || OAUTH_ACCESS_TYPES.include?(access_type)
118 end
check_valid_approval_prompt?()
[show source]
    # File lib/rodauth/features/oauth_authorize_base.rb
120 def check_valid_approval_prompt?
121   return true unless use_oauth_access_type?
122 
123   approval_prompt = param_or_nil("approval_prompt")
124   !approval_prompt || OAUTH_APPROVAL_PROMPTS.include?(approval_prompt)
125 end
check_valid_grant_challenge?(grant, verifier)
[show source]
   # File lib/rodauth/features/oauth_pkce.rb
72 def check_valid_grant_challenge?(grant, verifier)
73   challenge = grant[oauth_grants_code_challenge_column]
74 
75   case grant[oauth_grants_code_challenge_method_column]
76   when "plain"
77     challenge == verifier
78   when "S256"
79     generated_challenge = Base64.urlsafe_encode64(Digest::SHA256.digest(verifier), padding: false)
80 
81     challenge == generated_challenge
82   else
83     redirect_response_error("unsupported_transform_algorithm")
84   end
85 end
check_valid_no_fragment_uri?(uri)
[show source]
    # File lib/rodauth/features/oauth_base.rb
867 def check_valid_no_fragment_uri?(uri)
868   check_valid_uri?(uri) && URI.parse(uri).fragment.nil?
869 end
check_valid_response_type?()
[show source]
    # File lib/rodauth/features/oauth_authorization_code_grant.rb
153 def check_valid_response_type?
154   response_type = param_or_nil("response_type")
155 
156   response_type == "code" || response_type == "none" || super
157 end
check_valid_scopes?(scp = scopes)
[show source]
    # File lib/rodauth/features/oauth_authorize_base.rb
105 def check_valid_scopes?(scp = scopes)
106   super(scp - %w[offline_access])
107 end
check_valid_uri?(uri)
[show source]
    # File lib/rodauth/features/oauth_base.rb
863 def check_valid_uri?(uri)
864   URI::DEFAULT_PARSER.make_regexp(oauth_valid_uri_schemes).match?(uri)
865 end
clear_session()
[show source]
   # File lib/rodauth/features/oidc_session_management.rb
30 def clear_session
31   super
32 
33   # update user agent state in the process
34   # TODO: dangerous if this gets overidden by the user
35 
36   user_agent_state_cookie_opts = Hash[oauth_oidc_user_agent_state_cookie_options]
37   user_agent_state_cookie_opts[:value] = oauth_unique_id_generator
38   user_agent_state_cookie_opts[:secure] = true
39   if oauth_oidc_user_agent_state_cookie_expires_in
40     user_agent_state_cookie_opts[:expires] = convert_timestamp(Time.now + oauth_oidc_user_agent_state_cookie_expires_in)
41   end
42   ::Rack::Utils.set_cookie_header!(response.headers, oauth_oidc_user_agent_state_cookie_key, user_agent_state_cookie_opts)
43 end
client_assertion_type(assertion_type = param("client_assertion_type"))
[show source]
   # File lib/rodauth/features/oauth_assertion_base.rb
88 def client_assertion_type(assertion_type = param("client_assertion_type"))
89   assertion_type.delete_prefix("urn:ietf:params:oauth:client-assertion-type:").tr("-", "_")
90 end
client_assertion_type?(client_assertion_type = param("client_assertion_type"))
[show source]
   # File lib/rodauth/features/oauth_assertion_base.rb
80 def client_assertion_type?(client_assertion_type = param("client_assertion_type"))
81   client_assertion_type.start_with?("urn:ietf:params:oauth:client-assertion-type:")
82 end
client_certificate()
[show source]
    # File lib/rodauth/features/oauth_tls_client_auth.rb
126 def client_certificate
127   return @client_certificate if defined?(@client_certificate)
128 
129   unless (pem_cert = request.env["SSL_CLIENT_CERT"] || request.env["HTTP_SSL_CLIENT_CERT"] || request.env["HTTP_X_SSL_CLIENT_CERT"])
130     return
131   end
132 
133   return if pem_cert.empty?
134 
135   @certificate = OpenSSL::X509::Certificate.new(pem_cert)
136 end
client_certificate_sans()
[show source]
    # File lib/rodauth/features/oauth_tls_client_auth.rb
138 def client_certificate_sans
139   return @client_certificate_sans if defined?(@client_certificate_sans)
140 
141   @client_certificate_sans = begin
142     return [] unless client_certificate
143 
144     san = client_certificate.extensions.find { |ext| ext.oid == "subjectAltName" }
145 
146     return [] unless san
147 
148     ostr = OpenSSL::ASN1.decode(san.to_der).value.last
149 
150     sans = OpenSSL::ASN1.decode(ostr.value)
151 
152     return [] unless sans
153 
154     sans.value
155   end
156 end
convert_to_boolean(key, value)
[show source]
    # File lib/rodauth/features/oauth_dynamic_client_registration.rb
389 def convert_to_boolean(key, value)
390   case value
391   when true, false then value
392   when "true" then true
393   when "false" then false
394   else
395     register_throw_json_response_error(
396       "invalid_client_metadata",
397       register_invalid_param_message(key)
398     )
399   end
400 end
create_oauth_application()
[show source]
    # File lib/rodauth/features/oauth_application_management.rb
184 def create_oauth_application
185   create_params = {
186     oauth_applications_account_id_column => account_id,
187     oauth_applications_name_column => oauth_application_params[oauth_application_name_param],
188     oauth_applications_description_column => oauth_application_params[oauth_application_description_param],
189     oauth_applications_scopes_column => oauth_application_params[oauth_application_scopes_param],
190     oauth_applications_homepage_url_column => oauth_application_params[oauth_application_homepage_url_param]
191   }
192 
193   redirect_uris = oauth_application_params[oauth_application_redirect_uri_param]
194   redirect_uris = redirect_uris.to_a.reject(&:empty?).join(" ") if redirect_uris.respond_to?(:each)
195   create_params[oauth_applications_redirect_uri_column] = redirect_uris unless redirect_uris.empty?
196 
197   # set client ID/secret pairs
198   set_client_secret(create_params, oauth_application_params[oauth_application_client_secret_param])
199 
200   if create_params[oauth_applications_scopes_column]
201     create_params[oauth_applications_scopes_column] = create_params[oauth_applications_scopes_column].join(oauth_scope_separator)
202   end
203 
204   rescue_from_uniqueness_error do
205     create_params[oauth_applications_client_id_column] = oauth_unique_id_generator
206     db[oauth_applications_table].insert(create_params)
207   end
208 end
create_oauth_grant(create_params = {})
[show source]
    # File lib/rodauth/features/oauth_authorize_base.rb
188 def create_oauth_grant(create_params = {})
189   create_params[oauth_grants_oauth_application_id_column] ||= oauth_application[oauth_applications_id_column]
190   create_params[oauth_grants_redirect_uri_column] ||= redirect_uri
191   create_params[oauth_grants_expires_in_column] ||= Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_grant_expires_in)
192   create_params[oauth_grants_scopes_column] ||= scopes.join(oauth_scope_separator)
193 
194   if use_oauth_access_type? && (access_type = param_or_nil("access_type"))
195     create_params[oauth_grants_access_type_column] = access_type
196   end
197 
198   ds = db[oauth_grants_table]
199 
200   create_params[oauth_grants_code_column] = oauth_unique_id_generator
201 
202   if oauth_reuse_access_token
203     unique_conds = Hash[oauth_grants_unique_columns.map { |column| [column, create_params[column]] }]
204     valid_grant = valid_oauth_grant_ds(unique_conds).select(oauth_grants_id_column).first
205     if valid_grant
206       create_params[oauth_grants_id_column] = valid_grant[oauth_grants_id_column]
207       rescue_from_uniqueness_error do
208         __insert_or_update_and_return__(
209           ds,
210           oauth_grants_id_column,
211           [oauth_grants_id_column],
212           create_params
213         )
214       end
215       return create_params[oauth_grants_code_column]
216     end
217   end
218 
219   rescue_from_uniqueness_error do
220     if __one_oauth_token_per_account
221       __insert_or_update_and_return__(
222         ds,
223         oauth_grants_id_column,
224         oauth_grants_unique_columns,
225         create_params,
226         nil,
227         {
228           oauth_grants_expires_in_column => Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_grant_expires_in),
229           oauth_grants_revoked_at_column => nil
230         }
231       )
232     else
233       __insert_and_return__(ds, oauth_grants_id_column, create_params)
234     end
235   end
236   create_params[oauth_grants_code_column]
237 end
create_oauth_grant_with_token(create_params = {})
[show source]
    # File lib/rodauth/features/oidc.rb
487 def create_oauth_grant_with_token(create_params = {})
488   create_params.merge!(resource_owner_params)
489   create_params[oauth_grants_type_column] = "hybrid"
490   create_params[oauth_grants_expires_in_column] = Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_access_token_expires_in)
491   authorization_code = create_oauth_grant(create_params)
492   access_token = if oauth_jwt_access_tokens
493                    _generate_jwt_access_token(create_params)
494                  else
495                    oauth_grant = valid_oauth_grant_ds.where(oauth_grants_code_column => authorization_code).first
496                    _generate_access_token(oauth_grant)
497                  end
498 
499   json_access_token_payload(oauth_grants_token_column => access_token).merge("code" => authorization_code)
500 end
create_token(grant_type)
[show source]
   # File lib/rodauth/features/oauth_assertion_base.rb
52 def create_token(grant_type)
53   return super unless assertion_grant_type?(grant_type) && supported_grant_type?(grant_type)
54 
55   account = __send__(:"account_from_#{assertion_grant_type}_assertion", param("assertion"))
56 
57   redirect_response_error("invalid_grant") unless account
58 
59   grant_scopes = if param_or_nil("scope")
60                    redirect_response_error("invalid_scope") unless check_valid_scopes?
61                    scopes
62                  else
63                    @oauth_application[oauth_applications_scopes_column]
64                  end
65 
66   grant_params = {
67     oauth_grants_type_column => grant_type,
68     oauth_grants_account_id_column => account[account_id_column],
69     oauth_grants_oauth_application_id_column => @oauth_application[oauth_applications_id_column],
70     oauth_grants_scopes_column => grant_scopes
71   }
72 
73   generate_token(grant_params, false)
74 end
create_token_from_authorization_code(grant_params, should_generate_refresh_token = !use_oauth_access_type?, oauth_grant: nil)
[show source]
    # File lib/rodauth/features/oauth_authorize_base.rb
179 def create_token_from_authorization_code(grant_params, should_generate_refresh_token = !use_oauth_access_type?, oauth_grant: nil)
180   # fetch oauth grant
181   oauth_grant ||= valid_locked_oauth_grant(grant_params)
182 
183   should_generate_refresh_token ||= oauth_grant[oauth_grants_access_type_column] == "offline"
184 
185   generate_token(oauth_grant, should_generate_refresh_token)
186 end
create_token_from_token(oauth_grant, update_params)
[show source]
    # File lib/rodauth/features/oauth_base.rb
695 def create_token_from_token(oauth_grant, update_params)
696   redirect_response_error("invalid_grant") unless grant_from_application?(oauth_grant, oauth_application)
697 
698   rescue_from_uniqueness_error do
699     oauth_grants_ds = db[oauth_grants_table].where(oauth_grants_id_column => oauth_grant[oauth_grants_id_column])
700     access_token = _generate_access_token(update_params)
701     oauth_grant = __update_and_return__(oauth_grants_ds, update_params)
702 
703     oauth_grant[oauth_grants_token_column] = access_token
704     oauth_grant
705   end
706 end
current_oauth_account()
[show source]
    # File lib/rodauth/features/oauth_base.rb
176 def current_oauth_account
177   account_id = authorization_token[oauth_grants_account_id_column]
178 
179   return unless account_id
180 
181   oauth_account_ds(account_id).first
182 end
current_oauth_application()
[show source]
    # File lib/rodauth/features/oauth_base.rb
184 def current_oauth_application
185   oauth_application_ds(authorization_token[oauth_grants_oauth_application_id_column]).first
186 end
decode_access_token(access_token = fetch_access_token)
[show source]
   # File lib/rodauth/features/oauth_jwt.rb
63 def decode_access_token(access_token = fetch_access_token)
64   return unless access_token
65 
66   jwt_claims = jwt_decode(access_token, verify_headers: method(:verify_access_token_headers))
67 
68   return unless jwt_claims
69 
70   return unless jwt_claims["sub"]
71 
72   return unless jwt_claims["aud"]
73 
74   jwt_claims
75 end
decode_request_object(request_object)
[show source]
    # File lib/rodauth/features/oauth_jwt_secured_authorization_request.rb
103 def decode_request_object(request_object)
104   request_sig_enc_opts = {
105     jws_algorithm: oauth_application[oauth_applications_request_object_signing_alg_column],
106     jws_encryption_algorithm: oauth_application[oauth_applications_request_object_encryption_alg_column],
107     jws_encryption_method: oauth_application[oauth_applications_request_object_encryption_enc_column]
108   }.compact
109 
110   request_sig_enc_opts[:jws_algorithm] ||= "none" if oauth_request_object_signing_alg_allow_none
111 
112   if request_sig_enc_opts[:jws_algorithm] == "none"
113     redirect_response_error("invalid_request_object") if require_signed_request_object?
114 
115     jwks = nil
116   elsif (jwks = oauth_application_jwks(oauth_application))
117     jwks = JSON.parse(jwks, symbolize_names: true) if jwks.is_a?(String)
118   else
119     redirect_response_error("invalid_request_object")
120   end
121 
122   claims = jwt_decode(request_object,
123                       jwks: jwks,
124                       verify_jti: false,
125                       verify_iss: false,
126                       verify_aud: false,
127                       **request_sig_enc_opts)
128 
129   redirect_response_error("invalid_request_object") unless claims
130 
131   claims
132 end
distinguished_name_match?(sub1, sub2)
[show source]
    # File lib/rodauth/features/oauth_tls_client_auth.rb
158 def distinguished_name_match?(sub1, sub2)
159   sub1 = OpenSSL::X509::Name.parse(sub1) if sub1.is_a?(String)
160   sub2 = OpenSSL::X509::Name.parse(sub2) if sub2.is_a?(String)
161   # OpenSSL::X509::Name#cp calls X509_NAME_cmp via openssl.
162   # https://www.openssl.org/docs/manmaster/man3/X509_NAME_cmp.html
163   # This procedure adheres to the matching rules for Distinguished Names (DN) given in
164   # RFC 4517 section 4.2.15 and RFC 5280 section 7.1.
165   sub1.cmp(sub2).zero?
166 end
do_authorize(response_params = {}, response_mode = param_or_nil("response_mode"))
[show source]
   # File lib/rodauth/features/oauth_authorization_code_grant.rb
50 def do_authorize(response_params = {}, response_mode = param_or_nil("response_mode"))
51   response_mode ||= oauth_response_mode
52 
53   redirect_response_error("invalid_request") unless response_mode.nil? || supported_response_mode?(response_mode)
54 
55   response_type = param_or_nil("response_type")
56 
57   redirect_response_error("invalid_request") unless response_type.nil? || supported_response_type?(response_type)
58 
59   case response_type
60   when "code", nil
61     response_params.replace(_do_authorize_code)
62   end
63 
64   response_params["state"] = param("state") if param_or_nil("state")
65 
66   [response_params, response_mode]
67 end
do_register(return_params = request.params.dup)
[show source]
    # File lib/rodauth/features/oauth_dynamic_client_registration.rb
283 def do_register(return_params = request.params.dup)
284   applications_ds = db[oauth_applications_table]
285   application_columns = applications_ds.columns
286 
287   # set defaults
288   create_params = @oauth_application_params
289 
290   # If omitted, an authorization server MAY register a client with a default set of scopes
291   create_params[oauth_applications_scopes_column] ||= return_params["scopes"] = oauth_application_scopes.join(" ")
292 
293   # https://datatracker.ietf.org/doc/html/rfc7591#section-2
294   if create_params[oauth_applications_grant_types_column] ||= begin
295     # If omitted, the default behavior is that the client will use only the "authorization_code" Grant Type.
296     return_params["grant_types"] = %w[authorization_code] # rubocop:disable Lint/AssignmentInCondition
297     "authorization_code"
298   end
299     create_params[oauth_applications_token_endpoint_auth_method_column] ||= begin
300       # If unspecified or omitted, the default is "client_secret_basic", denoting the HTTP Basic
301       # authentication scheme as specified in Section 2.3.1 of OAuth 2.0.
302       return_params["token_endpoint_auth_method"] =
303         "client_secret_basic"
304       "client_secret_basic"
305     end
306   end
307   create_params[oauth_applications_response_types_column] ||= begin
308     # If omitted, the default is that the client will use only the "code" response type.
309     return_params["response_types"] = %w[code]
310     "code"
311   end
312   rescue_from_uniqueness_error do
313     initialize_register_params(create_params, return_params)
314     create_params.delete_if { |k, _| !application_columns.include?(k) }
315     applications_ds.insert(create_params)
316   end
317 
318   return_params
319 end
dpop_bound_access_tokens_required?()
[show source]
    # File lib/rodauth/features/oauth_dpop.rb
305 def dpop_bound_access_tokens_required?
306   oauth_dpop_bound_access_tokens || (oauth_application && oauth_application[oauth_applications_dpop_bound_access_tokens_column])
307 end
dpop_decode(dpop)
[show source]
    # File lib/rodauth/features/oauth_dpop.rb
153 def dpop_decode(dpop)
154   # decode first without verifying!
155   _, headers = jwt_decode_no_key(dpop)
156 
157   redirect_response_error("invalid_dpop_proof") unless verify_dpop_jwt_headers(headers)
158 
159   dpop_jwk = headers["jwk"]
160 
161   jwt_decode(
162     dpop,
163     jws_key: jwk_key(dpop_jwk),
164     jws_algorithm: headers["alg"],
165     verify_iss: false,
166     verify_aud: false,
167     verify_jti: false
168   )
169 end
dpop_nonce_required(dpop_claims)
[show source]
    # File lib/rodauth/features/oauth_dpop.rb
322 def dpop_nonce_required(dpop_claims)
323   response["DPoP-Nonce"] = generate_dpop_nonce(dpop_claims)
324 
325   if @dpop_access_token
326     # protected resource access
327     throw_json_response_error(401, "use_dpop_nonce")
328   else
329     redirect_response_error("use_dpop_nonce")
330   end
331 end
dpop_use_nonce?()
[show source]
    # File lib/rodauth/features/oauth_dpop.rb
309 def dpop_use_nonce?
310   oauth_dpop_use_nonce || (oauth_application && oauth_application[oauth_applications_dpop_bound_access_tokens_column])
311 end
fetch_access_token()
[show source]
    # File lib/rodauth/features/oauth_base.rb
233 def fetch_access_token
234   if (token = request.params["access_token"])
235     if request.post? && !(request.content_type.start_with?("application/x-www-form-urlencoded") &&
236                     request.params.size == 1)
237       return
238     end
239   else
240     token = fetch_access_token_from_authorization_header
241   end
242 
243   return if token.nil? || token.empty?
244 
245   token
246 end
fetch_access_token_from_authorization_header(token_type = oauth_token_type)
[show source]
    # File lib/rodauth/features/oauth_base.rb
248 def fetch_access_token_from_authorization_header(token_type = oauth_token_type)
249   value = request.env["HTTP_AUTHORIZATION"]
250 
251   return unless value && !value.empty?
252 
253   scheme, token = value.split(" ", 2)
254 
255   return unless scheme.downcase == token_type
256 
257   token
258 end
fetch_dpop_token()
[show source]
    # File lib/rodauth/features/oauth_dpop.rb
294 def fetch_dpop_token
295   dpop = request.env["HTTP_DPOP"]
296 
297   return if dpop.nil? || dpop.empty?
298 
299   # 4.3.1 - There is not more than one DPoP HTTP request header field.
300   redirect_response_error("multiple_dpop_proofs") if dpop.split(";").size > 1
301 
302   dpop
303 end
fill_with_account_claims(claims, account, scopes, claims_locales)

aka fill_with_standard_claims

[show source]
    # File lib/rodauth/features/oidc.rb
581 def fill_with_account_claims(claims, account, scopes, claims_locales)
582   additional_claims_info = {}
583 
584   scopes_by_claim = scopes.each_with_object({}) do |scope, by_oidc|
585     next if scope == "openid"
586 
587     if scope.is_a?(Array)
588       # essential claims
589       param, additional_info = scope
590 
591       param = param.to_sym
592 
593       oidc, = OIDC_SCOPES_MAP.find do |_, oidc_scopes|
594         oidc_scopes.include?(param)
595       end || param.to_s
596 
597       param = nil if oidc == param.to_s
598 
599       additional_claims_info[param] = additional_info
600     else
601 
602       oidc, param = scope.split(".", 2)
603 
604       param = param.to_sym if param
605     end
606 
607     by_oidc[oidc] ||= []
608 
609     by_oidc[oidc] << param.to_sym if param
610   end
611 
612   oidc_scopes, additional_scopes = scopes_by_claim.keys.partition { |key| OIDC_SCOPES_MAP.key?(key) }
613 
614   claims_locales = claims_locales.split(" ").map(&:to_sym) if claims_locales
615 
616   unless oidc_scopes.empty?
617     if respond_to?(:get_oidc_param)
618       get_oidc_param = proxy_get_param(:get_oidc_param, claims, claims_locales, additional_claims_info)
619 
620       oidc_scopes.each do |scope|
621         scope_claims = claims
622         params = scopes_by_claim[scope]
623         params = params.empty? ? OIDC_SCOPES_MAP[scope] : (OIDC_SCOPES_MAP[scope] & params)
624 
625         scope_claims = (claims["address"] = {}) if scope == "address"
626 
627         params.each do |param|
628           get_oidc_param[account, param, scope_claims]
629         end
630       end
631     else
632       warn "`get_oidc_param(account, claim)` must be implemented to use oidc scopes."
633     end
634   end
635 
636   return if additional_scopes.empty?
637 
638   if respond_to?(:get_additional_param)
639     get_additional_param = proxy_get_param(:get_additional_param, claims, claims_locales, additional_claims_info)
640 
641     additional_scopes.each do |scope|
642       get_additional_param[account, scope.to_sym]
643     end
644   else
645     warn "`get_additional_param(account, claim)` must be implemented to use oidc scopes."
646   end
647 end
form_post_error_response_html(url)
[show source]
    # File lib/rodauth/features/oauth_authorization_code_grant.rb
128     def form_post_error_response_html(url)
129       <<-FORM
130         <html>
131           <head><title></title></head>
132           <body onload="javascript:document.forms[0].submit()">
133             <form method="post" action="#{url}">
134               #{yield}
135             </form>
136           </body>
137         </html>
138       FORM
139     end
form_post_response_html(url)
[show source]
    # File lib/rodauth/features/oauth_authorization_code_grant.rb
114     def form_post_response_html(url)
115       <<-FORM
116         <html>
117           <head><title>Authorized</title></head>
118           <body onload="javascript:document.forms[0].submit()">
119             <form method="post" action="#{url}">
120               #{yield}
121               <input type="submit" class="btn btn-outline-primary" value="#{scope.h(oauth_authorize_post_button)}"/>
122             </form>
123           </body>
124         </html>
125       FORM
126     end
generate_dpop_nonce(dpop_claims)

Nonce

[show source]
    # File lib/rodauth/features/oauth_dpop.rb
357 def generate_dpop_nonce(dpop_claims)
358   issued_at = Time.now.to_i
359 
360   aud = "#{dpop_claims['htm']}:#{dpop_claims['htu']}"
361 
362   nonce_claims = {
363     iss: oauth_jwt_issuer,
364     iat: issued_at,
365     exp: issued_at + oauth_dpop_nonce_expires_in,
366     aud: aud
367   }
368 
369   jwt_encode(nonce_claims)
370 end
generate_frontchannel_logout_urls(visited_sites, logout_urls)
[show source]
    # File lib/rodauth/features/oidc_frontchannel_logout.rb
 76 def generate_frontchannel_logout_urls(visited_sites, logout_urls)
 77   @frontchannel_logout_urls = logout_urls.flat_map do |client_id, logout_url|
 78     next unless logout_url
 79 
 80     sids = visited_sites.select { |cid, _| cid == client_id }.map(&:last)
 81 
 82     sids.map do |sid|
 83       logout_url = URI(logout_url)
 84 
 85       if sid
 86         query = logout_url.query
 87         query = if query
 88                   URI.decode_www_form(query)
 89                 else
 90                   []
 91                 end
 92         query << ["iss", oauth_jwt_issuer]
 93         query << ["sid", sid]
 94         logout_url.query = URI.encode_www_form(query)
 95       end
 96 
 97       logout_url
 98     end
 99   end.compact
100 end
generate_id_token(oauth_grant, include_claims = false)
[show source]
    # File lib/rodauth/features/oidc.rb
508 def generate_id_token(oauth_grant, include_claims = false)
509   oauth_scopes = oauth_grant[oauth_grants_scopes_column].split(oauth_scope_separator)
510 
511   return unless oauth_scopes.include?("openid")
512 
513   signing_algorithm = oauth_application[oauth_applications_id_token_signed_response_alg_column] ||
514                       oauth_jwt_keys.keys.first
515 
516   id_claims = id_token_claims(oauth_grant, signing_algorithm)
517 
518   account = db[accounts_table].where(account_id_column => oauth_grant[oauth_grants_account_id_column]).first
519 
520   # this should never happen!
521   # a newly minted oauth token from a grant should have been assigned to an account
522   # who just authorized its generation.
523   return unless account
524 
525   if (claims = oauth_grant[oauth_grants_claims_column])
526     claims = JSON.parse(claims)
527     if (id_token_essential_claims = claims["id_token"])
528       oauth_scopes |= id_token_essential_claims.to_a
529 
530       include_claims = true
531     end
532   end
533 
534   # OpenID Connect Core 1.0's 5.4 Requesting Claims using Scope Values:
535   # If standard claims (profile, email, etc) are requested as scope values in the Authorization Request,
536   # include in the response.
537   include_claims ||= (OIDC_SCOPES_MAP.keys & oauth_scopes).any?
538 
539   # However, when no Access Token is issued (which is the case for the response_type value id_token),
540   # the resulting Claims are returned in the ID Token.
541   fill_with_account_claims(id_claims, account, oauth_scopes, param_or_nil("claims_locales")) if include_claims
542 
543   params = {
544     jwks: oauth_application_jwks(oauth_application),
545     signing_algorithm: signing_algorithm,
546     encryption_algorithm: oauth_application[oauth_applications_id_token_encrypted_response_alg_column],
547     encryption_method: oauth_application[oauth_applications_id_token_encrypted_response_enc_column],
548 
549     # Not officially part of the spec, but some providers follow this convention.
550     # This is useful for distinguishing between ID Tokens and JWT Access Tokens.
551     headers: { typ: "id_token+jwt" }
552   }.compact
553 
554   oauth_grant[:id_token] = jwt_encode(id_claims, **params)
555 end
generate_jti(payload)
[show source]
    # File lib/rodauth/features/oauth_jwt_base.rb
 98 def generate_jti(payload)
 99   # Use the key and iat to create a unique key per request to prevent replay attacks
100   jti_raw = [
101     payload[:aud] || payload["aud"],
102     payload[:iat] || payload["iat"]
103   ].join(":").to_s
104   Digest::SHA256.hexdigest(jti_raw)
105 end
generate_logout_token(oauth_application, sid)
[show source]
   # File lib/rodauth/features/oidc_backchannel_logout.rb
50 def generate_logout_token(oauth_application, sid)
51   issued_at = Time.now.to_i
52 
53   logout_claims = {
54     iss: oauth_jwt_issuer, # issuer
55     iat: issued_at, # issued at
56     exp: issued_at + oauth_logout_token_expires_in,
57     aud: oauth_application[oauth_applications_client_id_column],
58     events: {
59       "http://schemas.openid.net/event/backchannel-logout": {}
60     }
61   }
62 
63   logout_claims[:sid] = sid if sid
64 
65   signing_algorithm = oauth_application[oauth_applications_id_token_signed_response_alg_column] ||
66                       oauth_jwt_keys.keys.first
67 
68   params = {
69     jwks: oauth_application_jwks(oauth_application),
70     headers: { typ: "logout+jwt" },
71     signing_algorithm: signing_algorithm,
72     encryption_algorithm: oauth_application[oauth_applications_id_token_encrypted_response_alg_column],
73     encryption_method: oauth_application[oauth_applications_id_token_encrypted_response_enc_column]
74   }.compact
75 
76   jwt_encode(logout_claims, **params)
77 end
generate_saml_settings(saml_settings)
[show source]
   # File lib/rodauth/features/oauth_saml_bearer_grant.rb
72 def generate_saml_settings(saml_settings)
73   settings = OneLogin::RubySaml::Settings.new
74 
75   # issuer
76   settings.idp_entity_id = saml_settings[oauth_saml_settings_issuer_column]
77 
78   # audience
79   settings.sp_entity_id = saml_settings[oauth_saml_settings_audience_column] || token_url
80 
81   # recipient
82   settings.assertion_consumer_service_url = token_url
83 
84   settings.idp_cert = saml_settings[oauth_saml_settings_idp_cert_column]
85   settings.idp_cert_fingerprint = saml_settings[oauth_saml_settings_idp_cert_fingerprint_column]
86   settings.idp_cert_fingerprint_algorithm = saml_settings[oauth_saml_settings_idp_cert_fingerprint_algorithm_column]
87 
88   if settings.idp_cert
89     check_idp_cert_expiration = saml_settings[oauth_saml_settings_idp_cert_check_expiration_column]
90     check_idp_cert_expiration = oauth_saml_idp_cert_check_expiration if check_idp_cert_expiration.nil?
91     settings.security[:check_idp_cert_expiration] = check_idp_cert_expiration
92   end
93   settings.security[:strict_audience_validation] = true
94   settings.security[:want_name_id] = true
95 
96   settings.name_identifier_format = saml_settings[oauth_saml_settings_name_identifier_format_column] ||
97                                     oauth_saml_name_identifier_format
98   settings
99 end
generate_session_state()
[show source]
   # File lib/rodauth/features/oidc_session_management.rb
64 def generate_session_state
65   salt = oauth_oidc_session_management_salt
66 
67   uri = URI(redirect_uri)
68   origin = if uri.respond_to?(:origin)
69              uri.origin
70            else
71              # TODO: remove when not supporting uri < 0.11
72              "#{uri.scheme}://#{uri.host}#{":#{uri.port}" if uri.port != uri.default_port}"
73            end
74   session_id = "#{oauth_application[oauth_applications_client_id_column]} " \
75                "#{origin} " \
76                "#{request.cookies[oauth_oidc_user_agent_state_cookie_key]} #{salt}"
77 
78   "#{Digest::SHA256.hexdigest(session_id)}.#{salt}"
79 end
generate_token(grant_params = {}, should_generate_refresh_token = true)
[show source]
    # File lib/rodauth/features/oauth_base.rb
464 def generate_token(grant_params = {}, should_generate_refresh_token = true)
465   if grant_params[oauth_grants_id_column] && oauth_reuse_access_token &&
466      (
467        if oauth_grants_token_hash_column
468          grant_params[oauth_grants_token_hash_column]
469        else
470          grant_params[oauth_grants_token_column]
471        end
472      )
473     return grant_params
474   end
475 
476   update_params = {
477     oauth_grants_expires_in_column => Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_access_token_expires_in),
478     oauth_grants_code_column => nil
479   }
480 
481   rescue_from_uniqueness_error do
482     access_token = _generate_access_token(update_params)
483     refresh_token = _generate_refresh_token(update_params) if should_generate_refresh_token
484     oauth_grant = store_token(grant_params, update_params)
485 
486     return unless oauth_grant
487 
488     oauth_grant[oauth_grants_token_column] = access_token
489     oauth_grant[oauth_grants_refresh_token_column] = refresh_token if refresh_token
490     oauth_grant
491   end
492 end
generate_token_hash(token)
[show source]
    # File lib/rodauth/features/oauth_base.rb
450 def generate_token_hash(token)
451   Base64.urlsafe_encode64(Digest::SHA256.digest(token))
452 end
generate_user_code()
[show source]
    # File lib/rodauth/features/oauth_device_code_grant.rb
120 def generate_user_code
121   user_code_size = oauth_device_code_grant_user_code_size
122   SecureRandom.random_number(36**user_code_size)
123               .to_s(36) # 0 to 9, a to z
124               .upcase
125               .rjust(user_code_size, "0")
126 end
get_oidc_account_last_login_at(account_id)
[show source]
    # File lib/rodauth/features/oidc.rb
350 def get_oidc_account_last_login_at(account_id)
351   return get_activity_timestamp(account_id, account_activity_last_activity_column) if features.include?(:account_expiration)
352 
353   # active sessions based
354   ds = db[active_sessions_table].where(active_sessions_account_id_column => account_id)
355 
356   ds = ds.order(Sequel.desc(active_sessions_created_at_column))
357 
358   convert_timestamp(ds.get(active_sessions_created_at_column))
359 end
grant_from_application?(oauth_grant, oauth_application)
[show source]
    # File lib/rodauth/features/oauth_base.rb
454 def grant_from_application?(oauth_grant, oauth_application)
455   oauth_grant[oauth_grants_oauth_application_id_column] == oauth_application[oauth_applications_id_column]
456 end
id_token_claims(oauth_grant, signing_algorithm)
[show source]
    # File lib/rodauth/features/oidc.rb
557 def id_token_claims(oauth_grant, signing_algorithm)
558   claims = jwt_claims(oauth_grant)
559 
560   claims[:nonce] = oauth_grant[oauth_grants_nonce_column] if oauth_grant[oauth_grants_nonce_column]
561 
562   claims[:acr] = oauth_grant[oauth_grants_acr_column] if oauth_grant[oauth_grants_acr_column]
563 
564   # Time when the End-User authentication occurred.
565   claims[:auth_time] = get_oidc_account_last_login_at(oauth_grant[oauth_grants_account_id_column]).to_i
566 
567   # Access Token hash value.
568   if (access_token = oauth_grant[oauth_grants_token_column])
569     claims[:at_hash] = id_token_hash(access_token, signing_algorithm)
570   end
571 
572   # code hash value.
573   if (code = oauth_grant[oauth_grants_code_column])
574     claims[:c_hash] = id_token_hash(code, signing_algorithm)
575   end
576 
577   claims
578 end
id_token_hash(hash, algo)
[show source]
    # File lib/rodauth/features/oidc.rb
863 def id_token_hash(hash, algo)
864   digest = case algo
865            when /256/ then Digest::SHA256
866            when /384/ then Digest::SHA384
867            when /512/ then Digest::SHA512
868            end
869 
870   return unless digest
871 
872   hash = digest.digest(hash)
873   hash = hash[0...(hash.size / 2)]
874   Base64.urlsafe_encode64(hash).tr("=", "")
875 end
initialize_register_params(create_params, return_params)
[show source]
    # File lib/rodauth/features/oauth_dynamic_client_registration.rb
321 def initialize_register_params(create_params, return_params)
322   client_id = oauth_unique_id_generator
323   create_params[oauth_applications_client_id_column] = client_id
324   return_params["client_id"] = client_id
325   return_params["client_id_issued_at"] = Time.now.utc.iso8601
326 
327   registration_access_token = oauth_unique_id_generator
328   create_params[oauth_applications_registration_access_token_column] = secret_hash(registration_access_token)
329   return_params["registration_access_token"] = registration_access_token
330   return_params["registration_client_uri"] = "#{base_url}/#{registration_client_uri_route}/#{return_params['client_id']}"
331 
332   if create_params.key?(oauth_applications_client_secret_column)
333     set_client_secret(create_params, create_params[oauth_applications_client_secret_column])
334     return_params.delete("client_secret")
335   else
336     client_secret = oauth_unique_id_generator
337     set_client_secret(create_params, client_secret)
338     return_params["client_secret"] = client_secret
339     return_params["client_secret_expires_at"] = 0
340 
341   end
342 end
introspection_request(token_type_hint, token)
[show source]
   # File lib/rodauth/features/oauth_resource_server.rb
47 def introspection_request(token_type_hint, token)
48   introspect_url = URI("#{authorization_server_url}#{introspect_path}")
49 
50   response = http_request(introspect_url, { "token_type_hint" => token_type_hint, "token" => token }) do |request|
51     before_introspection_request(request)
52   end
53 
54   JSON.parse(response.body)
55 end
json_access_token_payload(oauth_grant)
[show source]
    # File lib/rodauth/features/oauth_base.rb
644 def json_access_token_payload(oauth_grant)
645   payload = {
646     "access_token" => oauth_grant[oauth_grants_token_column],
647     "token_type" => oauth_token_type,
648     "expires_in" => oauth_access_token_expires_in
649   }
650   payload["refresh_token"] = oauth_grant[oauth_grants_refresh_token_column] if oauth_grant[oauth_grants_refresh_token_column]
651   payload
652 end
json_request?()

copied from the jwt feature

[show source]
    # File lib/rodauth/features/oauth_base.rb
195 def json_request?
196   return super if features.include?(:jsonn)
197   return @json_request if defined?(@json_request)
198 
199   @json_request = request.content_type =~ json_request_regexp
200 end
json_response_oauth_application(oauth_application)
[show source]
    # File lib/rodauth/features/oauth_dynamic_client_registration.rb
402 def json_response_oauth_application(oauth_application)
403   params = methods.map { |k| k.to_s[/\Aoauth_applications_(\w+)_column\z/, 1] }.compact
404 
405   body = params.each_with_object({}) do |k, hash|
406     next if %w[id account_id client_id client_secret cliennt_secret_hash].include?(k)
407 
408     value = oauth_application[__send__(:"oauth_applications_#{k}_column")]
409 
410     next unless value
411 
412     case k
413     when "redirect_uri"
414       hash["redirect_uris"] = value.split(" ")
415     when "token_endpoint_auth_method", "grant_types", "response_types", "request_uris", "post_logout_redirect_uris"
416       hash[k] = value.split(" ")
417     when "scopes"
418       hash["scope"] = value
419     when "jwks"
420       hash[k] = value.is_a?(String) ? JSON.parse(value) : value
421     when "homepage_url"
422       hash["client_uri"] = value
423     when "name"
424       hash["client_name"] = value
425     else
426       hash[k] = value
427     end
428   end
429 
430   response.status = 200
431   response["Content-Type"] ||= json_response_content_type
432   response["Cache-Control"] = "no-store"
433   response["Pragma"] = "no-cache"
434   json_payload = _json_response_body(body)
435   return_response(json_payload)
436 end
json_response_success(body, cache = false)
[show source]
    # File lib/rodauth/features/oauth_base.rb
808 def json_response_success(body, cache = false)
809   response.status = 200
810   response["Content-Type"] ||= json_response_content_type
811   if cache
812     # defaulting to 1-day for everyone, for now at least
813     max_age = 60 * 60 * 24
814     response["Cache-Control"] = "private, max-age=#{max_age}"
815   else
816     response["Cache-Control"] = "no-store"
817     response["Pragma"] = "no-cache"
818   end
819   json_payload = _json_response_body(body)
820   return_response(json_payload)
821 end
json_token_introspect_payload(grant_or_claims)
[show source]
    # File lib/rodauth/features/oauth_dpop.rb
390 def json_token_introspect_payload(grant_or_claims)
391   claims = super
392 
393   return claims unless grant_or_claims
394 
395   if (jkt = grant_or_claims.dig("cnf", "jkt"))
396     (claims[:cnf] ||= {})[:jkt] = jkt
397     claims[:token_type] = "DPoP"
398   end
399 
400   claims
401 end
json_webfinger_payload()

Webfinger

[show source]
    # File lib/rodauth/features/oidc.rb
781 def json_webfinger_payload
782   JSON.dump({
783               subject: param("resource"),
784               links: [{
785                 rel: "http://openid.net/specs/connect/1.0/issuer",
786                 href: authorization_server_url
787               }]
788             })
789 end
jwk_export(key)
[show source]
    # File lib/rodauth/features/oauth_jwt_base.rb
152 def jwk_export(key)
153   key_to_jwk(key)
154 end
jwk_import(jwk)
[show source]
    # File lib/rodauth/features/oauth_jwt_base.rb
156 def jwk_import(jwk)
157   JSON::JWK.new(jwk)
158 end
jwk_key(jwk)
[show source]
    # File lib/rodauth/features/oauth_jwt_base.rb
160 def jwk_key(jwk)
161   jwk = jwk_import(jwk) unless jwk.is_a?(JSON::JWK)
162   jwk.to_key
163 end
jwk_thumbprint(jwk)
[show source]
    # File lib/rodauth/features/oauth_jwt_base.rb
165 def jwk_thumbprint(jwk)
166   jwk = jwk_import(jwk) if jwk.is_a?(Hash)
167   jwk.thumbprint
168 end
jwks_set()
[show source]
   # File lib/rodauth/features/oauth_jwt_jwks.rb
28 def jwks_set
29   @jwks_set ||= [
30     *(
31       unless oauth_jwt_public_keys.empty?
32         oauth_jwt_public_keys.flat_map { |algo, pkeys| Array(pkeys).map { |pkey| jwk_export(pkey).merge(use: "sig", alg: algo) } }
33       end
34     ),
35     *(
36       unless oauth_jwt_jwe_public_keys.empty?
37         oauth_jwt_jwe_public_keys.flat_map do |(algo, _enc), pkeys|
38           Array(pkeys).map do |pkey|
39             jwk_export(pkey).merge(use: "enc", alg: algo)
40           end
41         end
42       end
43     )
44   ].compact
45 end
jwt_assertion(assertion, **kwargs)
[show source]
   # File lib/rodauth/features/oauth_jwt_bearer_grant.rb
83 def jwt_assertion(assertion, **kwargs)
84   claims = jwt_decode(assertion, verify_iss: false, verify_aud: false, verify_jti: false, **kwargs)
85 
86   return unless claims && verify_aud(request.url, claims["aud"])
87 
88   claims
89 end
jwt_claims(oauth_grant)
[show source]
    # File lib/rodauth/features/oauth_dpop.rb
234 def jwt_claims(oauth_grant)
235   claims = super
236   if @dpop_thumbprint
237     # the authorization server associates the issued access token with the
238     # public key from the DPoP proof
239     claims[:cnf] = { jkt: @dpop_thumbprint }
240   end
241   claims
242 end
jwt_decode( token, jwks: nil, jws_algorithm: oauth_jwt_public_keys.keys.first || oauth_jwt_keys.keys.first, jws_key: oauth_jwt_keys[jws_algorithm] || _jwt_key, jws_encryption_algorithm: oauth_jwt_jwe_keys.keys.dig(0, 0), jws_encryption_method: oauth_jwt_jwe_keys.keys.dig(0, 1), jwe_key: oauth_jwt_jwe_keys[[jws_encryption_algorithm, jws_encryption_method]] || oauth_jwt_jwe_keys.values.first, verify_claims: true, verify_jti: true, verify_iss: true, verify_aud: true, verify_headers: nil, ** )
[show source]
    # File lib/rodauth/features/oauth_jwt_base.rb
217 def jwt_decode(
218   token,
219   jwks: nil,
220   jws_algorithm: oauth_jwt_public_keys.keys.first || oauth_jwt_keys.keys.first,
221   jws_key: oauth_jwt_keys[jws_algorithm] || _jwt_key,
222   jws_encryption_algorithm: oauth_jwt_jwe_keys.keys.dig(0, 0),
223   jws_encryption_method: oauth_jwt_jwe_keys.keys.dig(0, 1),
224   jwe_key: oauth_jwt_jwe_keys[[jws_encryption_algorithm, jws_encryption_method]] || oauth_jwt_jwe_keys.values.first,
225   verify_claims: true,
226   verify_jti: true,
227   verify_iss: true,
228   verify_aud: true,
229   verify_headers: nil,
230   **
231 )
232   jws_key = jws_key.first if jws_key.is_a?(Array)
233 
234   if jwe_key
235     jwe_key = jwe_key.first if jwe_key.is_a?(Array)
236     token = JSON::JWT.decode(token, jwe_key).plain_text
237   end
238 
239   claims = if is_authorization_server?
240              if jwks
241                jwks = jwks[:keys] if jwks.is_a?(Hash)
242 
243                enc_algs = [jws_encryption_algorithm].compact
244                enc_meths = [jws_encryption_method].compact
245 
246                sig_algs = jws_algorithm ? [jws_algorithm] : jwks.select { |k| k[:use] == "sig" }.map { |k| k[:alg] }
247                sig_algs = sig_algs.compact.map(&:to_sym)
248 
249                # JWKs may be set up without a KID, when there's a single one
250                if jwks.size == 1 && !jwks[0][:kid]
251                  key = jwks[0]
252                  jwk_key = JSON::JWK.new(key)
253                  jws = JSON::JWT.decode(token, jwk_key)
254                else
255                  jws = JSON::JWT.decode(token, JSON::JWK::Set.new({ keys: jwks }), enc_algs + sig_algs, enc_meths)
256                  jws = JSON::JWT.decode(jws.plain_text, JSON::JWK::Set.new({ keys: jwks }), sig_algs) if jws.is_a?(JSON::JWE)
257                end
258                jws
259              elsif jws_key
260                JSON::JWT.decode(token, jws_key)
261              else
262                JSON::JWT.decode(token, nil, jws_algorithm)
263              end
264            elsif (jwks = auth_server_jwks_set)
265              JSON::JWT.decode(token, JSON::JWK::Set.new(jwks))
266            end
267 
268   now = Time.now
269   if verify_claims &&
270      (!claims[:exp] || Time.at(claims[:exp]) < now) &&
271      claims[:nbf] && Time.at(claims[:nbf]) < now &&
272      claims[:iat] && Time.at(claims[:iat]) < now &&
273      verify_iss && claims[:iss] != oauth_jwt_issuer &&
274      verify_aud && !verify_aud(claims[:aud], claims[:client_id]) &&
275      verify_jti && !verify_jti(claims[:jti], claims)
276 
277     return
278   end
279 
280   return if verify_headers && !verify_headers.call(claims.header)
281 
282   claims
283 rescue JSON::JWT::Exception
284   nil
285 end
jwt_decode_no_key(token)
[show source]
    # File lib/rodauth/features/oauth_jwt_base.rb
287 def jwt_decode_no_key(token)
288   jws = JSON::JWT.decode(token, :skip_verification)
289   [jws.to_h, jws.header]
290 end
jwt_decode_with_jwe( token, jwks: nil, jws_encryption_algorithm: oauth_jwt_jwe_keys.keys.dig(0, 0), jws_encryption_method: oauth_jwt_jwe_keys.keys.dig(0, 1), jwe_key: oauth_jwt_jwe_keys[[jws_encryption_algorithm, jws_encryption_method]] || oauth_jwt_jwe_keys.values.first, **args )
[show source]
    # File lib/rodauth/features/oauth_jwt_base.rb
469 def jwt_decode_with_jwe(
470   token,
471   jwks: nil,
472   jws_encryption_algorithm: oauth_jwt_jwe_keys.keys.dig(0, 0),
473   jws_encryption_method: oauth_jwt_jwe_keys.keys.dig(0, 1),
474   jwe_key: oauth_jwt_jwe_keys[[jws_encryption_algorithm, jws_encryption_method]] || oauth_jwt_jwe_keys.values.first,
475   **args
476 )
477   token = if jwks && jwks.any? { |k| k[:use] == "enc" }
478             JWE.__rodauth_oauth_decrypt_from_jwks(token, jwks, alg: jws_encryption_algorithm, enc: jws_encryption_method)
479           elsif jwe_key
480             jwe_key = jwe_key.first if jwe_key.is_a?(Array)
481             JWE.decrypt(token, jwe_key)
482           else
483             token
484           end
485 
486   jwt_decode_without_jwe(token, jwks: jwks, **args)
487 rescue JWE::DecodeError => e
488   jwt_decode_without_jwe(token, jwks: jwks, **args) if e.message.include?("Not enough or too many segments")
489 end
jwt_encode(payload, jwks: nil, headers: {}, encryption_algorithm: oauth_jwt_jwe_keys.keys.dig(0, 0), encryption_method: oauth_jwt_jwe_keys.keys.dig(0, 1), jwe_key: oauth_jwt_jwe_keys[[encryption_algorithm, encryption_method]], signing_algorithm: oauth_jwt_keys.keys.first)
[show source]
    # File lib/rodauth/features/oauth_jwt_base.rb
174 def jwt_encode(payload,
175                jwks: nil,
176                headers: {},
177                encryption_algorithm: oauth_jwt_jwe_keys.keys.dig(0, 0),
178                encryption_method: oauth_jwt_jwe_keys.keys.dig(0, 1),
179                jwe_key: oauth_jwt_jwe_keys[[encryption_algorithm,
180                                             encryption_method]],
181                signing_algorithm: oauth_jwt_keys.keys.first)
182   payload[:jti] = generate_jti(payload)
183   jwt = JSON::JWT.new(payload)
184 
185   key = oauth_jwt_keys[signing_algorithm] || _jwt_key
186   key = key.first if key.is_a?(Array)
187 
188   jwk = JSON::JWK.new(key || "")
189 
190   # update headers
191   headers.each_key do |k|
192     if jwt.respond_to?(:"#{k}=")
193       jwt.send(:"#{k}=", headers[k])
194       headers.delete(k)
195     end
196   end
197   jwt.header.merge(headers) unless headers.empty?
198 
199   jwt = jwt.sign(jwk, signing_algorithm)
200 
201   return jwt.to_s unless encryption_algorithm && encryption_method
202 
203   if jwks && (jwk = jwks.find { |k| k[:use] == "enc" && k[:alg] == encryption_algorithm && k[:enc] == encryption_method })
204     jwk = JSON::JWK.new(jwk)
205     jwe = jwt.encrypt(jwk, encryption_algorithm.to_sym, encryption_method.to_sym)
206     jwe.to_s
207   elsif jwe_key
208     jwe_key = jwe_key.first if jwe_key.is_a?(Array)
209     algorithm = encryption_algorithm.to_sym
210     meth = encryption_method.to_sym
211     jwt.encrypt(jwe_key, algorithm, meth)
212   else
213     jwt.to_s
214   end
215 end
jwt_encode_authorization_response_mode(params)
[show source]
    # File lib/rodauth/features/oauth_jwt_secured_authorization_response_mode.rb
 99 def jwt_encode_authorization_response_mode(params)
100   now = Time.now.to_i
101   claims = {
102     iss: oauth_jwt_issuer,
103     aud: oauth_application[oauth_applications_client_id_column],
104     exp: now + oauth_authorization_response_mode_expires_in,
105     iat: now
106   }.merge(params)
107 
108   encode_params = {
109     jwks: oauth_application_jwks(oauth_application),
110     signing_algorithm: oauth_application[oauth_applications_authorization_signed_response_alg_column],
111     encryption_algorithm: oauth_application[oauth_applications_authorization_encrypted_response_alg_column],
112     encryption_method: oauth_application[oauth_applications_authorization_encrypted_response_enc_column]
113   }.compact
114 
115   jwt_encode(claims, **encode_params)
116 end
jwt_encode_with_jwe( payload, jwks: nil, encryption_algorithm: oauth_jwt_jwe_keys.keys.dig(0, 0), encryption_method: oauth_jwt_jwe_keys.keys.dig(0, 1), jwe_key: oauth_jwt_jwe_keys[[encryption_algorithm, encryption_method]], **args )
[show source]
    # File lib/rodauth/features/oauth_jwt_base.rb
367 def jwt_encode_with_jwe(
368   payload,
369   jwks: nil,
370   encryption_algorithm: oauth_jwt_jwe_keys.keys.dig(0, 0),
371   encryption_method: oauth_jwt_jwe_keys.keys.dig(0, 1),
372   jwe_key: oauth_jwt_jwe_keys[[encryption_algorithm, encryption_method]],
373   **args
374 )
375   token = jwt_encode_without_jwe(payload, **args)
376 
377   return token unless encryption_algorithm && encryption_method
378 
379   if jwks && jwks.any? { |k| k[:use] == "enc" }
380     JWE.__rodauth_oauth_encrypt_from_jwks(token, jwks, alg: encryption_algorithm, enc: encryption_method)
381   elsif jwe_key
382     jwe_key = jwe_key.first if jwe_key.is_a?(Array)
383     params = {
384       zip: "DEF",
385       copyright: oauth_jwt_jwe_copyright
386     }
387     params[:enc] = encryption_method if encryption_method
388     params[:alg] = encryption_algorithm if encryption_algorithm
389     JWE.encrypt(token, jwe_key, **params)
390   else
391     token
392   end
393 end
jwt_response_success(jwt, cache = false)
[show source]
    # File lib/rodauth/features/oidc.rb
849 def jwt_response_success(jwt, cache = false)
850   response.status = 200
851   response["Content-Type"] ||= "application/jwt"
852   if cache
853     # defaulting to 1-day for everyone, for now at least
854     max_age = 60 * 60 * 24
855     response["Cache-Control"] = "private, max-age=#{max_age}"
856   else
857     response["Cache-Control"] = "no-store"
858     response["Pragma"] = "no-cache"
859   end
860   return_response(jwt)
861 end
jwt_subject(account_unique_id, client_application = oauth_application)
[show source]
   # File lib/rodauth/features/oauth_jwt_base.rb
66 def jwt_subject(account_unique_id, client_application = oauth_application)
67   (account_unique_id || client_application[oauth_applications_client_id_column]).to_s
68 end
key_to_jwk(key)
[show source]
    # File lib/rodauth/features/oauth_jwt_base.rb
148 def key_to_jwk(key)
149   JSON::JWK.new(key)
150 end
load_oauth_application_management_routes()

/oauth-applications routes

[show source]
    # File lib/rodauth/features/oauth_application_management.rb
 73 def load_oauth_application_management_routes
 74   request.on(oauth_applications_route) do
 75     check_csrf if check_csrf?
 76     require_account
 77 
 78     request.get "new" do
 79       new_oauth_application_view
 80     end
 81 
 82     request.on(oauth_applications_id_pattern) do |id|
 83       oauth_application = db[oauth_applications_table]
 84                           .where(oauth_applications_id_column => id)
 85                           .where(oauth_applications_account_id_column => account_id)
 86                           .first
 87       next unless oauth_application
 88 
 89       scope.instance_variable_set(:@oauth_application, oauth_application)
 90 
 91       request.is do
 92         request.get do
 93           oauth_application_view
 94         end
 95       end
 96 
 97       request.on(oauth_applications_oauth_grants_path) do
 98         page = Integer(param_or_nil("page") || 1)
 99         per_page = per_page_param(oauth_grants_per_page)
100         oauth_grants = db[oauth_grants_table]
101                        .where(oauth_grants_oauth_application_id_column => id)
102                        .order(Sequel.desc(oauth_grants_id_column))
103         scope.instance_variable_set(:@oauth_grants, oauth_grants.paginate(page, per_page))
104         request.is do
105           request.get do
106             oauth_application_oauth_grants_view
107           end
108         end
109       end
110     end
111 
112     request.is do
113       request.get do
114         page = Integer(param_or_nil("page") || 1)
115         per_page = per_page_param(oauth_applications_per_page)
116         scope.instance_variable_set(:@oauth_applications, db[oauth_applications_table]
117           .where(oauth_applications_account_id_column => account_id)
118           .order(Sequel.desc(oauth_applications_id_column))
119           .paginate(page, per_page))
120 
121         oauth_applications_view
122       end
123 
124       request.post do
125         catch_error do
126           validate_oauth_application_params
127 
128           transaction do
129             before_create_oauth_application
130             id = create_oauth_application
131             after_create_oauth_application
132             set_notice_flash create_oauth_application_notice_flash
133             redirect "#{request.path}/#{id}"
134           end
135         end
136         set_error_flash create_oauth_application_error_flash
137         new_oauth_application_view
138       end
139     end
140   end
141 end
load_oauth_grant_management_routes()
[show source]
   # File lib/rodauth/features/oauth_grant_management.rb
36 def load_oauth_grant_management_routes
37   request.on(oauth_grants_route) do
38     check_csrf if check_csrf?
39     require_account
40 
41     request.post(oauth_grants_id_pattern) do |id|
42       db[oauth_grants_table]
43         .where(oauth_grants_id_column => id)
44         .where(oauth_grants_account_id_column => account_id)
45         .update(oauth_grants_revoked_at_column => Sequel::CURRENT_TIMESTAMP)
46 
47       set_notice_flash revoke_oauth_grant_notice_flash
48       redirect oauth_grants_path || "/"
49     end
50 
51     request.is do
52       request.get do
53         page = Integer(param_or_nil("page") || 1)
54         per_page = per_page_param(oauth_grants_per_page)
55 
56         scope.instance_variable_set(:@oauth_grants, db[oauth_grants_table]
57           .select(Sequel[oauth_grants_table].*, Sequel[oauth_applications_table][oauth_applications_name_column])
58           .join(oauth_applications_table, Sequel[oauth_grants_table][oauth_grants_oauth_application_id_column] =>
59             Sequel[oauth_applications_table][oauth_applications_id_column])
60           .where(Sequel[oauth_grants_table][oauth_grants_account_id_column] => account_id)
61           .where(oauth_grants_revoked_at_column => nil)
62           .order(Sequel.desc(oauth_grants_id_column))
63           .paginate(page, per_page))
64         oauth_grants_view
65       end
66     end
67   end
68 end
load_oauth_server_metadata_route(issuer = nil)
[show source]
    # File lib/rodauth/features/oauth_base.rb
150 def load_oauth_server_metadata_route(issuer = nil)
151   request.on(".well-known") do
152     request.get("oauth-authorization-server") do
153       json_response_success(oauth_server_metadata_body(issuer), true)
154     end
155   end
156 end
load_openid_configuration_route(alt_issuer = nil)
[show source]
    # File lib/rodauth/features/oidc.rb
186 def load_openid_configuration_route(alt_issuer = nil)
187   request.on(".well-known/openid-configuration") do
188     allow_cors(request)
189 
190     request.is do
191       request.get do
192         json_response_success(openid_configuration_body(alt_issuer), cache: true)
193       end
194     end
195   end
196 end
load_registration_client_uri_routes()
[show source]
   # File lib/rodauth/features/oauth_dynamic_client_registration.rb
17 def load_registration_client_uri_routes
18   request.on(registration_client_uri_route) do
19     # CLIENT REGISTRATION URI
20     request.on(String) do |client_id|
21       token = (v = request.env["HTTP_AUTHORIZATION"]) && v[/\A *Bearer (.*)\Z/, 1]
22 
23       next unless token
24 
25       oauth_application = db[oauth_applications_table]
26                           .where(oauth_applications_client_id_column => client_id)
27                           .first
28       next unless oauth_application
29 
30       authorization_required unless password_hash_match?(oauth_application[oauth_applications_registration_access_token_column], token)
31 
32       request.is do
33         request.get do
34           json_response_oauth_application(oauth_application)
35         end
36         request.on method: :put do
37           %w[client_id registration_access_token registration_client_uri client_secret_expires_at
38              client_id_issued_at].each do |prohibited_param|
39             if request.params.key?(prohibited_param)
40               register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(prohibited_param))
41             end
42           end
43           validate_client_registration_params
44 
45           # if the client includes the "client_secret" field in the request, the value of this field MUST match the currently
46           # issued client secret for that client.  The client MUST NOT be allowed to overwrite its existing client secret with
47           # its own chosen value.
48           authorization_required if request.params.key?("client_secret") && secret_matches?(oauth_application,
49                                                                                             request.params["client_secret"])
50 
51           oauth_application = transaction do
52             applications_ds = db[oauth_applications_table]
53             __update_and_return__(applications_ds, @oauth_application_params)
54           end
55           json_response_oauth_application(oauth_application)
56         end
57 
58         request.on method: :delete do
59           applications_ds = db[oauth_applications_table]
60           applications_ds.where(oauth_applications_client_id_column => client_id).delete
61           response.status = 204
62           response["Cache-Control"] = "no-store"
63           response["Pragma"] = "no-cache"
64           response.finish
65         end
66       end
67     end
68   end
69 end
load_webfinger_route()
[show source]
    # File lib/rodauth/features/oidc.rb
198 def load_webfinger_route
199   request.on(".well-known/webfinger") do
200     request.get do
201       resource = param_or_nil("resource")
202 
203       throw_json_response_error(400, "invalid_request") unless resource
204 
205       response.status = 200
206       response["Content-Type"] ||= "application/jrd+json"
207 
208       return_response(json_webfinger_payload)
209     end
210   end
211 end
logout()
[show source]
   # File lib/rodauth/features/oidc_backchannel_logout.rb
18 def logout
19   visited_sites = session[visited_sites_key]
20 
21   return super unless visited_sites
22 
23   oauth_applications = db[oauth_applications_table].where(oauth_applications_client_id_column => visited_sites.map(&:first))
24                                                    .as_hash(oauth_applications_id_column)
25 
26   logout_params = oauth_applications.flat_map do |_id, oauth_application|
27     logout_url = oauth_application[oauth_applications_backchannel_logout_uri_column]
28 
29     next unless logout_url
30 
31     client_id = oauth_application[oauth_applications_client_id_column]
32 
33     sids = visited_sites.select { |cid, _| cid == client_id }.map(&:last)
34 
35     sids.map do |sid|
36       logout_token = generate_logout_token(oauth_application, sid)
37 
38       [logout_url, logout_token]
39     end
40   end.compact
41 
42   perform_logout_requests(logout_params) unless logout_params.empty?
43 
44   # now we can clear the session
45   super
46 end
normalize_redirect_uri_for_comparison(redirect_uri)
[show source]
    # File lib/rodauth/features/oauth_authorize_base.rb
239 def normalize_redirect_uri_for_comparison(redirect_uri)
240   uri = URI(redirect_uri)
241 
242   return redirect_uri unless uri.scheme == "http" && uri.port
243 
244   hostname = uri.hostname
245 
246   # https://www.rfc-editor.org/rfc/rfc8252#section-7.3
247   # ignore (potentially ephemeral) port number for native clients per RFC8252
248   begin
249     ip = IPAddr.new(hostname)
250     uri.port = nil if ip.loopback?
251   rescue IPAddr::InvalidAddressError
252     # https://www.rfc-editor.org/rfc/rfc8252#section-8.3
253     # Although the use of localhost is NOT RECOMMENDED, it is still allowed.
254     uri.port = nil if hostname == "localhost"
255   end
256 
257   uri.to_s
258 end
oauth_account_ds(account_id)
[show source]
    # File lib/rodauth/features/oauth_base.rb
314 def oauth_account_ds(account_id)
315   account_ds(account_id)
316 end
oauth_acr_values_supported()
[show source]
    # File lib/rodauth/features/oidc.rb
275 def oauth_acr_values_supported
276   acr_values = []
277   acr_values << "phrh" if features.include?(:webauthn_login)
278   acr_values << "phr" if respond_to?(:require_two_factor_authenticated)
279   acr_values
280 end
oauth_application()
[show source]
    # File lib/rodauth/features/oauth_base.rb
221 def oauth_application
222   return @oauth_application if defined?(@oauth_application)
223 
224   @oauth_application = begin
225     client_id = param_or_nil("client_id")
226 
227     return unless client_id
228 
229     db[oauth_applications_table].filter(oauth_applications_client_id_column => client_id).first
230   end
231 end
oauth_application_ds(oauth_application_id)
[show source]
    # File lib/rodauth/features/oauth_base.rb
318 def oauth_application_ds(oauth_application_id)
319   db[oauth_applications_table].where(oauth_applications_id_column => oauth_application_id)
320 end
oauth_application_in_visited_sites()
[show source]
   # File lib/rodauth/features/oidc_logout_base.rb
43 def oauth_application_in_visited_sites
44   visited_sites = session[visited_sites_key] || []
45 
46   session_id = yield
47 
48   visited_site = [oauth_application[oauth_applications_client_id_column], session_id]
49 
50   return if visited_sites.include?(visited_site)
51 
52   visited_sites << visited_site
53   set_session_value(visited_sites_key, visited_sites)
54 end
oauth_application_jwks(oauth_application)
[show source]
    # File lib/rodauth/features/oauth_jwt_base.rb
115 def oauth_application_jwks(oauth_application)
116   jwks = oauth_application[oauth_applications_jwks_column]
117 
118   if jwks
119     jwks = JSON.parse(jwks, symbolize_names: true) if jwks.is_a?(String)
120     return jwks
121   end
122 
123   jwks_uri = oauth_application[oauth_applications_jwks_uri_column]
124 
125   return unless jwks_uri
126 
127   jwks_uri = URI(jwks_uri)
128 
129   http_request_with_cache(jwks_uri)
130 end
oauth_application_params()
[show source]
    # File lib/rodauth/features/oauth_application_management.rb
145 def oauth_application_params
146   @oauth_application_params ||= oauth_application_required_params.each_with_object({}) do |param, params|
147     value = request.params[__send__(:"oauth_application_#{param}_param")]
148     if value && !value.empty?
149       params[param] = value
150     else
151       set_field_error(param, null_error_message)
152     end
153   end
154 end
oauth_application_path(id)
[show source]
   # File lib/rodauth/features/oauth_application_management.rb
68 def oauth_application_path(id)
69   "#{oauth_applications_path}/#{id}"
70 end
oauth_applications_path(opts = {})
[show source]
   # File lib/rodauth/features/oauth_application_management.rb
64 def oauth_applications_path(opts = {})
65   route_path(oauth_applications_route, opts)
66 end
oauth_grant_by_refresh_token(token, **kwargs)
[show source]
    # File lib/rodauth/features/oauth_base.rb
640 def oauth_grant_by_refresh_token(token, **kwargs)
641   oauth_grant_by_refresh_token_ds(token, **kwargs).first
642 end
oauth_grant_by_refresh_token_ds(token, revoked: false)
[show source]
    # File lib/rodauth/features/oauth_base.rb
619 def oauth_grant_by_refresh_token_ds(token, revoked: false)
620   ds = db[oauth_grants_table].where(oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column])
621   #
622   # filter expired refresh tokens out.
623   # an expired refresh token is a token whose access token expired for a period longer than the
624   # refresh token expiration period.
625   #
626   ds = ds.where(Sequel.date_add(oauth_grants_expires_in_column,
627                                 seconds: (oauth_refresh_token_expires_in - oauth_access_token_expires_in)) >= Sequel::CURRENT_TIMESTAMP)
628 
629   ds = if oauth_grants_refresh_token_hash_column
630          ds.where(oauth_grants_refresh_token_hash_column => generate_token_hash(token))
631        else
632          ds.where(oauth_grants_refresh_token_column => token)
633        end
634 
635   ds = ds.where(oauth_grants_revoked_at_column => nil) unless revoked
636 
637   ds
638 end
oauth_grant_by_token(token)
[show source]
    # File lib/rodauth/features/oauth_base.rb
615 def oauth_grant_by_token(token)
616   oauth_grant_by_token_ds(token).first
617 end
oauth_grant_by_token_ds(token)
[show source]
    # File lib/rodauth/features/oauth_base.rb
605 def oauth_grant_by_token_ds(token)
606   ds = valid_oauth_grant_ds
607 
608   if oauth_grants_token_hash_column
609     ds.where(Sequel[oauth_grants_table][oauth_grants_token_hash_column] => generate_token_hash(token))
610   else
611     ds.where(Sequel[oauth_grants_table][oauth_grants_token_column] => token)
612   end
613 end
oauth_grant_path(id)
[show source]
   # File lib/rodauth/features/oauth_grant_management.rb
32 def oauth_grant_path(id)
33   "#{oauth_grants_path}/#{id}"
34 end
oauth_grant_types_supported()
[show source]
   # File lib/rodauth/features/oauth_authorization_code_grant.rb
11 def oauth_grant_types_supported
12   super | %w[authorization_code]
13 end
oauth_grants_path(opts = {})
[show source]
   # File lib/rodauth/features/oauth_grant_management.rb
28 def oauth_grants_path(opts = {})
29   route_path(oauth_grants_route, opts)
30 end
oauth_grants_resource_owner_columns()
[show source]
    # File lib/rodauth/features/oauth_authorize_base.rb
131 def oauth_grants_resource_owner_columns
132   [oauth_grants_account_id_column]
133 end
oauth_grants_unique_columns()

OAuth Token Unique/Reuse

[show source]
    # File lib/rodauth/features/oauth_base.rb
338 def oauth_grants_unique_columns
339   [
340     oauth_grants_oauth_application_id_column,
341     oauth_grants_account_id_column,
342     oauth_grants_scopes_column
343   ]
344 end
oauth_jwt_audience()
[show source]
   # File lib/rodauth/features/oauth_jwt_base.rb
39 def oauth_jwt_audience
40   # The JWT MUST contain an "aud" (audience) claim containing a
41   # value that identifies the authorization server as an intended
42   # audience.  The token endpoint URL of the authorization server
43   # MAY be used as a value for an "aud" element to identify the
44   # authorization server as an intended audience of the JWT.
45   @oauth_jwt_audience ||= if is_authorization_server?
46                             oauth_application[oauth_applications_client_id_column]
47                           else
48                             metadata = authorization_server_metadata
49 
50                             return unless metadata
51 
52                             metadata[:token_endpoint]
53                           end
54 end
oauth_jwt_issuer()
[show source]
   # File lib/rodauth/features/oauth_jwt_base.rb
33 def oauth_jwt_issuer
34   # The JWT MUST contain an "iss" (issuer) claim that contains a
35   # unique identifier for the entity that issued the JWT.
36   @oauth_jwt_issuer ||= authorization_server_url
37 end
oauth_jwt_jwe_algorithms_supported()
[show source]
    # File lib/rodauth/features/oauth_jwt_base.rb
309 def oauth_jwt_jwe_algorithms_supported
310   JWE::VALID_ALG
311 end
oauth_jwt_jwe_encryption_methods_supported()
[show source]
    # File lib/rodauth/features/oauth_jwt_base.rb
313 def oauth_jwt_jwe_encryption_methods_supported
314   JWE::VALID_ENC
315 end
oauth_oidc_session_management_salt()
[show source]
   # File lib/rodauth/features/oidc_session_management.rb
87 def oauth_oidc_session_management_salt
88   oauth_unique_id_generator
89 end
oauth_response_modes_for_code_supported()
[show source]
   # File lib/rodauth/features/oauth_authorization_code_grant.rb
41 def oauth_response_modes_for_code_supported
42   %w[query form_post]
43 end
oauth_response_modes_for_token_supported()
[show source]
   # File lib/rodauth/features/oauth_implicit_grant.rb
37 def oauth_response_modes_for_token_supported
38   %w[fragment]
39 end
oauth_response_modes_supported()
[show source]
   # File lib/rodauth/features/oauth_authorization_code_grant.rb
19 def oauth_response_modes_supported
20   super | %w[query form_post]
21 end
oauth_response_types_supported()
[show source]
   # File lib/rodauth/features/oauth_authorization_code_grant.rb
15 def oauth_response_types_supported
16   super | %w[code]
17 end
oauth_server_metadata_body(*)
[show source]
    # File lib/rodauth/features/oauth_authorization_code_grant.rb
159 def oauth_server_metadata_body(*)
160   super.tap do |data|
161     data[:authorization_endpoint] = authorize_url
162   end
163 end
oauth_token_endpoint_auth_methods_supported()
[show source]
   # File lib/rodauth/features/oauth_jwt_bearer_grant.rb
17 def oauth_token_endpoint_auth_methods_supported
18   if oauth_applications_client_secret_hash_column.nil?
19     super | %w[client_secret_jwt private_key_jwt urn:ietf:params:oauth:client-assertion-type:jwt-bearer]
20   else
21     super | %w[private_key_jwt]
22   end
23 end
oauth_token_subject()
[show source]
    # File lib/rodauth/features/oauth_base.rb
167 def oauth_token_subject
168   return unless authorization_token
169 
170   authorization_token[oauth_grants_account_id_column] ||
171     db[oauth_applications_table].where(
172       oauth_applications_id_column => authorization_token[oauth_grants_oauth_application_id_column]
173     ).select_map(oauth_applications_client_id_column).first
174 end
oauth_unique_id_generator()
[show source]
    # File lib/rodauth/features/oauth_base.rb
446 def oauth_unique_id_generator
447   SecureRandom.urlsafe_base64(32)
448 end
oidc_authorize_on_prompt_none?(_account)
[show source]
    # File lib/rodauth/features/oidc.rb
282 def oidc_authorize_on_prompt_none?(_account)
283   false
284 end
oidc_grant_params()
[show source]
    # File lib/rodauth/features/oidc.rb
747 def oidc_grant_params
748   grant_params = {
749     **resource_owner_params,
750     oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
751     oauth_grants_scopes_column => scopes.join(oauth_scope_separator),
752     oauth_grants_redirect_uri_column => param_or_nil("redirect_uri")
753   }
754   if (nonce = param_or_nil("nonce"))
755     grant_params[oauth_grants_nonce_column] = nonce
756   end
757   grant_params[oauth_grants_acr_column] = @acr if @acr
758   if (claims_locales = param_or_nil("claims_locales"))
759     grant_params[oauth_grants_claims_locales_column] = claims_locales
760   end
761   if (claims = param_or_nil("claims"))
762     grant_params[oauth_grants_claims_column] = claims
763   end
764   grant_params
765 end
openid_configuration_body(path = nil)

Metadata

[show source]
    # File lib/rodauth/features/oidc.rb
793 def openid_configuration_body(path = nil)
794   metadata = oauth_server_metadata_body(path).slice(*VALID_METADATA_KEYS)
795 
796   scope_claims = oauth_application_scopes.each_with_object([]) do |scope, claims|
797     oidc, param = scope.split(".", 2)
798     if param
799       claims << param
800     else
801       oidc_claims = OIDC_SCOPES_MAP[oidc]
802       claims.concat(oidc_claims) if oidc_claims
803     end
804   end
805 
806   scope_claims.unshift("auth_time")
807 
808   metadata.merge(
809     userinfo_endpoint: userinfo_url,
810     subject_types_supported: %w[public pairwise],
811     acr_values_supported: oauth_acr_values_supported,
812     claims_parameter_supported: true,
813 
814     id_token_signing_alg_values_supported: oauth_jwt_jws_algorithms_supported,
815     id_token_encryption_alg_values_supported: oauth_jwt_jwe_algorithms_supported,
816     id_token_encryption_enc_values_supported: oauth_jwt_jwe_encryption_methods_supported,
817 
818     userinfo_signing_alg_values_supported: oauth_jwt_jws_algorithms_supported,
819     userinfo_encryption_alg_values_supported: oauth_jwt_jwe_algorithms_supported,
820     userinfo_encryption_enc_values_supported: oauth_jwt_jwe_encryption_methods_supported,
821 
822     request_object_signing_alg_values_supported: oauth_jwt_jws_algorithms_supported,
823     request_object_encryption_alg_values_supported: oauth_jwt_jwe_algorithms_supported,
824     request_object_encryption_enc_values_supported: oauth_jwt_jwe_encryption_methods_supported,
825 
826     # These Claim Types are described in Section 5.6 of OpenID Connect Core 1.0 [OpenID.Core].
827     # Values defined by this specification are normal, aggregated, and distributed.
828     # If omitted, the implementation supports only normal Claims.
829     claim_types_supported: %w[normal],
830     claims_supported: %w[sub iss iat exp aud] | scope_claims
831   ).reject do |key, val|
832     # Filter null values in optional items
833     (!REQUIRED_METADATA_KEYS.include?(key.to_sym) && val.nil?) ||
834       # Claims with zero elements MUST be omitted from the response
835       (val.respond_to?(:empty?) && val.empty?)
836   end
837 end
parse_saml_assertion(assertion)

rubocop:disable Naming/MemoizedInstanceVariableName

[show source]
    # File lib/rodauth/features/oauth_saml_bearer_grant.rb
102 def parse_saml_assertion(assertion)
103   return @assertion if defined?(@assertion)
104 
105   response = OneLogin::RubySaml::Response.new(assertion)
106 
107   # The SAML Assertion XML data MUST be encoded using base64url
108   redirect_response_error("invalid_grant", oauth_saml_assertion_not_base64_message) unless response.send(:base64_encoded?, assertion)
109 
110   # 1. The Assertion's <Issuer> element MUST contain a unique identifier
111   # for the entity that issued the Assertion.
112   redirect_response_error("invalid_grant", oauth_saml_assertion_single_issuer_message) unless response.issuers.size == 1
113 
114   @saml_settings = db[oauth_saml_settings_table].where(
115     oauth_saml_settings_issuer_column => response.issuers.first
116   ).first
117 
118   redirect_response_error("invalid_grant", oauth_saml_settings_not_found_message) unless @saml_settings
119 
120   response.settings = generate_saml_settings(@saml_settings)
121 
122   # 2. The Assertion MUST contain a <Conditions> element ...
123   # 3. he Assertion MUST have an expiry that limits the time window ...
124   # 4. The Assertion MUST have an expiry that limits the time window ...
125   # 5. The <Subject> element MUST contain at least one ...
126   # 6. The authorization server MUST reject the entire Assertion if the ...
127   # 7. If the Assertion issuer directly authenticated the subject, ...
128   redirect_response_error("invalid_grant", response.errors.join("; ")) unless response.is_valid?
129 
130   @assertion = response
131 end
password_hash(password)
[show source]
    # File lib/rodauth/features/oauth_base.rb
458 def password_hash(password)
459   return super if features.include?(:login_password_requirements_base)
460 
461   BCrypt::Password.create(password, cost: BCrypt::Engine::DEFAULT_COST)
462 end
per_page_param(default_per_page)
[show source]
   # File lib/rodauth/features/oauth_management_base.rb
60 def per_page_param(default_per_page)
61   per_page = param_or_nil("per_page")
62 
63   return default_per_page unless per_page
64 
65   per_page = per_page.to_i
66 
67   return default_per_page if per_page <= 0
68 
69   [per_page, default_per_page].min
70 end
perform_logout_requests(logout_params)
[show source]
   # File lib/rodauth/features/oidc_backchannel_logout.rb
79 def perform_logout_requests(logout_params)
80   # performs logout requests sequentially
81   logout_params.each do |logout_url, logout_token|
82     http_request(logout_url, { "logout_token" => logout_token })
83   rescue StandardError
84     warn "failed to perform backchannel logout on #{logout_url}"
85   end
86 end
post_configure()
[show source]
    # File lib/rodauth/features/oauth_base.rb
292 def post_configure
293   super
294 
295   i18n_register(File.expand_path(File.join(__dir__, "..", "..", "..", "locales"))) if features.include?(:i18n)
296 
297   # all of the extensions below involve DB changes. Resource server mode doesn't use
298   # database functions for OAuth though.
299   return unless is_authorization_server?
300 
301   self.class.__send__(:include, Rodauth::OAuth::ExtendDatabase(db))
302 
303   # Check whether we can reutilize db entries for the same account / application pair
304   one_oauth_token_per_account = db.indexes(oauth_grants_table).values.any? do |definition|
305     definition[:unique] &&
306       definition[:columns] == oauth_grants_unique_columns
307   end
308 
309   self.class.send(:define_method, :__one_oauth_token_per_account) { one_oauth_token_per_account }
310 end
private_jwk?(jwk)
[show source]
    # File lib/rodauth/features/oauth_jwt_base.rb
170 def private_jwk?(jwk)
171   %w[d p q dp dq qi].any?(&jwk.method(:key?))
172 end
proxy_get_param(get_param_func, claims, claims_locales, additional_claims_info)
[show source]
    # File lib/rodauth/features/oidc.rb
649 def proxy_get_param(get_param_func, claims, claims_locales, additional_claims_info)
650   meth = method(get_param_func)
651   if meth.arity == 2
652     lambda do |account, param, cl = claims|
653       additional_info = additional_claims_info[param] || EMPTY_HASH
654       value = additional_info["value"] || meth[account, param]
655       value = nil if additional_info["values"] && additional_info["values"].include?(value)
656       cl[param] = value unless value.nil?
657     end
658   elsif claims_locales.nil?
659     lambda do |account, param, cl = claims|
660       additional_info = additional_claims_info[param] || EMPTY_HASH
661       value = additional_info["value"] || meth[account, param, nil]
662       value = nil if additional_info["values"] && additional_info["values"].include?(value)
663       cl[param] = value unless value.nil?
664     end
665   else
666     lambda do |account, param, cl = claims|
667       claims_values = claims_locales.map do |locale|
668         additional_info = additional_claims_info[param] || EMPTY_HASH
669         value = additional_info["value"] || meth[account, param, locale]
670         value = nil if additional_info["values"] && additional_info["values"].include?(value)
671         value
672       end.compact
673 
674       if claims_values.uniq.size == 1
675         cl[param] = claims_values.first
676       else
677         claims_locales.zip(claims_values).each do |locale, value|
678           cl["#{param}##{locale}"] = value if value
679         end
680       end
681     end
682   end
683 end
redirect_authorize_error(parameter, referer = request.referer || default_redirect)
[show source]
    # File lib/rodauth/features/oauth_authorize_base.rb
151 def redirect_authorize_error(parameter, referer = request.referer || default_redirect)
152   error_message = oauth_authorize_parameter_required(parameter: parameter)
153 
154   if accepts_json?
155     status_code = oauth_invalid_response_status
156 
157     throw_json_response_error(status_code, "invalid_request", error_message)
158   else
159     scope.instance_variable_set(:@error, error_message)
160     scope.instance_variable_set(:@back_url, referer)
161 
162     return_response(authorize_error_view)
163   end
164 end
redirect_logout_with_error(error_message = oauth_invalid_client_message)
[show source]
    # File lib/rodauth/features/oidc_rp_initiated_logout.rb
129 def redirect_logout_with_error(error_message = oauth_invalid_client_message)
130   set_notice_flash(error_message)
131   redirect(logout_redirect)
132 end
redirect_response_error(error_code, message = nil)
[show source]
    # File lib/rodauth/features/oauth_base.rb
765 def redirect_response_error(error_code, message = nil)
766   if accepts_json?
767     status_code = if respond_to?(:"oauth_#{error_code}_response_status")
768                     send(:"oauth_#{error_code}_response_status")
769                   else
770                     oauth_invalid_response_status
771                   end
772 
773     throw_json_response_error(status_code, error_code, message)
774   else
775     redirect_url = redirect_uri || request.referer || default_redirect
776     redirect_url = URI.parse(redirect_url)
777     params = response_error_params(error_code, message)
778     state = param_or_nil("state")
779     params["state"] = state if state
780     _redirect_response_error(redirect_url, params)
781   end
782 end
redirect_uri()
[show source]
    # File lib/rodauth/features/oauth_base.rb
212 def redirect_uri
213   param_or_nil("redirect_uri") || begin
214     return unless oauth_application
215 
216     redirect_uris = oauth_application[oauth_applications_redirect_uri_column].split(" ")
217     redirect_uris.size == 1 ? redirect_uris.first : nil
218   end
219 end
register_invalid_application_type_message(application_type)
[show source]
    # File lib/rodauth/features/oidc_dynamic_client_registration.rb
286 def register_invalid_application_type_message(application_type)
287   "The application type '#{application_type}' is not allowed."
288 end
register_invalid_client_metadata_message(key, value)
[show source]
    # File lib/rodauth/features/oauth_dynamic_client_registration.rb
356 def register_invalid_client_metadata_message(key, value)
357   "The value '#{value}' is not supported by this server for param '#{key}'."
358 end
register_invalid_contacts_message(contacts)
[show source]
    # File lib/rodauth/features/oauth_dynamic_client_registration.rb
360 def register_invalid_contacts_message(contacts)
361   "The contacts '#{contacts}' are not allowed by this server."
362 end
register_invalid_jwks_param_message(key1, key2)
[show source]
    # File lib/rodauth/features/oauth_dynamic_client_registration.rb
368 def register_invalid_jwks_param_message(key1, key2)
369   "The param '#{key1}' cannot be accepted together with param '#{key2}'."
370 end
register_invalid_param_message(key)
[show source]
    # File lib/rodauth/features/oauth_dynamic_client_registration.rb
352 def register_invalid_param_message(key)
353   "The param '#{key}' is not supported by this server."
354 end
register_invalid_response_type_for_grant_type_message(response_type, grant_type)
[show source]
    # File lib/rodauth/features/oauth_dynamic_client_registration.rb
384 def register_invalid_response_type_for_grant_type_message(response_type, grant_type)
385   "The grant type '#{grant_type}' must be registered for the response " \
386     "type '#{response_type}' to be allowed."
387 end
register_invalid_response_type_message(response_type)
[show source]
    # File lib/rodauth/features/oauth_dynamic_client_registration.rb
380 def register_invalid_response_type_message(response_type)
381   "The response type #{response_type} is not allowed by this server."
382 end
register_invalid_scopes_message(scopes)
[show source]
    # File lib/rodauth/features/oauth_dynamic_client_registration.rb
372 def register_invalid_scopes_message(scopes)
373   "The given scopes (#{scopes}) are not allowed by this server."
374 end
register_invalid_uri_message(uri)
[show source]
    # File lib/rodauth/features/oauth_dynamic_client_registration.rb
364 def register_invalid_uri_message(uri)
365   "The '#{uri}' URL is not allowed by this server."
366 end
register_oauth_invalid_grant_type_message(grant_type)
[show source]
    # File lib/rodauth/features/oauth_dynamic_client_registration.rb
376 def register_oauth_invalid_grant_type_message(grant_type)
377   "The grant type #{grant_type} is not allowed by this server."
378 end
register_required_param_message(key)
[show source]
    # File lib/rodauth/features/oauth_dynamic_client_registration.rb
348 def register_required_param_message(key)
349   "The param '#{key}' is required by this server."
350 end
register_throw_json_response_error(code, message)
[show source]
    # File lib/rodauth/features/oauth_dynamic_client_registration.rb
344 def register_throw_json_response_error(code, message)
345   throw_json_response_error(oauth_invalid_response_status, code, message)
346 end
request_object_encryption_alg_values_supported()
[show source]
    # File lib/rodauth/features/oidc.rb
267 def request_object_encryption_alg_values_supported
268   oauth_jwt_jwe_algorithms_supported
269 end
request_object_encryption_enc_values_supported()
[show source]
    # File lib/rodauth/features/oidc.rb
271 def request_object_encryption_enc_values_supported
272   oauth_jwt_jwe_encryption_methods_supported
273 end
request_object_signing_alg_values_supported()
[show source]
    # File lib/rodauth/features/oidc.rb
263 def request_object_signing_alg_values_supported
264   oauth_jwt_jws_algorithms_supported
265 end
require_acr_value(_acr)
[show source]
    # File lib/rodauth/features/oidc.rb
478 def require_acr_value(_acr)
479   true
480 end
require_acr_value_phr()
[show source]
    # File lib/rodauth/features/oidc.rb
465 def require_acr_value_phr
466   return false unless respond_to?(:require_two_factor_authenticated)
467 
468   require_two_factor_authenticated
469   true
470 end
require_acr_value_phrh()
[show source]
    # File lib/rodauth/features/oidc.rb
472 def require_acr_value_phrh
473   return false unless features.include?(:webauthn_login)
474 
475   require_acr_value_phr && two_factor_login_type_match?("webauthn")
476 end
require_authorizable_account()
[show source]
    # File lib/rodauth/features/oauth_base.rb
322 def require_authorizable_account
323   require_account
324 end
require_oauth_application()
[show source]
   # File lib/rodauth/features/oauth_assertion_base.rb
24 def require_oauth_application
25   if assertion_grant_type?
26     @oauth_application = __send__(:"require_oauth_application_from_#{assertion_grant_type}_assertion_issuer", param("assertion"))
27   elsif client_assertion_type?
28     @oauth_application = __send__(:"require_oauth_application_from_#{client_assertion_type}_assertion_subject",
29                                   param("client_assertion"))
30 
31     if (client_id = param_or_nil("client_id")) &&
32        client_id != @oauth_application[oauth_applications_client_id_column]
33       # If present, the value of the
34       # "client_id" parameter MUST identify the same client as is
35       # identified by the client assertion.
36       redirect_response_error("invalid_grant")
37     end
38   else
39     super
40   end
41 end
require_oauth_application_for_introspect()
[show source]
    # File lib/rodauth/features/oauth_token_introspection.rb
101 def require_oauth_application_for_introspect
102   token = (v = request.env["HTTP_AUTHORIZATION"]) && v[/\A *Bearer (.*)\Z/, 1]
103 
104   return require_oauth_application unless token
105 
106   oauth_application = current_oauth_application
107 
108   authorization_required unless oauth_application
109 
110   @oauth_application = oauth_application
111 end
require_oauth_application_from_account()
[show source]
    # File lib/rodauth/features/oauth_base.rb
412 def require_oauth_application_from_account
413   ds = db[oauth_applications_table]
414        .join(oauth_grants_table, Sequel[oauth_grants_table][oauth_grants_oauth_application_id_column] =>
415                                  Sequel[oauth_applications_table][oauth_applications_id_column])
416        .where(oauth_grant_by_token_ds(param("token")).opts.fetch(:where, true))
417        .where(Sequel[oauth_applications_table][oauth_applications_account_id_column] => account_id)
418 
419   @oauth_application = ds.qualify.first
420   return if @oauth_application
421 
422   set_redirect_error_flash revoke_unauthorized_account_error_flash
423   redirect request.referer || "/"
424 end
require_oauth_application_from_client_secret_basic(token)
[show source]
    # File lib/rodauth/features/oauth_base.rb
378 def require_oauth_application_from_client_secret_basic(token)
379   client_id, client_secret = Base64.decode64(token).split(":", 2)
380   authorization_required unless client_id
381   oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => client_id).first
382   authorization_required unless supports_auth_method?(oauth_application,
383                                                       "client_secret_basic") && secret_matches?(oauth_application, client_secret)
384   oauth_application
385 end
require_oauth_application_from_client_secret_jwt(client_id, assertion, alg)
[show source]
   # File lib/rodauth/features/oauth_jwt_bearer_grant.rb
57 def require_oauth_application_from_client_secret_jwt(client_id, assertion, alg)
58   oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => client_id).first
59   authorization_required unless oauth_application && supports_auth_method?(oauth_application, "client_secret_jwt")
60   client_secret = oauth_application[oauth_applications_client_secret_column]
61   claims = jwt_assertion(assertion, jws_key: client_secret, jws_algorithm: alg)
62   authorization_required unless claims && claims["iss"] == client_id
63   oauth_application
64 end
require_oauth_application_from_client_secret_post(client_id, client_secret)
[show source]
    # File lib/rodauth/features/oauth_base.rb
387 def require_oauth_application_from_client_secret_post(client_id, client_secret)
388   oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => client_id).first
389   authorization_required unless supports_auth_method?(oauth_application,
390                                                       "client_secret_post") && secret_matches?(oauth_application, client_secret)
391   oauth_application
392 end
require_oauth_application_from_jwt_bearer_assertion_issuer(assertion)
[show source]
   # File lib/rodauth/features/oauth_jwt_bearer_grant.rb
31 def require_oauth_application_from_jwt_bearer_assertion_issuer(assertion)
32   claims = jwt_assertion(assertion)
33 
34   return unless claims
35 
36   db[oauth_applications_table].where(
37     oauth_applications_client_id_column => claims["iss"]
38   ).first
39 end
require_oauth_application_from_jwt_bearer_assertion_subject(assertion)
[show source]
   # File lib/rodauth/features/oauth_jwt_bearer_grant.rb
41 def require_oauth_application_from_jwt_bearer_assertion_subject(assertion)
42   claims, header = jwt_decode_no_key(assertion)
43 
44   client_id = claims["sub"]
45 
46   case header["alg"]
47   when "none"
48     # do not accept jwts with no alg set
49     authorization_required
50   when /\AHS/
51     require_oauth_application_from_client_secret_jwt(client_id, assertion, header["alg"])
52   else
53     require_oauth_application_from_private_key_jwt(client_id, assertion)
54   end
55 end
require_oauth_application_from_none(client_id)
[show source]
    # File lib/rodauth/features/oauth_base.rb
394 def require_oauth_application_from_none(client_id)
395   oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => client_id).first
396   authorization_required unless supports_auth_method?(oauth_application, "none")
397   oauth_application
398 end
require_oauth_application_from_private_key_jwt(client_id, assertion)
[show source]
   # File lib/rodauth/features/oauth_jwt_bearer_grant.rb
66 def require_oauth_application_from_private_key_jwt(client_id, assertion)
67   oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => client_id).first
68   authorization_required unless oauth_application && supports_auth_method?(oauth_application, "private_key_jwt")
69   jwks = oauth_application_jwks(oauth_application)
70   claims = jwt_assertion(assertion, jwks: jwks)
71   authorization_required unless claims
72   oauth_application
73 end
require_oauth_application_from_saml2_bearer_assertion_issuer(assertion)
[show source]
   # File lib/rodauth/features/oauth_saml_bearer_grant.rb
43 def require_oauth_application_from_saml2_bearer_assertion_issuer(assertion)
44   parse_saml_assertion(assertion)
45 
46   return unless @saml_settings
47 
48   db[oauth_applications_table].where(
49     oauth_applications_id_column => @saml_settings[oauth_saml_settings_oauth_application_id_column]
50   ).first
51 end
require_oauth_application_from_saml2_bearer_assertion_subject(assertion)
[show source]
   # File lib/rodauth/features/oauth_saml_bearer_grant.rb
53 def require_oauth_application_from_saml2_bearer_assertion_subject(assertion)
54   parse_saml_assertion(assertion)
55 
56   return unless @assertion
57 
58   # 3.3.8 - For client authentication, the Subject MUST be the "client_id" of the OAuth client.
59   db[oauth_applications_table].where(
60     oauth_applications_client_id_column => @assertion.nameid
61   ).first
62 end
require_oauth_authorization(*scopes)
[show source]
    # File lib/rodauth/features/oauth_base.rb
271 def require_oauth_authorization(*scopes)
272   authorization_required unless authorization_token
273 
274   token_scopes = authorization_token[oauth_grants_scopes_column].split(oauth_scope_separator)
275 
276   authorization_required unless scopes.any? { |scope| token_scopes.include?(scope) }
277 end
require_signed_request_object?()
[show source]
    # File lib/rodauth/features/oauth_jwt_secured_authorization_request.rb
 95 def require_signed_request_object?
 96   return @require_signed_request_object if defined?(@require_signed_request_object)
 97 
 98   @require_signed_request_object = (oauth_application[oauth_applications_require_signed_request_object_column] if oauth_application)
 99   @require_signed_request_object = oauth_require_signed_request_object if @require_signed_request_object.nil?
100   @require_signed_request_object
101 end
requires_backchannel_logout_session?(oauth_application)
[show source]
    # File lib/rodauth/features/oidc_backchannel_logout.rb
106 def requires_backchannel_logout_session?(oauth_application)
107   (
108     oauth_application &&
109     oauth_application[oauth_applications_backchannel_logout_session_required_column]
110   ) || backchannel_logout_session_supported
111 end
requires_frontchannel_logout_session?(oauth_application)
[show source]
    # File lib/rodauth/features/oidc_frontchannel_logout.rb
120 def requires_frontchannel_logout_session?(oauth_application)
121   (
122     oauth_application &&
123     oauth_application[oauth_applications_frontchannel_logout_session_required_column]
124   ) || frontchannel_logout_session_supported
125 end
rescue_from_uniqueness_error(&block)
[show source]
    # File lib/rodauth/features/oauth_base.rb
326 def rescue_from_uniqueness_error(&block)
327   retries = oauth_unique_id_generation_retries
328   begin
329     transaction(savepoint: :only, &block)
330   rescue Sequel::UniqueConstraintViolation
331     redirect_response_error("already_in_use") if retries.zero?
332     retries -= 1
333     retry
334   end
335 end
resource_indicators()
[show source]
   # File lib/rodauth/features/oauth_resource_indicators.rb
11 def resource_indicators
12   return @resource_indicators if defined?(@resource_indicators)
13 
14   resources = param_or_nil("resource")
15 
16   return unless resources
17 
18   if json_request? || param_or_nil("request") # signed request
19     resources = Array(resources)
20   else
21     query = if request.form_data?
22               request.body.rewind
23               request.body.read
24             else
25               request.query_string
26             end
27     # resource query param does not conform to rack parsing rules
28     resources = URI.decode_www_form(query).each_with_object([]) do |(k, v), memo|
29       memo << v if k == "resource"
30     end
31   end
32 
33   @resource_indicators = resources
34 end
resource_owner_identifier(grant_or_claims)
[show source]
    # File lib/rodauth/features/oauth_token_introspection.rb
120 def resource_owner_identifier(grant_or_claims)
121   if (account_id = grant_or_claims[oauth_grants_account_id_column])
122     account_ds(account_id).select(login_column).first[login_column]
123   elsif (app_id = grant_or_claims[oauth_grants_oauth_application_id_column])
124     db[oauth_applications_table].where(oauth_applications_id_column => app_id)
125                                 .select(oauth_applications_name_column)
126                                 .first[oauth_applications_name_column]
127   elsif (subject = grant_or_claims["sub"])
128     # JWT
129     if subject == grant_or_claims["client_id"]
130       db[oauth_applications_table].where(oauth_applications_client_id_column => subject)
131                                   .select(oauth_applications_name_column)
132                                   .first[oauth_applications_name_column]
133     else
134       account_ds(subject).select(login_column).first[login_column]
135     end
136   end
137 end
resource_owner_params()
[show source]
    # File lib/rodauth/features/oauth_authorize_base.rb
127 def resource_owner_params
128   { oauth_grants_account_id_column => account_id }
129 end
resource_owner_params_from_jwt_claims(claims)
[show source]
   # File lib/rodauth/features/oauth_jwt_base.rb
70 def resource_owner_params_from_jwt_claims(claims)
71   { oauth_grants_account_id_column => claims["sub"] }
72 end
response_error_params(error_code, message = nil)
[show source]
    # File lib/rodauth/features/oauth_base.rb
794 def response_error_params(error_code, message = nil)
795   code = if respond_to?(:"oauth_#{error_code}_error_code")
796            send(:"oauth_#{error_code}_error_code")
797          else
798            error_code
799          end
800   payload = { "error" => code }
801   error_description = message
802   error_description ||= send(:"oauth_#{error_code}_message") if respond_to?(:"oauth_#{error_code}_message")
803   payload["error_description"] = error_description if error_description
804 
805   payload
806 end
return_response(body = nil)
[show source]
    # File lib/rodauth/features/oauth_base.rb
847 def return_response(body = nil)
848   response.write(body) if body
849   request.halt
850 end
revoke_oauth_grant()
[show source]
    # File lib/rodauth/features/oauth_token_revocation.rb
 82 def revoke_oauth_grant
 83   token = param("token")
 84 
 85   if param("token_type_hint") == "refresh_token"
 86     oauth_grant = oauth_grant_by_refresh_token(token)
 87     token_column = oauth_grants_refresh_token_column
 88   else
 89     oauth_grant = oauth_grant_by_token_ds(token).where(
 90       oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column]
 91     ).first
 92     token_column = oauth_grants_token_column
 93   end
 94 
 95   redirect_response_error("invalid_request") unless oauth_grant
 96 
 97   redirect_response_error("invalid_request") unless grant_from_application?(oauth_grant, oauth_application)
 98 
 99   update_params = { oauth_grants_revoked_at_column => Sequel::CURRENT_TIMESTAMP }
100 
101   ds = db[oauth_grants_table].where(oauth_grants_id_column => oauth_grant[oauth_grants_id_column])
102 
103   oauth_grant = __update_and_return__(ds, update_params)
104 
105   oauth_grant[token_column] = token
106   oauth_grant
107 
108   # If the particular
109   # token is a refresh token and the authorization server supports the
110   # revocation of access tokens, then the authorization server SHOULD
111   # also invalidate all access tokens based on the same authorization
112   # grant
113   #
114   # we don't need to do anything here, as we revalidate existing tokens
115 end
scopes()
[show source]
    # File lib/rodauth/features/oauth_base.rb
202 def scopes
203   scope = request.params["scope"]
204   case scope
205   when Array
206     scope
207   when String
208     scope.split(" ")
209   end
210 end
secret_hash(secret)
[show source]
    # File lib/rodauth/features/oauth_base.rb
442 def secret_hash(secret)
443   password_hash(secret)
444 end
secret_matches?(oauth_application, secret)
[show source]
    # File lib/rodauth/features/oauth_base.rb
426 def secret_matches?(oauth_application, secret)
427   if oauth_applications_client_secret_hash_column
428     BCrypt::Password.new(oauth_application[oauth_applications_client_secret_hash_column]) == secret
429   else
430     oauth_application[oauth_applications_client_secret_column] == secret
431   end
432 end
session_id_in_claims(oauth_grant, claims)
[show source]
   # File lib/rodauth/features/oidc_logout_base.rb
24 def session_id_in_claims(oauth_grant, claims)
25   oauth_application_in_visited_sites do
26     if should_set_sid_in_visited_sites?(oauth_application)
27       # id_token or token response types
28       session_id = if (sess = session[session_id_session_key])
29                      compute_hmac(sess)
30                    else
31                      # code response type
32                      ds = db[active_sessions_table]
33                      ds = ds.where(active_sessions_account_id_column => oauth_grant[oauth_grants_account_id_column])
34                      ds = ds.order(Sequel.desc(active_sessions_last_use_column))
35                      ds.get(active_sessions_session_id_column)
36                    end
37 
38       claims[:sid] = session_id
39     end
40   end
41 end
set_client_secret(params, secret)
[show source]
    # File lib/rodauth/features/oauth_base.rb
434 def set_client_secret(params, secret)
435   if oauth_applications_client_secret_hash_column
436     params[oauth_applications_client_secret_hash_column] = secret_hash(secret)
437   else
438     params[oauth_applications_client_secret_column] = secret
439   end
440 end
should_set_oauth_application_in_visited_sites?()
[show source]
    # File lib/rodauth/features/oidc_backchannel_logout.rb
 98 def should_set_oauth_application_in_visited_sites?
 99   true
100 end
should_set_sid_in_visited_sites?(oauth_application)
[show source]
    # File lib/rodauth/features/oidc_backchannel_logout.rb
102 def should_set_sid_in_visited_sites?(oauth_application)
103   super || requires_backchannel_logout_session?(oauth_application)
104 end
sid_in_visited_sites()
[show source]
   # File lib/rodauth/features/oidc_logout_base.rb
56 def sid_in_visited_sites
57   return unless should_set_oauth_application_in_visited_sites?
58 
59   oauth_application_in_visited_sites do
60     if should_set_sid_in_visited_sites?(oauth_application)
61       ds = active_sessions_ds.order(Sequel.desc(active_sessions_last_use_column))
62 
63       ds.get(active_sessions_session_id_column)
64     end
65   end
66 end
store_token(grant_params, update_params = {})
[show source]
    # File lib/rodauth/features/oauth_base.rb
526 def store_token(grant_params, update_params = {})
527   ds = db[oauth_grants_table]
528 
529   if __one_oauth_token_per_account
530 
531     to_update_if_null = [
532       oauth_grants_token_column,
533       oauth_grants_token_hash_column,
534       oauth_grants_refresh_token_column,
535       oauth_grants_refresh_token_hash_column
536     ].compact.map do |attribute|
537       [
538         attribute,
539         (
540           if ds.respond_to?(:supports_insert_conflict?) && ds.supports_insert_conflict?
541             Sequel.function(:coalesce, Sequel[oauth_grants_table][attribute], Sequel[:excluded][attribute])
542           else
543             Sequel.function(:coalesce, Sequel[oauth_grants_table][attribute], update_params[attribute])
544           end
545         )
546       ]
547     end
548 
549     token = __insert_or_update_and_return__(
550       ds,
551       oauth_grants_id_column,
552       oauth_grants_unique_columns,
553       grant_params.merge(update_params),
554       Sequel.expr(Sequel[oauth_grants_table][oauth_grants_expires_in_column]) > Sequel::CURRENT_TIMESTAMP,
555       Hash[to_update_if_null]
556     )
557 
558     # if the previous operation didn't return a row, it means that the conditions
559     # invalidated the update, and the existing token is still valid.
560     token || ds.where(
561       oauth_grants_account_id_column => update_params[oauth_grants_account_id_column],
562       oauth_grants_oauth_application_id_column => update_params[oauth_grants_oauth_application_id_column]
563     ).first
564   else
565 
566     if oauth_reuse_access_token
567       unique_conds = Hash[oauth_grants_unique_columns.map { |column| [column, update_params[column]] }]
568       valid_token_ds = valid_oauth_grant_ds(unique_conds)
569       if oauth_grants_token_hash_column
570         valid_token_ds.exclude(oauth_grants_token_hash_column => nil)
571       else
572         valid_token_ds.exclude(oauth_grants_token_column => nil)
573       end
574 
575       valid_token = valid_token_ds.first
576 
577       return valid_token if valid_token
578     end
579 
580     if grant_params[oauth_grants_id_column]
581       __update_and_return__(ds.where(oauth_grants_id_column => grant_params[oauth_grants_id_column]), update_params)
582     else
583       __insert_and_return__(ds, oauth_grants_id_column, grant_params.merge(update_params))
584     end
585   end
586 end
supported_grant_type?(grant_type, expected_grant_type = grant_type)
[show source]
    # File lib/rodauth/features/oauth_base.rb
708 def supported_grant_type?(grant_type, expected_grant_type = grant_type)
709   return false unless grant_type == expected_grant_type
710 
711   grant_types_supported = if oauth_application[oauth_applications_grant_types_column]
712                             oauth_application[oauth_applications_grant_types_column].split(/ +/)
713                           else
714                             oauth_grant_types_supported
715                           end
716 
717   grant_types_supported.include?(grant_type)
718 end
supported_request_uri?(request_uri, oauth_application)
[show source]
   # File lib/rodauth/features/oauth_jwt_secured_authorization_request.rb
87 def supported_request_uri?(request_uri, oauth_application)
88   return false unless check_valid_uri?(request_uri)
89 
90   request_uris = oauth_application[oauth_applications_request_uris_column]
91 
92   request_uris.nil? || request_uris.split(oauth_scope_separator).one? { |uri| request_uri.start_with?(uri) }
93 end
supported_response_mode?(response_mode, expected_response_mode = response_mode)
[show source]
    # File lib/rodauth/features/oauth_base.rb
734 def supported_response_mode?(response_mode, expected_response_mode = response_mode)
735   return false unless response_mode == expected_response_mode
736 
737   response_modes_supported = if oauth_application[oauth_applications_response_modes_column]
738                                oauth_application[oauth_applications_response_modes_column].split(/ +/)
739                              else
740                                oauth_response_modes_supported
741                              end
742 
743   response_modes_supported.include?(response_mode)
744 end
supported_response_type?(response_type, expected_response_type = response_type)
[show source]
    # File lib/rodauth/features/oauth_base.rb
720 def supported_response_type?(response_type, expected_response_type = response_type)
721   return false unless response_type == expected_response_type
722 
723   response_types_supported = if oauth_application[oauth_applications_response_types_column]
724                                oauth_application[oauth_applications_response_types_column].split(/ +/)
725                              else
726                                oauth_response_types_supported
727                              end
728 
729   response_types = response_type.split(/ +/)
730 
731   (response_types - response_types_supported).empty?
732 end
supports_auth_method?(oauth_application, auth_method)
[show source]
    # File lib/rodauth/features/oauth_base.rb
400 def supports_auth_method?(oauth_application, auth_method)
401   return false unless oauth_application
402 
403   supported_auth_methods = if oauth_application[oauth_applications_token_endpoint_auth_method_column]
404                              oauth_application[oauth_applications_token_endpoint_auth_method_column].split(/ +/)
405                            else
406                              oauth_token_endpoint_auth_methods_supported
407                            end
408 
409   supported_auth_methods.include?(auth_method)
410 end
template_path(page)
[show source]
    # File lib/rodauth/features/oauth_base.rb
350 def template_path(page)
351   path = File.join(File.dirname(__FILE__), "../../../templates", "#{page}.str")
352   return super unless File.exist?(path)
353 
354   path
355 end
throw_json_response_error(status, error_code, message = nil)
[show source]
    # File lib/rodauth/features/oauth_base.rb
823 def throw_json_response_error(status, error_code, message = nil)
824   set_response_error_status(status)
825   payload = response_error_params(error_code, message)
826   json_payload = _json_response_body(payload)
827   response["Content-Type"] ||= json_response_content_type
828   response["WWW-Authenticate"] = www_authenticate_header(payload) if status == 401
829   return_response(json_payload)
830 end
translate(key, default, args = EMPTY_HASH)

override

[show source]
    # File lib/rodauth/features/oauth_base.rb
284 def translate(key, default, args = EMPTY_HASH)
285   return i18n_translate(key, default, **args) if features.include?(:i18n)
286   # do not attempt to translate by default
287   return default if args.nil?
288 
289   default % args
290 end
try_acr_values()
[show source]
    # File lib/rodauth/features/oidc.rb
446 def try_acr_values
447   return unless (acr_values = param_or_nil("acr_values"))
448 
449   acr_values.split(" ").each do |acr_value|
450     next unless oauth_acr_values_supported.include?(acr_value)
451 
452     case acr_value
453     when "phr"
454       return acr_value if require_acr_value_phr
455     when "phrh"
456       return acr_value if require_acr_value_phrh
457     else
458       return acr_value if require_acr_value(acr_value)
459     end
460   end
461 
462   nil
463 end
try_approval_prompt()
[show source]
    # File lib/rodauth/features/oauth_authorize_base.rb
135 def try_approval_prompt
136   approval_prompt = param_or_nil("approval_prompt")
137 
138   return unless approval_prompt && approval_prompt == "auto"
139 
140   return if db[oauth_grants_table].where(resource_owner_params).where(
141     oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
142     oauth_grants_redirect_uri_column => redirect_uri,
143     oauth_grants_scopes_column => scopes.join(oauth_scope_separator),
144     oauth_grants_access_type_column => "online"
145   ).none?
146 
147   # if there's a previous oauth grant for the params combo, it means that this user has approved before.
148   request.env["REQUEST_METHOD"] = "POST"
149 end
try_prompt()

this executes before checking for a logged in account

[show source]
    # File lib/rodauth/features/oidc.rb
393 def try_prompt
394   return unless (prompt = param_or_nil("prompt"))
395 
396   case prompt
397   when "none"
398     return unless request.get?
399 
400     redirect_response_error("login_required") unless logged_in?
401 
402     require_account
403 
404     redirect_response_error("interaction_required") unless oidc_authorize_on_prompt_none?(account_from_session)
405 
406     request.env["REQUEST_METHOD"] = "POST"
407   when "login"
408     return unless request.get?
409 
410     if logged_in? && request.cookies[oauth_prompt_login_cookie_key] == "login"
411       ::Rack::Utils.delete_cookie_header!(response.headers, oauth_prompt_login_cookie_key, oauth_prompt_login_cookie_options)
412       return
413     end
414 
415     # logging out
416     clear_session
417     set_session_value(login_redirect_session_key, request.fullpath)
418 
419     login_cookie_opts = Hash[oauth_prompt_login_cookie_options]
420     login_cookie_opts[:value] = "login"
421     if oauth_prompt_login_interval
422       login_cookie_opts[:expires] = convert_timestamp(Time.now + oauth_prompt_login_interval) # 15 minutes
423     end
424     ::Rack::Utils.set_cookie_header!(response.headers, oauth_prompt_login_cookie_key, login_cookie_opts)
425 
426     redirect require_login_redirect
427   when "consent"
428     return unless request.post?
429 
430     require_account
431 
432     sc = scopes || []
433 
434     redirect_response_error("consent_required") if sc.empty?
435 
436   when "select-account"
437     return unless request.get?
438 
439     # only works if select_account plugin is available
440     require_select_account if respond_to?(:require_select_account)
441   else
442     redirect_response_error("invalid_request")
443   end
444 end
use_date_arithmetic?()
[show source]
    # File lib/rodauth/features/oauth_base.rb
279 def use_date_arithmetic?
280   true
281 end
userinfo_encryption_alg_values_supported()
[show source]
    # File lib/rodauth/features/oidc.rb
255 def userinfo_encryption_alg_values_supported
256   oauth_jwt_jwe_algorithms_supported
257 end
userinfo_encryption_enc_values_supported()
[show source]
    # File lib/rodauth/features/oidc.rb
259 def userinfo_encryption_enc_values_supported
260   oauth_jwt_jwe_encryption_methods_supported
261 end
userinfo_signing_alg_values_supported()
[show source]
    # File lib/rodauth/features/oidc.rb
251 def userinfo_signing_alg_values_supported
252   oauth_jwt_jws_algorithms_supported
253 end
valid_dpop_nonce?(nonce)
[show source]
    # File lib/rodauth/features/oauth_dpop.rb
372 def valid_dpop_nonce?(nonce)
373   nonce_claims = jwt_decode(nonce, verify_aud: false, verify_jti: false)
374 
375   return false unless nonce_claims
376 
377   jti = nonce_claims["jti"]
378 
379   return false unless jti
380 
381   return false unless jti == Digest::SHA256.hexdigest("#{request.request_method}:#{request.url}:#{nonce_claims['iat']}")
382 
383   return false unless nonce_claims.key?("aud")
384 
385   htm, htu = nonce_claims["aud"].split(":", 2)
386 
387   htm == request.request_method && htu == request.url
388 end
valid_dpop_proof_required(error_code = "invalid_dpop_proof")
[show source]
    # File lib/rodauth/features/oauth_dpop.rb
313 def valid_dpop_proof_required(error_code = "invalid_dpop_proof")
314   if @dpop_access_token
315     # protected resource access
316     throw_json_response_error(401, error_code)
317   else
318     redirect_response_error(error_code)
319   end
320 end
valid_locked_oauth_grant(grant_params = nil)
[show source]
    # File lib/rodauth/features/oauth_base.rb
588 def valid_locked_oauth_grant(grant_params = nil)
589   oauth_grant = valid_oauth_grant_ds(grant_params).for_update.first
590 
591   redirect_response_error("invalid_grant") unless oauth_grant
592 
593   oauth_grant
594 end
valid_oauth_grant_ds(grant_params = nil)
[show source]
    # File lib/rodauth/features/oauth_base.rb
596 def valid_oauth_grant_ds(grant_params = nil)
597   ds = db[oauth_grants_table]
598        .where(Sequel[oauth_grants_table][oauth_grants_revoked_at_column] => nil)
599        .where(Sequel.expr(Sequel[oauth_grants_table][oauth_grants_expires_in_column]) >= Sequel::CURRENT_TIMESTAMP)
600   ds = ds.where(grant_params) if grant_params
601 
602   ds
603 end
validate_ath(claims, access_token)
[show source]
    # File lib/rodauth/features/oauth_dpop.rb
210 def validate_ath(claims, access_token)
211   # When the DPoP proof is used in conjunction with the presentation of an access token in protected resource access
212   # the DPoP proof MUST also contain the following claim
213   ath = claims["ath"]
214 
215   redirect_response_error("invalid_token") unless ath
216 
217   # The value MUST be the result of a base64url encoding of the SHA-256 hash of the ASCII encoding of
218   # the associated access token's value.
219   redirect_response_error("invalid_token") unless ath == Base64.urlsafe_encode64(Digest::SHA256.digest(access_token), padding: false)
220 end
validate_authorize_params()
[show source]
   # File lib/rodauth/features/oauth_authorization_code_grant.rb
25 def validate_authorize_params
26   super
27 
28   response_mode = param_or_nil("response_mode")
29 
30   return unless response_mode
31 
32   redirect_response_error("invalid_request") unless oauth_response_modes_supported.include?(response_mode)
33 
34   response_type = param_or_nil("response_type")
35 
36   return unless response_type.nil? || response_type == "code"
37 
38   redirect_response_error("invalid_request") unless oauth_response_modes_for_code_supported.include?(response_mode)
39 end
validate_client_registration_params(request_params = request.params)
[show source]
    # File lib/rodauth/features/oauth_dynamic_client_registration.rb
121 def validate_client_registration_params(request_params = request.params)
122   @oauth_application_params = request_params.each_with_object({}) do |(key, value), params|
123     case key
124     when "redirect_uris"
125       if value.is_a?(Array)
126         value = value.each do |uri|
127           unless check_valid_no_fragment_uri?(uri)
128             register_throw_json_response_error("invalid_redirect_uri",
129                                                register_invalid_uri_message(uri))
130           end
131         end.join(" ")
132       else
133         register_throw_json_response_error("invalid_redirect_uri", register_invalid_uri_message(value))
134       end
135       key = oauth_applications_redirect_uri_column
136     when "token_endpoint_auth_method"
137       unless oauth_token_endpoint_auth_methods_supported.include?(value)
138         register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(key, value))
139       end
140       # verify if in range
141       key = oauth_applications_token_endpoint_auth_method_column
142     when "grant_types"
143       if value.is_a?(Array)
144         value = value.each do |grant_type|
145           unless oauth_grant_types_supported.include?(grant_type)
146             register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(grant_type, value))
147           end
148         end.join(" ")
149       else
150         register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(key, value))
151       end
152       key = oauth_applications_grant_types_column
153     when "response_types"
154       if value.is_a?(Array)
155         grant_types = request_params["grant_types"] || %w[authorization_code]
156         value = value.each do |response_type|
157           unless oauth_response_types_supported.include?(response_type)
158             register_throw_json_response_error("invalid_client_metadata",
159                                                register_invalid_response_type_message(response_type))
160           end
161 
162           validate_client_registration_response_type(response_type, grant_types)
163         end.join(" ")
164       else
165         register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(key, value))
166       end
167       key = oauth_applications_response_types_column
168       # verify if in range and match grant type
169     when "client_uri", "logo_uri", "tos_uri", "policy_uri", "jwks_uri"
170       register_throw_json_response_error("invalid_client_metadata", register_invalid_uri_message(value)) unless check_valid_uri?(value)
171       case key
172       when "client_uri"
173         key = oauth_applications_homepage_url_column
174       when "jwks_uri"
175         if request_params.key?("jwks")
176           register_throw_json_response_error("invalid_client_metadata",
177                                              register_invalid_jwks_param_message(key, "jwks"))
178         end
179       end
180       key = __send__(:"oauth_applications_#{key}_column")
181     when "jwks"
182       register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(value)) unless value.is_a?(Hash)
183       if request_params.key?("jwks_uri")
184         register_throw_json_response_error("invalid_client_metadata",
185                                            register_invalid_jwks_param_message(key, "jwks_uri"))
186       end
187 
188       key = oauth_applications_jwks_column
189       value = JSON.dump(value)
190     when "scope"
191       register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(value)) unless value.is_a?(String)
192       scopes = value.split(" ") - oauth_application_scopes
193       register_throw_json_response_error("invalid_client_metadata", register_invalid_scopes_message(value)) unless scopes.empty?
194       key = oauth_applications_scopes_column
195       # verify if in range
196     when "contacts"
197       register_throw_json_response_error("invalid_client_metadata", register_invalid_contacts_message(value)) unless value.is_a?(Array)
198       value = value.join(" ")
199       key = oauth_applications_contacts_column
200     when "client_name"
201       register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(value)) unless value.is_a?(String)
202       key = oauth_applications_name_column
203     when "dpop_bound_access_tokens"
204       unless respond_to?(:oauth_applications_dpop_bound_access_tokens_column)
205         register_throw_json_response_error("invalid_client_metadata",
206                                            register_invalid_param_message(key))
207       end
208       request_params[key] = value = convert_to_boolean(key, value)
209 
210       key = oauth_applications_dpop_bound_access_tokens_column
211     when "require_signed_request_object"
212       unless respond_to?(:oauth_applications_require_signed_request_object_column)
213         register_throw_json_response_error("invalid_client_metadata",
214                                            register_invalid_param_message(key))
215       end
216       request_params[key] = value = convert_to_boolean(key, value)
217 
218       key = oauth_applications_require_signed_request_object_column
219     when "require_pushed_authorization_requests"
220       unless respond_to?(:oauth_applications_require_pushed_authorization_requests_column)
221         register_throw_json_response_error("invalid_client_metadata",
222                                            register_invalid_param_message(key))
223       end
224       request_params[key] = value = convert_to_boolean(key, value)
225 
226       key = oauth_applications_require_pushed_authorization_requests_column
227     when "tls_client_certificate_bound_access_tokens"
228       property = :oauth_applications_tls_client_certificate_bound_access_tokens_column
229       register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key)) unless respond_to?(property)
230 
231       request_params[key] = value = convert_to_boolean(key, value)
232 
233       key = oauth_applications_tls_client_certificate_bound_access_tokens_column
234     when /\Atls_client_auth_/
235       unless respond_to?(:"oauth_applications_#{key}_column")
236         register_throw_json_response_error("invalid_client_metadata",
237                                            register_invalid_param_message(key))
238       end
239 
240       #  client using the tls_client_auth authentication method MUST use exactly one of the below metadata
241       # parameters to indicate the certificate subject value that the authorization server is to expect when
242       # authenticating the respective client.
243       if params.any? { |k, _| k.to_s.start_with?("tls_client_auth_") }
244         register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
245       end
246 
247       key = __send__(:"oauth_applications_#{key}_column")
248     else
249       if respond_to?(:"oauth_applications_#{key}_column")
250         if PROTECTED_APPLICATION_ATTRIBUTES.include?(key)
251           register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
252         end
253         property = :"oauth_applications_#{key}_column"
254         key = __send__(property)
255       elsif !db[oauth_applications_table].columns.include?(key.to_sym)
256         register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
257       end
258     end
259     params[key] = value
260   end
261 end
validate_client_registration_response_type(response_type, grant_types)
[show source]
    # File lib/rodauth/features/oauth_dynamic_client_registration.rb
263 def validate_client_registration_response_type(response_type, grant_types)
264   case response_type
265   when "code"
266     unless grant_types.include?("authorization_code")
267       register_throw_json_response_error("invalid_client_metadata",
268                                          register_invalid_response_type_for_grant_type_message(response_type,
269                                                                                                "authorization_code"))
270     end
271   when "token"
272     unless grant_types.include?("implicit")
273       register_throw_json_response_error("invalid_client_metadata",
274                                          register_invalid_response_type_for_grant_type_message(response_type, "implicit"))
275     end
276   when "none"
277     if grant_types.include?("implicit") || grant_types.include?("authorization_code")
278       register_throw_json_response_error("invalid_client_metadata", register_invalid_response_type_message(response_type))
279     end
280   end
281 end
validate_dpop_jwt_claims(claims)
[show source]
    # File lib/rodauth/features/oauth_dpop.rb
192 def validate_dpop_jwt_claims(claims)
193   jti = claims["jti"]
194 
195   unless jti && jti == Digest::SHA256.hexdigest("#{request.request_method}:#{request.url}:#{claims['iat']}")
196     redirect_response_error("invalid_dpop_jti")
197   end
198 
199   htm = claims["htm"]
200 
201   # 4.3.8 - Check if htm matches the request method
202   redirect_response_error("invalid_dpop_htm") unless htm && htm == request.request_method
203 
204   htu = claims["htu"]
205 
206   # 4.3.9 - Check if htu matches the request URL
207   redirect_response_error("invalid_dpop_htu") unless htu && htu == request.url
208 end
validate_dpop_proof_usage(claims)
[show source]
    # File lib/rodauth/features/oauth_dpop.rb
137 def validate_dpop_proof_usage(claims)
138   jti = claims["jti"]
139 
140   dpop_proof = __insert_or_do_nothing_and_return__(
141     db[oauth_dpop_proofs_table],
142     oauth_dpop_proofs_jti_column,
143     [oauth_dpop_proofs_jti_column],
144     oauth_dpop_proofs_jti_column => Digest::SHA256.hexdigest(jti),
145     oauth_dpop_proofs_first_use_column => Sequel::CURRENT_TIMESTAMP
146   )
147 
148   return unless (Time.now - dpop_proof[oauth_dpop_proofs_first_use_column]) > oauth_dpop_proof_expires_in
149 
150   redirect_response_error("invalid_dpop_proof")
151 end
validate_dpop_token(dpop)
[show source]
    # File lib/rodauth/features/oauth_dpop.rb
118 def validate_dpop_token(dpop)
119   # 4.3.2
120   @dpop_claims = dpop_decode(dpop)
121   redirect_response_error("invalid_dpop_proof") unless @dpop_claims
122 
123   validate_dpop_jwt_claims(@dpop_claims)
124 
125   # 4.3.10
126   validate_nonce(@dpop_claims)
127 
128   # 11.1
129   # To prevent multiple uses of the same DPoP proof, servers can store, in the
130   # context of the target URI, the jti value of each DPoP proof for the time window
131   # in which the respective DPoP proof JWT would be accepted.
132   validate_dpop_proof_usage(@dpop_claims)
133 
134   @dpop_claims
135 end
validate_introspect_params(token_hint_types = %w[access_token refresh_token].freeze)

Token introspect

[show source]
   # File lib/rodauth/features/oauth_token_introspection.rb
50 def validate_introspect_params(token_hint_types = %w[access_token refresh_token].freeze)
51   # check if valid token hint type
52   if param_or_nil("token_type_hint") && !token_hint_types.include?(param("token_type_hint"))
53     redirect_response_error("unsupported_token_type")
54   end
55 
56   redirect_response_error("invalid_request") unless param_or_nil("token")
57 end
validate_nonce(claims)
[show source]
    # File lib/rodauth/features/oauth_dpop.rb
222 def validate_nonce(claims)
223   nonce = claims["nonce"]
224 
225   unless nonce
226     dpop_nonce_required(claims) if dpop_use_nonce?
227 
228     return
229   end
230 
231   dpop_nonce_required(claims) unless valid_dpop_nonce?(nonce)
232 end
validate_oauth_application_params()
[show source]
    # File lib/rodauth/features/oauth_application_management.rb
156 def validate_oauth_application_params
157   oauth_application_params.each do |key, value|
158     if key == oauth_application_homepage_url_param
159 
160       set_field_error(key, invalid_url_message) unless check_valid_uri?(value)
161 
162     elsif key == oauth_application_redirect_uri_param
163 
164       if value.respond_to?(:each)
165         value.each do |uri|
166           next if uri.empty?
167 
168           set_field_error(key, invalid_url_message) unless check_valid_no_fragment_uri?(uri)
169         end
170       else
171         set_field_error(key, invalid_url_message) unless check_valid_no_fragment_uri?(value)
172       end
173     elsif key == oauth_application_scopes_param
174 
175       value.each do |scope|
176         set_field_error(key, oauth_invalid_scope_message) unless oauth_application_scopes.include?(scope)
177       end
178     end
179   end
180 
181   throw :rodauth_error if @field_errors && !@field_errors.empty?
182 end
validate_oidc_logout_params()

Logout

[show source]
    # File lib/rodauth/features/oidc_rp_initiated_logout.rb
120 def validate_oidc_logout_params
121   # check if valid token hint type
122   return unless (redirect_uri = param_or_nil("post_logout_redirect_uri"))
123 
124   return if check_valid_no_fragment_uri?(redirect_uri)
125 
126   redirect_logout_with_error(oauth_invalid_client_message)
127 end
validate_par_params()
[show source]
    # File lib/rodauth/features/oauth_dpop.rb
104 def validate_par_params
105   super
106 
107   return unless (dpop = fetch_dpop_token)
108 
109   validate_dpop_token(dpop)
110 
111   if (dpop_jkt = param_or_nil("dpop_jkt"))
112     redirect_response_error("invalid_request") if dpop_jkt != @dpop_thumbprint
113   else
114     request.params["dpop_jkt"] = @dpop_thumbprint
115   end
116 end
validate_pkce_challenge_params()
[show source]
   # File lib/rodauth/features/oauth_pkce.rb
60 def validate_pkce_challenge_params
61   if param_or_nil("code_challenge")
62 
63     challenge_method = param_or_nil("code_challenge_method")
64     redirect_response_error("code_challenge_required") unless oauth_pkce_challenge_method == challenge_method
65   else
66     return unless oauth_require_pkce
67 
68     redirect_response_error("code_challenge_required")
69   end
70 end
validate_revoke_params(token_hint_types = %w[access_token refresh_token].freeze)
[show source]
   # File lib/rodauth/features/oauth_token_revocation.rb
57 def validate_revoke_params(token_hint_types = %w[access_token refresh_token].freeze)
58   token_hint = param_or_nil("token_type_hint")
59 
60   if features.include?(:oauth_jwt) && oauth_jwt_access_tokens && (!token_hint || token_hint == "access_token")
61     # JWT access tokens can't be revoked
62     throw(:rodauth_error)
63   end
64 
65   # check if valid token hint type
66   redirect_response_error("unsupported_token_type") if token_hint && !token_hint_types.include?(token_hint)
67 
68   redirect_response_error("invalid_request") unless param_or_nil("token")
69 end
validate_token_params()
[show source]
   # File lib/rodauth/features/oauth_assertion_base.rb
18 def validate_token_params
19   return super unless assertion_grant_type?
20 
21   redirect_response_error("invalid_grant") unless param_or_nil("assertion")
22 end
verify_access_token_headers(headers)
[show source]
   # File lib/rodauth/features/oauth_jwt.rb
59 def verify_access_token_headers(headers)
60   headers["typ"] == "at+jwt"
61 end
verify_aud(expected_aud, aud)
[show source]
    # File lib/rodauth/features/oauth_jwt_base.rb
111 def verify_aud(expected_aud, aud)
112   expected_aud == aud
113 end
verify_dpop_jwt_headers(headers)
[show source]
    # File lib/rodauth/features/oauth_dpop.rb
171 def verify_dpop_jwt_headers(headers)
172   # 4.3.4 - A field with the value dpop+jwt
173   return false unless headers["typ"] == "dpop+jwt"
174 
175   # 4.3.5 - It MUST NOT be none or an identifier for a symmetric algorithm
176   alg = headers["alg"]
177   return false unless alg && oauth_dpop_signing_alg_values_supported.include?(alg)
178 
179   dpop_jwk = headers["jwk"]
180 
181   return false unless dpop_jwk
182 
183   # 4.3.7 - It MUST NOT contain a private key.
184   return false if private_jwk?(dpop_jwk)
185 
186   # store thumbprint for future assertions
187   @dpop_thumbprint = jwk_thumbprint(dpop_jwk)
188 
189   true
190 end
verify_jti(jti, claims)
[show source]
    # File lib/rodauth/features/oauth_jwt_base.rb
107 def verify_jti(jti, claims)
108   generate_jti(claims) == jti
109 end
www_authenticate_header(*)
[show source]
    # File lib/rodauth/features/oauth_base.rb
832 def www_authenticate_header(*)
833   oauth_token_type.capitalize
834 end