lib/rodauth/features/oidc.rb in rodauth-oauth-0.4.3 vs lib/rodauth/features/oidc.rb in rodauth-oauth-0.5.0
- old
+ new
@@ -12,10 +12,11 @@
}.freeze
VALID_METADATA_KEYS = %i[
issuer
authorization_endpoint
+ end_session_endpoint
token_endpoint
userinfo_endpoint
jwks_uri
registration_endpoint
scopes_supported
@@ -73,10 +74,14 @@
auth_value_method :oauth_prompt_login_cookie_key, "_rodauth_oauth_prompt_login"
auth_value_method :oauth_prompt_login_cookie_options, {}.freeze
auth_value_method :oauth_prompt_login_interval, 5 * 60 * 60 # 5 minutes
+ # logout
+ auth_value_method :oauth_applications_post_logout_redirect_uri_column, :post_logout_redirect_uri
+ auth_value_method :use_rp_initiated_logout?, false
+
auth_value_methods(:get_oidc_param, :get_additional_param)
# /userinfo
route(:userinfo) do |r|
next unless is_authorization_server?
@@ -106,14 +111,85 @@
throw_json_response_error(authorization_required_error_status, "invalid_token")
end
end
- def openid_configuration(issuer = nil)
+ # /oidc-logout
+ route(:oidc_logout) do |r|
+ next unless use_rp_initiated_logout?
+
+ before_oidc_logout_route
+ require_authorizable_account
+
+ # OpenID Providers MUST support the use of the HTTP GET and POST methods
+ r.on method: %i[get post] do
+ catch_error do
+ validate_oidc_logout_params
+
+ #
+ # why this is done:
+ #
+ # we need to decode the id token in order to get the application, because, if the
+ # signing key is application-specific, we don't know how to verify the signature
+ # beforehand. Hence, we have to do it twice: decode-and-do-not-verify, initialize
+ # the @oauth_application, and then decode-and-verify.
+ #
+ oauth_token = jwt_decode(param("id_token_hint"), verify_claims: false)
+ oauth_application_id = oauth_token["client_id"]
+
+ # check whether ID token belongs to currently logged-in user
+ redirect_response_error("invalid_request") unless oauth_token["sub"] == jwt_subject(
+ oauth_tokens_account_id_column => account_id,
+ oauth_tokens_oauth_application_id_column => oauth_application_id
+ )
+
+ # When an id_token_hint parameter is present, the OP MUST validate that it was the issuer of the ID Token.
+ redirect_response_error("invalid_request") unless oauth_token && oauth_token["iss"] == issuer
+
+ # now let's logout from IdP
+ transaction do
+ before_logout
+ logout
+ after_logout
+ end
+
+ if (post_logout_redirect_uri = param_or_nil("post_logout_redirect_uri"))
+ catch(:default_logout_redirect) do
+ oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => oauth_token["client_id"]).first
+
+ throw(:default_logout_redirect) unless oauth_application
+
+ post_logout_redirect_uris = oauth_application[oauth_applications_post_logout_redirect_uri_column].split(" ")
+
+ throw(:default_logout_redirect) unless post_logout_redirect_uris.include?(post_logout_redirect_uri)
+
+ if (state = param_or_nil("state"))
+ post_logout_redirect_uri = URI(post_logout_redirect_uri)
+ params = ["state=#{state}"]
+ params << post_logout_redirect_uri.query if post_logout_redirect_uri.query
+ post_logout_redirect_uri.query = params.join("&")
+ post_logout_redirect_uri = post_logout_redirect_uri.to_s
+ end
+
+ redirect(post_logout_redirect_uri)
+ end
+
+ end
+
+ # regular logout procedure
+ set_notice_flash(logout_notice_flash)
+ redirect(logout_redirect)
+ end
+
+ redirect_response_error("invalid_request")
+ end
+ end
+
+ def openid_configuration(alt_issuer = nil)
request.on(".well-known/openid-configuration") do
request.get do
- json_response_success(openid_configuration_body(issuer), cache: true)
+ json_response_success(openid_configuration_body(alt_issuer), cache: true)
end
end
end
def webfinger
@@ -340,10 +416,22 @@
params = json_access_token_payload(oauth_token)
params.delete("access_token")
params
end
+ # Logout
+
+ def validate_oidc_logout_params
+ redirect_response_error("invalid_request") unless param_or_nil("id_token_hint")
+ # check if valid token hint type
+ return unless (redirect_uri = param_or_nil("post_logout_redirect_uri"))
+
+ return if check_valid_uri?(redirect_uri)
+
+ redirect_response_error("invalid_request")
+ end
+
# Metadata
def openid_configuration_body(path)
metadata = oauth_server_metadata_body(path).select do |k, _|
VALID_METADATA_KEYS.include?(k)
@@ -366,9 +454,10 @@
response_types_supported += ["none", "id_token", "code token", "code id_token", "id_token token", "code id_token token"]
end
metadata.merge(
userinfo_endpoint: userinfo_url,
+ end_session_endpoint: (oidc_logout_url if use_rp_initiated_logout?),
response_types_supported: response_types_supported,
subject_types_supported: [oauth_jwt_subject_type],
id_token_signing_alg_values_supported: metadata[:token_endpoint_auth_signing_alg_values_supported],
id_token_encryption_alg_values_supported: [oauth_jwt_jwe_algorithm].compact,